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

This commit is contained in:
Vicent Llopis 2022-07-12 08:09:02 +02:00
commit 1eb6ee69cc
233 changed files with 10305 additions and 25764 deletions

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

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

View File

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

View File

@ -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);
}
};

View File

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

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');
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');

View File

@ -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);
});
});

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 @@
LOAD DATA LOCAL INFILE ?
INTO TABLE bucket
INTO TABLE `edi`.`bucket`
FIELDS TERMINATED BY ';'
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7, @col8, @col9, @col10, @col11, @col12)
SET

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
LOAD DATA LOCAL INFILE ?
INTO TABLE edi.supplier
INTO TABLE `edi`.`supplier`
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)
SET

View File

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

View File

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

View File

@ -19,137 +19,243 @@ 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);
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`;
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);
await fs.rmdir(tempDir, {recursive: true});
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;
}
if (fs.existsSync(paths.tempFile))
await fs.unlink(paths.tempFile);
async function getChecksum(file) {
const ftpClient = await getFtpClient();
console.debug(`Checking checksum for file ${file.name}...`);
await dumpData({file, entries, paths});
ftpClient.cat(`codes/${file.name}.txt`);
await fs.rmdir(paths.tempDir, {recursive: true});
const response = await new Promise((resolve, reject) => {
ftpClient.exec((err, response) => {
if (response.error) {
console.debug(`Error downloading checksum file... ${response.error}`);
reject(err);
}
resolve(response);
});
});
if (response && response.data) {
const fileContents = response.data;
const rows = fileContents.split('\n');
const row = rows[4];
const columns = row.split(/\s+/);
let fileChecksum;
if (file.keyValue)
fileChecksum = columns[1];
if (!file.keyValue)
fileChecksum = columns[0];
return fileChecksum;
}
}
async function downloadFile(remoteFile, tempFile) {
const ftpClient = await getFtpClient();
ftpClient.get(remoteFile, tempFile);
return new Promise((resolve, reject) => {
ftpClient.exec((err, response) => {
if (response.error) {
console.debug(`Error downloading file... ${response.error}`);
reject(err);
}
resolve(response);
});
});
}
async function dumpData({file, entries, paths}) {
const toTable = file.toTable;
const baseName = file.fileName;
async function extractFile(tempFile, tempDir) {
const JSZip = require('jszip');
for (const zipEntry of entries) {
const entryName = zipEntry.entryName;
console.log(`Reading file ${entryName}...`);
try {
await fs.mkdir(tempDir);
} catch (error) {
if (error.code !== 'EEXIST')
throw e;
}
const startIndex = (entryName.length - 10);
const endIndex = (entryName.length - 4);
const dateString = entryName.substring(startIndex, endIndex);
const lastUpdated = new Date();
const fileStream = await fs.readFile(tempFile);
if (fileStream) {
const zip = new JSZip();
const zipContents = await zip.loadAsync(fileStream);
// Format string date to a date object
let updated = null;
if (file.updated) {
updated = new Date(file.updated);
updated.setHours(0, 0, 0, 0);
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);
}
lastUpdated.setFullYear(`20${dateString.substring(4, 6)}`);
lastUpdated.setMonth(parseInt(dateString.substring(2, 4)) - 1);
lastUpdated.setDate(dateString.substring(0, 2));
lastUpdated.setHours(0, 0, 0, 0);
if (updated && lastUpdated <= updated) {
console.debug(`Table ${toTable} already updated, skipping...`);
continue;
}
console.log('Dumping data...');
const templatePath = path.join(__dirname, `./sql/${toTable}.sql`);
const sqlTemplate = fs.readFileSync(templatePath, 'utf8');
const rawPath = path.join(paths.tempDir, entryName);
try {
const tx = await Self.beginTransaction({});
const options = {transaction: tx};
await Self.rawSql(`DELETE FROM edi.${toTable}`, null, options);
await Self.rawSql(sqlTemplate, [rawPath], options);
await Self.rawSql(`
UPDATE edi.fileConfig
SET updated = ?
WHERE fileName = ?
`, [lastUpdated, baseName], options);
tx.commit();
} catch (error) {
tx.rollback();
throw error;
}
console.log(`Updated table ${toTable}\n`);
}
}
async function dumpData(tempDir, table) {
const toTable = table.toTable;
const baseName = table.fileName;
const firstEntry = entries[0];
const entryName = firstEntry.entryName;
const startIndex = (entryName.length - 10);
const endIndex = (entryName.length - 4);
const dateString = entryName.substring(startIndex, endIndex);
const lastUpdated = new Date();
let updated = null;
if (file.updated) {
updated = new Date(file.updated);
updated.setHours(0, 0, 0, 0);
}
// Format string date to a date object
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...`);
return;
}
const tx = await Self.beginTransaction({});
try {
const options = {transaction: tx};
const tableName = `edi.${toTable}`;
await Self.rawSql(`DELETE FROM ??`, [tableName], options);
const dirFiles = await fs.readdir(tempDir);
const files = dirFiles.filter(file => file.startsWith(baseName));
for (const file of files) {
console.log(`Dumping data from file ${file}...`);
const templatePath = path.join(__dirname, `./sql/${toTable}.sql`);
const sqlTemplate = await fs.readFile(templatePath, 'utf8');
const filePath = path.join(tempDir, file);
await Self.rawSql(sqlTemplate, [filePath], options);
await Self.rawSql(`
UPDATE edi.tableConfig
SET updated = ?
WHERE fileName = ?
`, [new Date(), baseName], options);
}
tx.commit();
} catch (error) {
tx.rollback();
throw error;
}
console.log(`Updated table ${toTable}\n`);
}
};

View File

@ -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);
};

View File

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

View File

@ -24,9 +24,6 @@
},
"isManaged":{
"type": "boolean"
},
"hasStowaway":{
"type": "boolean"
}
},
"acls": [

View File

@ -1,4 +1,4 @@
FROM mariadb:10.4.13
FROM mariadb:10.7.3
ENV MYSQL_ROOT_PASSWORD root
ENV TZ Europe/Madrid
@ -31,11 +31,13 @@ COPY \
import-changes.sh \
config.ini \
dump/mysqlPlugins.sql \
dump/mockDate.sql \
dump/structure.sql \
dump/dumpedFixtures.sql \
./
RUN gosu mysql docker-init.sh \
&& docker-dump.sh mysqlPlugins \
&& docker-dump.sh mockDate \
&& docker-dump.sh structure \
&& docker-dump.sh dumpedFixtures \
&& gosu mysql docker-temp-stop.sh

View File

@ -1,14 +1,14 @@
CREATE TABLE `vn`.`mdbBranch` (
CREATE TABLE IF NOT EXISTS `vn`.`mdbBranch` (
`name` VARCHAR(255),
PRIMARY KEY(`name`)
);
CREATE TABLE `vn`.`mdbVersion` (
CREATE TABLE IF NOT EXISTS `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');
INSERT IGNORE INTO `salix`.`ACL` (`id`, `model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES(318, '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,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,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

@ -1,4 +1,4 @@
DROP PROCEDURE IF EXISTS vn.ticket_doRefund;
DROP PROCEDURE IF EXISTS `vn`.`ticket_doRefund`;
DELIMITER $$
$$

View File

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

View File

@ -0,0 +1 @@
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,6 @@
INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalType,principalId)
VALUES
('ClaimObservation','*','WRITE','ALLOW','ROLE','salesPerson'),
('ClaimObservation','*','READ','ALLOW','ROLE','salesPerson'),
('Client','setPassword','WRITE','ALLOW','ROLE','salesPerson'),
('Client','updateUser','WRITE','ALLOW','ROLE','salesPerson');

View File

@ -0,0 +1,3 @@
alter table `vn`.`client`
add hasIncoterms tinyint(1) default 0 not null comment 'Received incoterms authorization from client';

View File

@ -0,0 +1,13 @@
DROP FUNCTION `account`.`userGetId`;
DROP FUNCTION `account`.`myUserGetName`;
DROP FUNCTION `account`.`myUserGetId`;
DROP FUNCTION `account`.`myUserHasRole`;
DROP FUNCTION `account`.`myUserHasRoleId`;
DROP FUNCTION `account`.`userGetName`;
DROP FUNCTION `account`.`userHasRole`;
DROP FUNCTION `account`.`userHasRoleId`;
DROP PROCEDURE `account`.`myUserLogout`;
DROP PROCEDURE `account`.`userLogin`;
DROP PROCEDURE `account`.`userLoginWithKey`;
DROP PROCEDURE `account`.`userLoginWithName`;
DROP PROCEDURE `account`.`userSetPassword`;

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`item` MODIFY COLUMN description TEXT CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL NULL;

View File

@ -0,0 +1,2 @@
INSERT INTO `vn`.`sample` (code, description, isVisible, hasCompany, hasPreview, datepickerEnabled)
VALUES ('incoterms-authorization', 'Autorización de incoterms', 1, 1, 1, 0);

View File

@ -0,0 +1,13 @@
CREATE TABLE `vn`.`claimObservation` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`claimFk` int(10) unsigned NOT NULL,
`workerFk` int(10) unsigned DEFAULT NULL,
`text` text COLLATE utf8_unicode_ci NOT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `worker_key` (`workerFk`),
KEY `claim_key` (`claimFk`),
KEY `claimObservation_created_IDX` (`created`) USING BTREE,
CONSTRAINT `claimObservation_ibfk_1` FOREIGN KEY (`claimFk`) REFERENCES `vn`.`claim` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `claimObservation_ibfk_2` FOREIGN KEY (`workerFk`) REFERENCES `vn`.`worker` (`id`) ON UPDATE CASCADE
) COMMENT='Todas las observaciones referentes a una reclamación'

View File

@ -0,0 +1,2 @@
INSERT INTO `vn`.`claimObservation` (`claimFk`, `text`, `created`)
SELECT `id`, `observation`, `created` FROM `vn`.`claim`

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

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

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

File diff suppressed because it is too large Load Diff

View File

@ -65,6 +65,7 @@ TABLES=(
ticketUpdateAction
time
volumeConfig
workCenter
)
dump_tables ${TABLES[@]}
@ -101,7 +102,6 @@ TABLES=(
media_type
professional_category
profile_type
workcenter
)
dump_tables ${TABLES[@]}
@ -111,4 +111,4 @@ TABLES=(
TiposTransacciones
TiposRetencion
)
dump_tables ${TABLES[@]}
dump_tables ${TABLES[@]}

View File

@ -96,5 +96,12 @@ mysqldump \
--databases \
${SCHEMAS[@]} \
${IGNORETABLES[@]} \
| sed 's/\bCURDATE\b/util.VN_CURDATE/ig'\
| sed 's/\bCURTIME\b/util.VN_CURTIME/ig' \
| sed 's/\bNOW\b/util.VN_NOW/ig' \
| sed 's/\bCURRENT_DATE\b/util.VN_CURDATE/ig' \
| sed 's/\bCURRENT_TIME\b/util.VN_CURTIME/ig' \
| sed 's/\bLOCALTIME\b/util.VN_NOW/ig' \
| sed 's/\bLOCALTIMESTAMP\b/util.VN_NOW/ig' \
| sed 's/ AUTO_INCREMENT=[0-9]* //g' \
> dump/structure.sql
> dump/structure.sql

View File

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

View File

@ -55,6 +55,7 @@ export default {
setPassword: '.vn-menu [name="setPassword"]',
activateAccount: '.vn-menu [name="enableAccount"]',
activateUser: '.vn-menu [name="activateUser"]',
deactivateUser: '.vn-menu [name="deactivateUser"]',
newPassword: 'vn-textfield[ng-model="$ctrl.newPassword"]',
repeatPassword: 'vn-textfield[ng-model="$ctrl.repeatPassword"]',
newRole: 'vn-autocomplete[ng-model="$ctrl.newRole"]',
@ -275,6 +276,7 @@ export default {
clientWebAccess: {
enableWebAccessCheckbox: 'vn-check[label="Enable web access"]',
userName: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.name"]',
email: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.email"]',
saveButton: 'button[type=submit]'
},
clientNotes: {
@ -542,7 +544,8 @@ export default {
searchResultDate: 'vn-ticket-summary [label=Landed] span',
topbarSearch: 'vn-searchbar',
moreMenu: 'vn-ticket-index vn-icon-button[icon=more_vert]',
sixthWeeklyTicket: 'vn-ticket-weekly-index vn-table vn-tr:nth-child(6)',
fourthWeeklyTicket: 'vn-ticket-weekly-index vn-table vn-tbody vn-tr:nth-child(4)',
fiveWeeklyTicket: 'vn-ticket-weekly-index vn-table vn-tbody vn-tr:nth-child(5)',
weeklyTicket: 'vn-ticket-weekly-index vn-table > div > vn-tbody > vn-tr',
firstWeeklyTicketDeleteIcon: 'vn-ticket-weekly-index vn-tr:nth-child(1) vn-icon-button[icon="delete"]',
firstWeeklyTicketAgency: 'vn-ticket-weekly-index vn-tr:nth-child(1) [ng-model="weekly.agencyModeFk"]',
@ -562,8 +565,6 @@ export default {
isDeletedIcon: 'vn-ticket-descriptor vn-icon[icon="icon-deletedTicket"]',
goBackToModuleIndexButton: 'vn-ticket-descriptor a[ui-sref="ticket.index"]',
moreMenu: 'vn-ticket-descriptor vn-ticket-descriptor-menu > vn-icon-button[icon=more_vert]',
moreMenuAddStowaway: '.vn-menu [name="addStowaway"]',
moreMenuDeleteStowawayButton: '.vn-menu [name="deleteStowaway"]',
moreMenuAddToTurn: '.vn-menu [name="addTurn"]',
moreMenuDeleteTicket: '.vn-menu [name="deleteTicket"]',
moreMenuRestoreTicket: '.vn-menu [name="restoreTicket"]',
@ -576,8 +577,6 @@ export default {
sendSMSbutton: 'button[response="accept"]',
changeShippedHourDialog: '.vn-dialog.shown',
changeShippedHour: '.vn-dialog.shown vn-input-time[ng-model="$ctrl.newShipped"]',
addStowawayDialogFirstTicket: '.vn-dialog.shown vn-table vn-tbody vn-tr',
shipButton: 'vn-ticket-descriptor vn-icon[icon="icon-stowaway"]',
thursdayButton: '.vn-popup.shown vn-tool-bar > vn-button:nth-child(4)',
saturdayButton: '.vn-popup.shown vn-tool-bar > vn-button:nth-child(6)',
acceptDialog: '.vn-dialog.shown button[response="accept"]',
@ -585,7 +584,6 @@ export default {
descriptorDeliveryDate: 'vn-ticket-descriptor slot-body > .attributes > vn-label-value:nth-child(4) > section > span',
descriptorDeliveryAgency: 'vn-ticket-descriptor slot-body > .attributes > vn-label-value:nth-child(5) > section > span',
acceptInvoiceOutButton: '.vn-confirm.shown button[response="accept"]',
acceptDeleteStowawayButton: '.vn-dialog.shown button[response="accept"]'
},
ticketNotes: {
firstNoteRemoveButton: 'vn-icon[icon="delete"]',
@ -595,7 +593,7 @@ export default {
submitNotesButton: 'button[type=submit]'
},
ticketExpedition: {
secondExpeditionRemoveButton: 'vn-ticket-expedition vn-table div > vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(1) > vn-icon-button[icon="delete"]',
thirdExpeditionRemoveButton: 'vn-ticket-expedition vn-table div > vn-tbody > vn-tr:nth-child(3) > vn-td:nth-child(1) > vn-icon-button[icon="delete"]',
expeditionRow: 'vn-ticket-expedition vn-table vn-tbody > vn-tr'
},
ticketPackages: {
@ -727,7 +725,7 @@ export default {
claimSummary: {
header: 'vn-claim-summary > vn-card > h5',
state: 'vn-claim-summary vn-label-value[label="State"] > section > span',
observation: 'vn-claim-summary vn-textarea[ng-model="$ctrl.summary.claim.observation"]',
observation: 'vn-claim-summary vn-horizontal.text',
firstSaleItemId: 'vn-claim-summary vn-horizontal > vn-auto:nth-child(5) vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(1) > span',
firstSaleDescriptorImage: '.vn-popover.shown vn-item-descriptor img',
itemDescriptorPopover: '.vn-popover.shown vn-item-descriptor',
@ -739,7 +737,6 @@ export default {
},
claimBasicData: {
claimState: 'vn-claim-basic-data vn-autocomplete[ng-model="$ctrl.claim.claimStateFk"]',
observation: 'vn-textarea[ng-model="$ctrl.claim.observation"]',
packages: 'vn-input-number[ng-model="$ctrl.claim.packages"]',
hasToPickUpCheckbox: 'vn-claim-basic-data vn-check[ng-model="$ctrl.claim.hasToPickUp"]',
saveButton: `button[type=submit]`
@ -769,6 +766,12 @@ export default {
secondClaimRedelivery: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.claimRedeliveryFk"]',
saveDevelopmentButton: 'button[type=submit]'
},
claimNote: {
addNoteFloatButton: 'vn-float-button',
note: 'vn-textarea[ng-model="$ctrl.note.text"]',
saveButton: 'button[type=submit]',
firstNoteText: 'vn-claim-note .text'
},
claimAction: {
importClaimButton: 'vn-claim-action vn-button[label="Import claim"]',
anyLine: 'vn-claim-action vn-tbody > vn-tr',
@ -903,52 +906,16 @@ export default {
dialogTimeInput: '.vn-dialog.shown vn-input-time[ng-model="$ctrl.newTimeEntry.timed"]',
dialogTimeDirection: '.vn-dialog.shown vn-autocomplete[ng-model="$ctrl.newTimeEntry.direction"]',
mondayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(1) > vn-icon-button',
tuesdayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(2) > vn-icon-button',
wednesdayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(3) > vn-icon-button',
thursdayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(4) > vn-icon-button',
fridayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(5) > vn-icon-button',
saturdayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(6) > vn-icon-button',
sundayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(7) > vn-icon-button',
firstEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(1) > vn-chip > div:nth-child(2)',
firstEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(1) > vn-chip > div:nth-child(2)',
firstEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(1) > vn-chip > div:nth-child(2)',
firstEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(1) > vn-chip > div:nth-child(2)',
firstEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(1) > vn-chip > div:nth-child(2)',
firstEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(1) > vn-chip > div:nth-child(2)',
firstEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(1) > vn-chip > div:nth-child(2)',
firstEntryOfMondayDelete: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(1) > vn-chip > vn-icon[icon="cancel"]',
secondEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(2) > vn-chip > div:nth-child(2)',
secondEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(2) > vn-chip > div:nth-child(2)',
secondEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(2) > vn-chip > div:nth-child(2)',
secondEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(2) > vn-chip > div:nth-child(2)',
secondEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(2) > vn-chip > div:nth-child(2)',
secondEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(2) > vn-chip > div:nth-child(2)',
secondEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(2) > vn-chip > div:nth-child(2)',
thirdEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(3) > vn-chip > div:nth-child(2)',
thirdEntryOfMondayDelete: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(3) > vn-chip > vn-icon[icon="cancel"]',
thirdEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(3) > vn-chip > div:nth-child(2)',
thirdEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(3) > vn-chip > div:nth-child(2)',
thirdEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(3) > vn-chip > div:nth-child(2)',
thirdEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(3) > vn-chip > div:nth-child(2)',
thirdEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(3) > vn-chip > div:nth-child(2)',
thirdEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(3) > vn-chip > div:nth-child(2)',
fourthEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(4) > vn-chip > div:nth-child(2)',
fourthEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(4) > vn-chip > div:nth-child(2)',
fourthEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(4) > vn-chip > div:nth-child(2)',
fourthEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(4) > vn-chip > div:nth-child(2)',
fourthEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(4) > vn-chip > div:nth-child(2)',
fourthEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(4) > vn-chip > div:nth-child(2)',
fourthEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(4) > vn-chip > div:nth-child(2)',
mondayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(1)',
tuesdayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(2)',
wednesdayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(3)',
thursdayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(4)',
fridayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(5)',
saturdayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(6)',
sundayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(7)',
weekWorkedHours: 'vn-worker-time-control vn-side-menu vn-label-value > section > span',
nextMonthButton: 'vn-worker-time-control vn-side-menu vn-calendar vn-button[icon=keyboard_arrow_right]',
previousMonthButton: 'vn-worker-time-control vn-side-menu vn-calendar vn-button[icon=keyboard_arrow_left]',
monthName: 'vn-worker-time-control vn-side-menu vn-calendar div > .title',
secondWeekDay: 'vn-worker-time-control vn-side-menu vn-calendar .day:nth-child(8) > .day-number',
thrirdWeekDay: 'vn-worker-time-control vn-side-menu vn-calendar .day:nth-child(15) > .day-number',
navigateBackToIndex: 'vn-worker-descriptor [name="goToModuleIndex"]'
},
workerCalendar: {

View File

@ -276,7 +276,7 @@ describe('Client Edit fiscalData path', () => {
// confirm invoice by address checkbox gets checked if the EQtax differs between addresses step 2
it(`should click on the 1st edit icon to access the address details and uncheck EQtax checkbox`, async() => {
await page.waitToClick(selectors.clientAddresses.firstEditAddress);
await page.waitForTextInField(selectors.clientAddresses.city, 'Silla');
await page.waitForTextInField(selectors.clientAddresses.city, 'Gotham');
await page.waitToClick(selectors.clientAddresses.equalizationTaxCheckbox);
await page.waitToClick(selectors.clientAddresses.saveButton);
const message = await page.waitForSnackbar();

View File

@ -45,7 +45,7 @@ describe('Client Add address path', () => {
expect(province).toContain('Province five');
});
it(`should receive an error after clicking save button as consignee, incoterms and customsAgent are empty`, async() => {
it(`should throw after clicking save button as consignee, incoterms and customsAgent are empty`, async() => {
await page.write(selectors.clientAddresses.consignee, 'Bruce Bunner');
await page.write(selectors.clientAddresses.streetAddress, '320 Park Avenue New York');
await page.waitToClick(selectors.clientAddresses.saveButton);

View File

@ -1,3 +1,4 @@
/* eslint max-len: ["error", { "code": 150 }]*/
import selectors from '../../helpers/selectors';
import getBrowser from '../../helpers/puppeteer';
@ -8,7 +9,7 @@ describe('Client Edit web access path', () => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('employee', 'client');
await page.accessToSearchResult('Bruce Banner');
await page.accessToSearchResult('max');
await page.accessToSection('client.card.webAccess');
});
@ -26,7 +27,16 @@ describe('Client Edit web access path', () => {
it(`should update the name`, async() => {
await page.clearInput(selectors.clientWebAccess.userName);
await page.write(selectors.clientWebAccess.userName, 'Hulk');
await page.write(selectors.clientWebAccess.userName, 'Legion');
await page.waitToClick(selectors.clientWebAccess.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it(`should update the email`, async() => {
await page.clearInput(selectors.clientWebAccess.email);
await page.write(selectors.clientWebAccess.email, 'legion@marvel.com');
await page.waitToClick(selectors.clientWebAccess.saveButton);
const message = await page.waitForSnackbar();
@ -43,30 +53,36 @@ describe('Client Edit web access path', () => {
it('should confirm web access name have been updated', async() => {
const result = await page.waitToGetProperty(selectors.clientWebAccess.userName, 'value');
expect(result).toEqual('Hulk');
expect(result).toEqual('Legion');
});
it('should confirm web access email have been updated', async() => {
const result = await page.waitToGetProperty(selectors.clientWebAccess.email, 'value');
expect(result).toEqual('legion@marvel.com');
});
it(`should navigate to the log section`, async() => {
await page.accessToSection('client.card.log');
});
it(`should confirm the last log is showing the updated client name and no modifications on the active checkbox`, async() => {
it(`should confirm the last log shows the updated client name and no modifications on active checkbox`, async() => {
let lastModificationPreviousValue = await page
.waitToGetProperty(selectors.clientLog.lastModificationPreviousValue, 'innerText');
let lastModificationCurrentValue = await page
.waitToGetProperty(selectors.clientLog.lastModificationCurrentValue, 'innerText');
expect(lastModificationPreviousValue).toEqual('name BruceBanner active false');
expect(lastModificationCurrentValue).toEqual('name Hulk active false');
expect(lastModificationPreviousValue).toEqual('name MaxEisenhardt active false');
expect(lastModificationCurrentValue).toEqual('name Legion active false');
});
it(`should confirm the penultimate log is showing the updated avtive field and no modifications on the client name`, async() => {
it(`should confirm the penultimate log shows the updated active and no modifications on client name`, async() => {
let penultimateModificationPreviousValue = await page
.waitToGetProperty(selectors.clientLog.penultimateModificationPreviousValue, 'innerText');
let penultimateModificationCurrentValue = await page
.waitToGetProperty(selectors.clientLog.penultimateModificationCurrentValue, 'innerText');
expect(penultimateModificationPreviousValue).toEqual('name BruceBanner active true');
expect(penultimateModificationCurrentValue).toEqual('name BruceBanner active false');
expect(penultimateModificationPreviousValue).toEqual('name MaxEisenhardt active true');
expect(penultimateModificationCurrentValue).toEqual('name MaxEisenhardt active false');
});
});

View File

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

View File

@ -1,3 +1,4 @@
/* eslint max-len: ["error", { "code": 150 }]*/
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
@ -10,414 +11,105 @@ describe('Worker time control path', () => {
await page.loginAndModule('salesBoss', 'worker');
await page.accessToSearchResult('HankPym');
await page.accessToSection('worker.card.timeControl');
await page.waitToClick(selectors.workerTimeControl.previousMonthButton);
await page.waitToClick(selectors.workerTimeControl.secondWeekDay);
});
afterAll(async() => {
await browser.close();
});
describe('as salesBoss', () => {
describe('on Monday', () => {
it('should scan in Hank Pym', async() => {
const scanTime = '07:00';
const eightAm = '08:00';
const fourPm = '16:00';
const hankPymId = 1107;
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText');
it('should go to the next month', async() => {
const date = new Date();
date.setMonth(date.getMonth() + 1);
const month = date.toLocaleString('default', {month: 'long'});
expect(result).toEqual(scanTime);
});
await page.waitToClick(selectors.workerTimeControl.nextMonthButton);
const result = await page.waitToGetProperty(selectors.workerTimeControl.monthName, 'innerText');
it(`should scan out Hank Pym for break`, async() => {
const scanTime = '10:00';
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfMonday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should scan in Hank Pym for a wrong hour and forget to scan in from the break`, async() => {
const scanTime = '18:00';
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfMonday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should delete the wrong entry for Hank Pym`, async() => {
const wrongScanTime = '18:00';
await page.waitForTextInElement(selectors.workerTimeControl.thirdEntryOfMonday, wrongScanTime);
await page.waitToClick(selectors.workerTimeControl.thirdEntryOfMondayDelete);
await page.respondToDialog('accept');
const message = await page.waitForSnackbar();
expect(message.text).toContain('Entry removed');
});
it(`should scan out Hank Pym to leave early`, async() => {
const scanTime = '14:00';
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfMonday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should add the break's scan in for Hank Pym and be in the right order`, async() => {
const scanTime = '10:20';
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfMonday, 'innerText');
expect(result).toEqual('14:00');
});
it(`should the third entry be the scan in from break`, async() => {
const scanTime = '10:20';
const result = await page
.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfMonday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should check Hank Pym worked 6:40 hours`, async() => {
await page.waitForTextInElement(selectors.workerTimeControl.mondayWorkedHours, '06:40 h.');
});
});
describe('on Tuesday', () => {
it('should happily scan in Hank Pym', async() => {
const scanTime = '08:00';
await page.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfTuesday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should happily scan out Hank Pym for break`, async() => {
const scanTime = '10:00';
await page.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfTuesday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should happily scan in Hank Pym from the break`, async() => {
const scanTime = '10:20';
await page.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfTuesday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should happily scan out Hank Pym for the day`, async() => {
const scanTime = '16:00';
await page.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfTuesday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should check Hank Pym worked 7:40 hours`, async() => {
await page.waitForTextInElement(selectors.workerTimeControl.tuesdayWorkedHours, '07:40 h.');
});
});
describe('on Wednesday', () => {
it('should cheerfully scan in Hank Pym', async() => {
const scanTime = '09:00';
await page.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfWednesday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should cheerfully scan out Hank Pym for break`, async() => {
const scanTime = '10:00';
await page.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfWednesday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should cheerfully scan in Hank Pym from the break`, async() => {
const scanTime = '10:20';
await page.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfWednesday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should cheerfully scan out Hank Pym for the day`, async() => {
const scanTime = '17:00';
await page.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfWednesday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should check Hank Pym worked 7:40 cheerfull hours`, async() => {
await page.waitForTextInElement(selectors.workerTimeControl.wednesdayWorkedHours, '07:40 h.');
});
});
describe('on Thursday', () => {
it('should joyfully scan in Hank Pym', async() => {
const scanTime = '09:59';
await page.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfThursday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should joyfully scan out Hank Pym for break`, async() => {
const scanTime = '10:00';
await page.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfThursday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should joyfully scan in Hank Pym from the break`, async() => {
const scanTime = '10:20';
await page.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfThursday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should joyfully scan out Hank Pym for the day`, async() => {
const scanTime = '17:59';
await page.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfThursday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should check Hank Pym worked 7:40 joyfull hours`, async() => {
await page.waitForTextInElement(selectors.workerTimeControl.thursdayWorkedHours, '07:40 h.');
});
});
describe('on Friday', () => {
it('should smilingly scan in Hank Pym', async() => {
const scanTime = '07:30';
await page.waitToClick(selectors.workerTimeControl.fridayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfFriday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should smilingly scan out Hank Pym for break`, async() => {
const scanTime = '10:00';
await page.waitToClick(selectors.workerTimeControl.fridayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfFriday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should smilingly scan in Hank Pym from the break`, async() => {
const scanTime = '10:20';
await page.waitToClick(selectors.workerTimeControl.fridayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfFriday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should smilingly scan out Hank Pym for the day`, async() => {
const scanTime = '15:30';
await page.waitToClick(selectors.workerTimeControl.fridayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfFriday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should check Hank Pym worked 7:40 hours with a smile on his face`, async() => {
await page.waitForTextInElement(selectors.workerTimeControl.fridayWorkedHours, '07:40 h.');
});
});
expect(result).toContain(month);
});
describe('as HHRR', () => {
describe('on Saturday', () => {
it('should log in as hr and pick the worker module', async() => {
await page.loginAndModule('hr', 'worker');
});
it('should go to current month', async() => {
const date = new Date();
const month = date.toLocaleString('default', {month: 'long'});
it('should search for a worker and access to its summary', async() => {
await page.accessToSearchResult('HankPym');
await page.waitForState('worker.card.summary');
});
await page.waitToClick(selectors.workerTimeControl.previousMonthButton);
const result = await page.waitToGetProperty(selectors.workerTimeControl.monthName, 'innerText');
it('should access to the time control section', async() => {
await page.accessToSection('worker.card.timeControl');
await page.waitForState('worker.card.timeControl');
await page.waitToClick(selectors.workerTimeControl.previousMonthButton);
await page.waitToClick(selectors.workerTimeControl.secondWeekDay);
});
it('should lovingly scan in Hank Pym', async() => {
const scanTime = '06:00';
await page.waitForTimeout(1000); // without this timeout the dialog doesn't pop up
await page.waitToClick(selectors.workerTimeControl.saturdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfSaturday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should lovingly scan out Hank Pym for the day with no break to leave a bit early`, async() => {
const scanTime = '13:40';
await page.waitToClick(selectors.workerTimeControl.saturdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfSaturday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should check Hank Pym worked 7:40 hours with all his will`, async() => {
await page.waitForTextInElement(selectors.workerTimeControl.saturdayWorkedHours, '07:40 h.');
});
});
describe('on Sunday', () => {
it('should gladly scan in Hank Pym', async() => {
const scanTime = '05:00';
await page.waitToClick(selectors.workerTimeControl.sundayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfSunday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should gladly scan out Hank Pym for the day with no break to leave a bit early`, async() => {
const scanTime = '12:40';
await page.waitToClick(selectors.workerTimeControl.sundayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfSunday, 'innerText');
expect(result).toEqual(scanTime);
});
it(`should check Hank Pym worked 7:40 glad hours`, async() => {
await page.waitForTextInElement(selectors.workerTimeControl.sundayWorkedHours, '07:40 h.');
});
it(`should check Hank Pym doesn't have hours set on the next months second week`, async() => {
await page.waitToClick(selectors.workerTimeControl.nextMonthButton);
await page.waitToClick(selectors.workerTimeControl.secondWeekDay);
await page.waitForTextInElement(selectors.workerTimeControl.weekWorkedHours, '00:00 h.');
});
it(`should check he didn't scan in this week yet`, async() => {
await page.waitToClick(selectors.workerTimeControl.navigateBackToIndex);
await page.accessToSearchResult('salesBoss');
await page.accessToSection('worker.card.timeControl');
const wholeWeekHours = await page
.waitToGetProperty(selectors.workerTimeControl.weekWorkedHours, 'innerText');
expect(wholeWeekHours).toEqual('00:00 h.');
});
});
expect(result).toContain(month);
});
describe('after all this amazing week', () => {
it('should log in Hank', async() => {
await page.loginAndModule('HankPym', 'worker');
await page.accessToSearchResult('HankPym');
await page.accessToSection('worker.card.timeControl');
await page.waitToClick(selectors.workerTimeControl.previousMonthButton);
await page.waitToClick(selectors.workerTimeControl.secondWeekDay);
});
it('should go 1 month in the past', async() => {
const date = new Date();
date.setMonth(date.getMonth() - 1);
const timestamp = Math.round(date.getTime() / 1000);
const month = date.toLocaleString('default', {month: 'long'});
it('should check his weekly hours are alright', async() => {
await page.waitForTextInElement(selectors.workerTimeControl.weekWorkedHours, '52:40 h.');
});
await page.loginAndModule('salesBoss', 'worker');
await page.goto(`http://localhost:5000/#!/worker/${hankPymId}/time-control?timestamp=${timestamp}`);
await page.waitToClick(selectors.workerTimeControl.secondWeekDay);
const result = await page.waitToGetProperty(selectors.workerTimeControl.monthName, 'innerText');
expect(result).toContain(month);
});
it(`should return error when insert 'out' of first entry`, async() => {
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, eightAm);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const message = await page.waitForSnackbar();
expect(message.text).toBeDefined();
});
it(`should insert 'in' monday`, async() => {
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, eightAm);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText');
expect(result).toEqual(eightAm);
});
it(`should insert 'out' monday`, async() => {
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, fourPm);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfMonday, 'innerText');
expect(result).toEqual(fourPm);
});
it(`should check Hank Pym worked 8:20 hours`, async() => {
await page.waitForTextInElement(selectors.workerTimeControl.mondayWorkedHours, '08:20 h.');
await page.waitForTextInElement(selectors.workerTimeControl.weekWorkedHours, '08:20 h.');
});
it('should remove first entry of monday', async() => {
await page.waitForTextInElement(selectors.workerTimeControl.firstEntryOfMonday, eightAm);
await page.waitForTextInElement(selectors.workerTimeControl.secondEntryOfMonday, fourPm);
await page.waitToClick(selectors.workerTimeControl.firstEntryOfMondayDelete);
await page.respondToDialog('accept');
const message = await page.waitForSnackbar();
expect(message.text).toContain('Entry removed');
});
it(`should be the 'out' the first entry of monday`, async() => {
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText');
expect(result).toEqual(fourPm);
});
it('should change week of month', async() => {
await page.waitToClick(selectors.workerTimeControl.thrirdWeekDay);
await page.waitForTextInElement(selectors.workerTimeControl.mondayWorkedHours, '00:00 h.');
});
});

View File

@ -18,7 +18,7 @@ describe('Ticket expeditions and log path', () => {
});
it(`should delete a former expedition and confirm the remaining expedition are the expected ones`, async() => {
await page.waitToClick(selectors.ticketExpedition.secondExpeditionRemoveButton);
await page.waitToClick(selectors.ticketExpedition.thirdExpeditionRemoveButton);
await page.waitToClick(selectors.globalItems.acceptButton);
await page.reloadSection('ticket.card.expedition');

View File

@ -42,7 +42,7 @@ describe('Ticket Edit basic data path', () => {
expect(disabled).toBeFalsy();
});
it(`should check the zone is for Silla247`, async() => {
it(`should check the zone is for Gotham247`, async() => {
let zone = await page
.waitToGetProperty(selectors.ticketBasicData.zone, 'value');
@ -63,7 +63,7 @@ describe('Ticket Edit basic data path', () => {
let zone = await page
.waitToGetProperty(selectors.ticketBasicData.agency, 'value');
expect(zone).toContain('Silla247Expensive');
expect(zone).toContain('Gotham247Expensive');
});
it(`should click next`, async() => {
@ -92,7 +92,7 @@ describe('Ticket Edit basic data path', () => {
});
it(`should split ticket without negatives`, async() => {
const newAgency = 'Silla247';
const newAgency = 'Gotham247';
const newDate = new Date();
newDate.setDate(newDate.getDate() - 1);

View File

@ -45,7 +45,7 @@ describe('Ticket descriptor path', () => {
it('should confirm the ticket 11 was added to thursday', async() => {
await page.accessToSection('ticket.weekly.index');
const result = await page.waitToGetProperty(selectors.ticketsIndex.sixthWeeklyTicket, 'value');
const result = await page.waitToGetProperty(selectors.ticketsIndex.fourthWeeklyTicket, 'value');
expect(result).toEqual('Thursday');
});
@ -80,7 +80,7 @@ describe('Ticket descriptor path', () => {
it('should confirm the ticket 11 was added on saturday', async() => {
await page.accessToSection('ticket.weekly.index');
const result = await page.waitToGetProperty(selectors.ticketsIndex.sixthWeeklyTicket, 'value');
const result = await page.waitToGetProperty(selectors.ticketsIndex.fiveWeeklyTicket, 'value');
expect(result).toEqual('Saturday');
});
@ -108,7 +108,7 @@ describe('Ticket descriptor path', () => {
});
it('should update the agency then remove it afterwards', async() => {
await page.autocompleteSearch(selectors.ticketsIndex.firstWeeklyTicketAgency, 'Silla247');
await page.autocompleteSearch(selectors.ticketsIndex.firstWeeklyTicketAgency, 'Gotham247');
let message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');

View File

@ -75,59 +75,6 @@ describe('Ticket descriptor path', () => {
});
});
describe('Add stowaway', () => {
it('should search for a ticket', async() => {
await page.accessToSearchResult('16');
await page.waitForState('ticket.card.summary');
});
it('should open the add stowaway dialog', async() => {
await page.waitForFunction(() => {
let element = document.querySelector('vn-ticket-descriptor-menu');
return element.$ctrl.canShowStowaway === true;
});
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitToClick(selectors.ticketDescriptor.moreMenuAddStowaway);
await page.waitForSelector(selectors.ticketDescriptor.addStowawayDialogFirstTicket);
const isVisible = await page.isVisible(selectors.ticketDescriptor.addStowawayDialogFirstTicket);
expect(isVisible).toBeTruthy();
});
it('should add a ticket as stowaway', async() => {
await page.waitToClick(selectors.ticketDescriptor.addStowawayDialogFirstTicket);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it(`should check the state of the stowaway ticket is embarked`, async() => {
await page.waitForState('ticket.card.summary');
const state = await page.waitToGetProperty(selectors.ticketDescriptor.stateLabelValue, 'innerText');
expect(state).toEqual('State Embarcando');
});
it(`should navigate back to the added ticket using the descriptors ship button`, async() => {
await page.waitToClick(selectors.ticketDescriptor.shipButton);
await page.waitForState('ticket.card.summary');
});
it('should delete the stowaway', async() => {
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitForContentLoaded();
await page.waitToClick(selectors.ticketDescriptor.moreMenuDeleteStowawayButton);
await page.waitToClick(selectors.ticketDescriptor.acceptDeleteStowawayButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it(`should confirm the ship buton doesn't exisist any more`, async() => {
await page.waitForSelector(selectors.ticketDescriptor.shipButton, {hidden: true});
});
});
describe('Make invoice', () => {
it('should login as administrative role then search for a ticket', async() => {
const invoiceableTicketId = '14';

View File

@ -6,7 +6,6 @@ describe('Ticket create path', () => {
let page;
let nextMonth = new Date();
nextMonth.setMonth(nextMonth.getMonth() + 1);
let stowawayTicketId;
beforeAll(async() => {
browser = await getBrowser();
@ -27,7 +26,7 @@ describe('Ticket create path', () => {
await page.autocompleteSearch(selectors.createTicketView.client, 'Clark Kent');
await page.pickDate(selectors.createTicketView.deliveryDate, nextMonth);
await page.autocompleteSearch(selectors.createTicketView.warehouse, 'Warehouse Two');
await page.autocompleteSearch(selectors.createTicketView.agency, 'Silla247');
await page.autocompleteSearch(selectors.createTicketView.agency, 'Gotham247');
await page.waitToClick(selectors.createTicketView.createButton);
const message = await page.waitForSnackbar();
@ -36,8 +35,6 @@ describe('Ticket create path', () => {
it('should check the url is now the summary of the ticket', async() => {
await page.waitForState('ticket.card.summary');
stowawayTicketId = await page.waitToGetProperty(selectors.ticketSummary.descriptorTicketId, 'innerText');
stowawayTicketId = stowawayTicketId.substring(1);
});
it('should again open the new ticket form', async() => {
@ -50,7 +47,7 @@ describe('Ticket create path', () => {
await page.autocompleteSearch(selectors.createTicketView.client, 'Clark Kent');
await page.pickDate(selectors.createTicketView.deliveryDate, nextMonth);
await page.autocompleteSearch(selectors.createTicketView.warehouse, 'Warehouse One');
await page.autocompleteSearch(selectors.createTicketView.agency, 'Silla247');
await page.autocompleteSearch(selectors.createTicketView.agency, 'Gotham247');
await page.waitToClick(selectors.createTicketView.createButton);
const message = await page.waitForSnackbar();
@ -61,15 +58,6 @@ describe('Ticket create path', () => {
await page.waitForState('ticket.card.summary');
});
it('should make the previously created ticket the stowaway of the current ticket', async() => {
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitToClick(selectors.ticketDescriptor.moreMenuAddStowaway);
await page.waitToClick(selectors.ticketDescriptor.addStowawayDialogFirstTicket);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should delete the current ticket', async() => {
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitToClick(selectors.ticketDescriptor.moreMenuDeleteTicket);
@ -78,11 +66,4 @@ describe('Ticket create path', () => {
expect(message.text).toContain('Ticket deleted. You can undo this action within the first hour');
});
it('should search for the stowaway ticket of the previously deleted ticket', async() => {
await page.accessToSearchResult(stowawayTicketId);
const result = await page.countElement(selectors.ticketDescriptor.shipButton);
expect(result).toBe(0);
});
});

View File

@ -22,8 +22,6 @@ describe('Claim edit basic data path', () => {
it(`should edit claim state and observation fields`, async() => {
await page.autocompleteSearch(selectors.claimBasicData.claimState, 'Gestionado');
await page.clearTextarea(selectors.claimBasicData.observation);
await page.write(selectors.claimBasicData.observation, 'edited observation');
await page.clearInput(selectors.claimBasicData.packages);
await page.write(selectors.claimBasicData.packages, '2');
await page.waitToClick(selectors.claimBasicData.saveButton);
@ -59,13 +57,6 @@ describe('Claim edit basic data path', () => {
expect(hasToPickUpCheckbox).toBe('checked');
});
it('should confirm the claim observation was edited', async() => {
const result = await page
.waitToGetProperty(selectors.claimBasicData.observation, 'value');
expect(result).toEqual('edited observation');
});
it('should confirm the claim packages was edited', async() => {
const result = await page
.waitToGetProperty(selectors.claimBasicData.packages, 'value');
@ -75,8 +66,6 @@ describe('Claim edit basic data path', () => {
it(`should edit the claim to leave it untainted`, async() => {
await page.autocompleteSearch(selectors.claimBasicData.claimState, 'Pendiente');
await page.clearTextarea(selectors.claimBasicData.observation);
await page.write(selectors.claimBasicData.observation, 'Observation one');
await page.clearInput(selectors.claimBasicData.packages);
await page.write(selectors.claimBasicData.packages, '0');
await page.waitToClick(selectors.claimBasicData.saveButton);

View File

@ -37,7 +37,7 @@ describe('Claim summary path', () => {
});
it('should display the observation', async() => {
const result = await page.waitToGetProperty(selectors.claimSummary.observation, 'value');
const result = await page.waitToGetProperty(selectors.claimSummary.observation, 'innerText');
expect(result).toContain('Wisi forensibus mnesarchum in cum. Per id impetus abhorreant');
});

View File

@ -0,0 +1,46 @@
import selectors from '../../helpers/selectors';
import getBrowser from '../../helpers/puppeteer';
describe('Claim Add note path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('salesPerson', 'claim');
await page.accessToSearchResult('2');
await page.accessToSection('claim.card.note.index');
});
afterAll(async() => {
await browser.close();
});
it(`should reach the claim note index`, async() => {
await page.waitForState('claim.card.note.index');
});
it(`should click on the add new note button`, async() => {
await page.waitToClick(selectors.claimNote.addNoteFloatButton);
await page.waitForState('claim.card.note.create');
});
it(`should create a new note`, async() => {
await page.waitForSelector(selectors.claimNote.note);
await page.type(`${selectors.claimNote.note} textarea`, 'The delivery was unsuccessful');
await page.waitToClick(selectors.claimNote.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it(`should redirect back to the claim notes page`, async() => {
await page.waitForState('claim.card.note.index');
});
it('should confirm the note was created', async() => {
const result = await page.waitToGetProperty(selectors.claimNote.firstNoteText, 'innerText');
expect(result).toEqual('The delivery was unsuccessful');
});
});

View File

@ -34,7 +34,7 @@ describe('Order summary path', () => {
it('should check the summary contains the order consignee', async() => {
const result = await page.waitToGetProperty(selectors.orderSummary.consignee, 'innerText');
expect(result).toEqual('address 26 - Silla (Province one)');
expect(result).toEqual('address 26 - Gotham (Province one)');
});
it('should check the summary contains the order subtotal', async() => {

View File

@ -9,7 +9,7 @@ describe('Route summary path', () => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('employee', 'route');
await page.waitToClick('vn-route-index vn-tbody > a:nth-child(1)');
await page.waitToClick('vn-route-index vn-tbody > a:nth-child(7)');
});
afterAll(async() => {

View File

@ -36,8 +36,7 @@ describe('Account create and basic data path', () => {
await page.waitForState('account.card.basicData');
});
it('should reload the section and check the name is as expected', async() => {
await page.reloadSection('account.card.basicData');
it('should check the name is as expected', async() => {
const result = await page.waitToGetProperty(selectors.accountBasicData.name, 'value');
expect(result).toEqual('Remy');
@ -103,25 +102,39 @@ describe('Account create and basic data path', () => {
});
});
// creating the account without the active property set to true seems to be creating an active user anyways
// describe('activate user', () => {
// it(`should check the inactive user icon is present in the descriptor`, async() => {
// await page.waitForSelector(selectors.accountDescriptor.activeUserIcon, {visible: true});
// });
describe('deactivate user', () => {
it(`should check the inactive user icon isn't present in the descriptor just yet`, async() => {
await page.waitForNumberOfElements(selectors.accountDescriptor.activeUserIcon, 0);
});
// it('should activate the user using the descriptor menu', async() => {
// await page.waitToClick(selectors.accountDescriptor.menuButton);
// await page.waitToClick(selectors.accountDescriptor.activateUser);
// await page.waitToClick(selectors.accountDescriptor.acceptButton);
// const message = await page.waitForSnackbar();
it('should deactivate the user using the descriptor menu', async() => {
await page.waitToClick(selectors.accountDescriptor.menuButton);
await page.waitToClick(selectors.accountDescriptor.deactivateUser);
await page.waitToClick(selectors.accountDescriptor.acceptButton);
const message = await page.waitForSnackbar();
// expect(message.text).toContain('user enabled?');
// });
expect(message.text).toContain('User deactivated!');
});
// it('should check the inactive user icon is not present anymore', async() => {
// await page.waitForNumberOfElements(selectors.accountDescriptor.activeUserIcon, 0);
// });
// });
it('should check the inactive user icon is now present', async() => {
await page.waitForNumberOfElements(selectors.accountDescriptor.activeUserIcon, 1);
});
});
describe('activate user', () => {
it('should activate the user using the descriptor menu', async() => {
await page.waitToClick(selectors.accountDescriptor.menuButton);
await page.waitToClick(selectors.accountDescriptor.activateUser);
await page.waitToClick(selectors.accountDescriptor.acceptButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('User activated!');
});
it('should check the inactive user icon is not present anymore', async() => {
await page.waitForNumberOfElements(selectors.accountDescriptor.activeUserIcon, 0);
});
});
describe('mail forwarding', () => {
it('should activate the mail forwarding and set the recipent email', async() => {

View File

@ -56,7 +56,7 @@ describe('Account Alias create and basic data path', () => {
expect(result).toContain('psykers');
});
it('should search for the IT alias group then access to the users section then check the role listed is the expected one', async() => {
it('should search IT alias then access the user section to check the role listed is the expected one', async() => {
await page.accessToSearchResult('IT');
await page.accessToSection('account.alias.card.users');
const rolesCount = await page.countElement(selectors.accountAliasUsers.anyResult);

View File

@ -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();

View File

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

View File

@ -10,6 +10,7 @@ export default class App {
constructor() {
this.loaderStatus = 0;
this.loading = false;
this.versionInterval = setInterval(this.getVersion.bind(this), 300000);
}
showMessage(message) {
@ -38,6 +39,21 @@ export default class App {
if (this.loaderStatus === 0)
this.loading = false;
}
getVersion() {
this.logger.$http.get('Applications/status');
}
setVersion(newVersion) {
if (newVersion) {
const currentVersion = localStorage.getItem('salix-version');
if (newVersion != currentVersion) {
this.hasNewVersion = true;
clearInterval(this.versionInterval);
}
localStorage.setItem('salix-version', newVersion);
}
}
}
ngModule.service('vnApp', App);

View File

@ -30,14 +30,17 @@ function interceptor($q, vnApp, vnToken, $translate) {
},
response(response) {
vnApp.popLoader();
const newVersion = response.headers('salix-version');
vnApp.setVersion(newVersion);
return response;
},
responseError(rejection) {
vnApp.popLoader();
let err = new HttpError(rejection.statusText);
const err = new HttpError(rejection.statusText);
Object.assign(err, rejection);
return $q.reject(err);
}
},
};
}
ngModule.factory('vnInterceptor', interceptor);

View File

@ -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";
@ -350,9 +343,6 @@
.icon-splur:before {
content: "\e970";
}
.icon-stowaway:before {
content: "\e94f";
}
.icon-supplier:before {
content: "\e925";
}

View File

@ -85,8 +85,6 @@
<glyph unicode="&#xe94b;" glyph-name="deaulter" d="M677.973-64c-30.72 35.84-61.867 70.827-91.307 107.52-40.96 51.2-80.64 103.253-121.173 154.88-16.64 21.333-21.76 20.48-30.72-4.693-13.227-36.693-25.6-73.387-40.107-109.653-5.12-12.8-13.227-26.88-24.32-34.56-51.627-34.987-104.107-69.12-157.867-100.693-10.667-6.4-30.72-5.547-41.813 0.853-8.107 4.693-12.373 23.893-11.093 35.84 0.853 8.96 11.093 19.627 19.627 25.6 39.253 26.453 78.933 51.627 119.040 76.8 18.347 11.52 30.293 26.027 35.84 47.787 12.373 48.213 27.307 95.573 39.253 143.36 8.533 33.707 26.88 58.88 56.32 77.227 40.533 25.173 80.64 52.053 120.747 78.507 6.4 4.267 10.24 11.52 15.36 17.493-7.253 2.56-14.933 7.253-22.187 6.827-75.52-6.4-151.467-13.227-226.987-20.48-2.133 0-4.693-0.853-6.827-0.853-22.613-1.707-39.253 10.24-40.96 29.867s12.373 33.707 35.413 35.84c45.227 4.267 90.88 8.96 136.107 12.8 65.707 5.547 131.84 10.667 197.547 15.36 26.027 1.707 53.76-21.76 67.413-55.467 9.813-23.893 5.12-46.080-18.347-65.28-49.92-40.107-100.693-78.933-151.040-118.187-23.040-17.92-23.893-23.467-6.4-46.507 58.453-78.080 116.48-156.587 174.933-234.667 27.307-36.693 25.173-50.773-12.373-75.52-5.12 0-9.813 0-14.080 0zM791.893 649.813c-43.093 1.28-76.373-31.573-77.227-75.52-0.853-44.373 29.44-76.8 72.107-77.653 45.227-1.28 77.653 29.44 78.080 73.813 0.427 45.227-29.44 78.080-72.96 79.36zM671.147 737.707c0-72.107-34.133-136.107-87.467-176.64l-235.52-21.76c-72.107 36.693-122.027 111.787-122.027 198.4 0 122.88 99.84 222.293 222.72 222.293 122.453 0 222.293-99.413 222.293-222.293zM592.213 680.533l-50.347 18.347c-2.133-8.533-5.12-16.213-9.813-22.613-5.12-6.4-10.24-11.947-16.213-17.067-5.973-4.267-12.373-8.107-19.2-11.093s-13.653-4.693-20.053-5.547c-17.92-2.987-33.707-0.427-48.64 6.827s-26.88 18.347-36.693 32.853l76.373 12.373 7.253 32.427-97.28-15.787c-1.28 5.547-2.987 11.093-3.84 16.64l-0.853 4.267 99.413 16.213 7.253 32.427-106.667-17.493c0.853 9.387 2.987 17.493 6.4 26.027 3.84 8.533 8.107 16.213 14.080 23.040 5.547 6.827 12.8 12.373 21.333 17.067s17.92 8.107 28.587 9.813c6.827 1.28 13.227 1.707 20.907 1.28s14.507-1.707 21.333-3.84c6.827-2.133 13.653-5.973 20.053-10.24 5.973-4.693 11.947-11.093 17.493-18.773l38.827 37.973c-13.227 17.92-30.293 31.147-52.053 39.253-21.76 8.533-46.080 10.667-73.387 6.4-19.627-2.987-36.267-9.387-51.2-17.92-14.507-8.533-26.88-19.2-37.547-32-10.667-12.373-18.773-26.027-23.893-40.96-5.547-14.507-8.96-29.867-9.813-45.653l-21.76-3.84-7.253-32.427 29.013 4.693 0.427-2.987c1.28-6.827 2.56-12.8 4.267-18.347l-23.467-3.84-8.107-32.427 43.52 7.253c6.827-13.653 15.787-26.027 26.027-36.693 10.24-11.52 22.187-20.48 35.413-27.733 13.227-7.68 27.307-12.8 42.667-15.787s31.573-3.413 47.36-0.853c12.373 2.133 24.32 5.12 35.84 10.667s22.613 11.52 32.853 19.2c10.24 8.107 18.347 16.64 26.027 26.453 6.827 9.387 12.373 20.48 15.36 32.427z" />
<glyph unicode="&#xe94c;" glyph-name="services" d="M951.467 217.6c0 8.533 0 21.333 0 29.867s0 21.333-4.267 29.867l64 51.2c4.267 4.267 8.533 12.8 4.267 21.333l-64 106.667c-4.267 8.533-12.8 8.533-17.067 8.533l-76.8-29.867c-17.067 12.8-34.133 21.333-51.2 29.867l-12.8 81.067c0 8.533-8.533 12.8-17.067 12.8h-123.733c-8.533 0-12.8-4.267-17.067-12.8l-12.8-81.067c-17.067-8.533-38.4-17.067-51.2-29.867l-76.8 29.867c-8.533 4.267-17.067 0-17.067-8.533l-64-106.667c-4.267-8.533-4.267-17.067 4.267-21.333l64-51.2c0-8.533-4.267-21.333-4.267-29.867s0-21.333 4.267-29.867l-55.467-51.2c-4.267-4.267-8.533-12.8-4.267-21.333l64-106.667c4.267-8.533 12.8-8.533 17.067-8.533l76.8 29.867c17.067-12.8 34.133-21.333 51.2-29.867l12.8-81.067c0-8.533 8.533-12.8 17.067-12.8h123.733c8.533 0 12.8 4.267 17.067 12.8l12.8 81.067c17.067 8.533 38.4 17.067 51.2 29.867l76.8-29.867c8.533-4.267 17.067 0 17.067 8.533l64 106.667c4.267 8.533 4.267 17.067-4.267 21.333 0 0-68.267 51.2-68.267 51.2zM721.067 132.267c-64 0-115.2 51.2-115.2 115.2s51.2 115.2 115.2 115.2 115.2-51.2 115.2-115.2c0-64-51.2-115.2-115.2-115.2zM345.6 174.933h-89.6v102.4h81.067c4.267 34.133 8.533 68.267 21.333 102.4h-102.4v102.4h162.133c34.133 42.667 72.533 76.8 119.467 102.4h-281.6v102.4h520.533v-59.733c51.2-8.533 102.4-25.6 145.067-51.2v281.6c0 55.467-46.933 102.4-102.4 102.4h-622.933c-55.467 0-102.4-46.933-102.4-102.4v-819.2c0-55.467 46.933-102.4 102.4-102.4h302.933c-81.067 55.467-136.533 140.8-153.6 238.933z" />
<glyph unicode="&#xe94d;" glyph-name="albaran" d="M819.2 960h-622.933c-55.467 0-102.4-46.933-102.4-102.4v-819.2c0-55.467 46.933-102.4 102.4-102.4h622.933c55.467 0 102.4 46.933 102.4 102.4v819.2c0 55.467-46.933 102.4-102.4 102.4zM358.4 174.933h-102.4v102.4h503.467v-102.4h-401.067zM256 379.733v102.4h503.467v-102.4h-503.467zM759.467 584.533h-503.467v102.4h503.467v-102.4z" />
<glyph unicode="&#xe94e;" glyph-name="solunion" d="M759.467 870.4v-136.533h-601.6c0 0-128-341.333 106.667-341.333s469.333 0 469.333 0 34.133 0 34.133-34.133-8.533-98.133-8.533-98.133h-541.867c0 0-247.467 29.867-204.8 320 0 0 8.533 140.8 72.533 298.667 0 0 21.333-8.533 85.333-8.533h588.8zM853.333 25.6c64 0 85.333-8.533 85.333-8.533 64 153.6 72.533 298.667 72.533 298.667 42.667 290.133-204.8 320-204.8 320h-541.867c0 0-8.533-64-8.533-98.133s34.133-34.133 34.133-34.133 238.933 0 469.333 0 106.667-341.333 106.667-341.333h-601.6v-136.533h588.8z" />
<glyph unicode="&#xe94f;" glyph-name="stowaway" d="M1006.933 452.267l-260.267 106.667 29.867 29.867c4.267 4.267 4.267 12.8 4.267 17.067-4.267 4.267-8.533 8.533-12.8 8.533h-157.867c0 93.867 76.8 157.867 174.933 157.867 4.267 0 8.533 4.267 12.8 8.533s4.267 8.533 0 17.067l-81.067 153.6c-4.267 0-12.8 4.267-17.067 4.267-46.933 0-93.867-17.067-132.267-42.667-21.333-17.067-42.667-38.4-55.467-59.733-12.8 21.333-29.867 42.667-55.467 59.733-34.133 12.8-81.067 34.133-128 34.133-4.267 0-12.8-4.267-12.8-8.533l-85.333-153.6c-4.267-4.267-4.267-4.267 0-12.8 4.267-4.267 8.533-8.533 12.8-8.533 98.133 0 174.933-59.733 174.933-153.6v0h-140.8c-4.267 0-12.8-4.267-12.8-8.533-8.533-4.267-4.267-17.067 0-21.333l21.333-21.333-277.333-110.933c-8.533-8.533-12.8-12.8-8.533-21.333 0-8.533 8.533-12.8 17.067-12.8v0l98.133 4.267-81.067-85.333c0-4.267-4.267-8.533 0-12.8 0-4.267 4.267-8.533 8.533-8.533l85.333-34.133v-179.2c0-8.533 4.267-12.8 8.533-12.8l358.4-145.067h8.533l358.4 145.067c4.267 4.267 8.533 8.533 8.533 12.8v179.2l85.333 34.133c4.267 0 8.533 4.267 8.533 8.533s0 8.533-4.267 12.8l-68.267 98.133 102.4-4.267c8.533 0 12.8 4.267 17.067 12.8 8.533 0 4.267 4.267-4.267 12.8zM110.933 456.533l196.267 76.8 8.533-8.533-166.4-64-38.4-4.267zM153.6 285.867v0l-68.267 34.133 68.267 98.133 328.533-132.267-68.267-98.133-260.267 98.133zM490.667-29.867l-328.533 132.267v153.6l243.2-98.133h12.8c0 0 0 0 4.267 0v0c0 0 4.267 0 4.267 4.267l64 85.333c0-4.267 0-277.333 0-277.333zM490.667 324.267l-298.667 115.2 149.333 64 153.6-157.867v-17.067h-4.267zM529.067 337.067l157.867 157.867 140.8-55.467-298.667-115.2c0 0 0 12.8 0 12.8zM849.067 102.4l-328.533-132.267v281.6l64-85.333c0 0 0-4.267 4.267-4.267v0h17.067l243.2 98.133v-157.867zM938.667 324.267l-324.267-132.267-68.267 98.133 328.533 132.267 64-98.133zM870.4 460.8l-157.867 64 12.8 8.533 187.733-76.8-42.667 4.267z" />
<glyph unicode="&#xe950;" glyph-name="agency-term" d="M789.333 266.667c-55.467 0-102.4-46.933-102.4-102.4s46.933-102.4 102.4-102.4c55.467 0 102.4 46.933 102.4 102.4 0 59.733-46.933 102.4-102.4 102.4zM789.333 113.067c-29.867 0-51.2 21.333-51.2 51.2s21.333 51.2 51.2 51.2c29.867 0 51.2-21.333 51.2-51.2 0-25.6-25.6-51.2-51.2-51.2zM251.733 266.667c-55.467 0-102.4-46.933-102.4-102.4s46.933-102.4 102.4-102.4 102.4 46.933 102.4 102.4c0 59.733-46.933 102.4-102.4 102.4zM251.733 113.067c-29.867 0-51.2 21.333-51.2 51.2s21.333 51.2 51.2 51.2 51.2-21.333 51.2-51.2c0-25.6-25.6-51.2-51.2-51.2zM1006.933 539.733l-196.267 192c-12.8 12.8-29.867 17.067-46.933 17.067h-98.133v38.4c0 25.6-21.333 51.2-51.2 51.2h-563.2c-29.867 0-51.2-21.333-51.2-51.2v-554.667c0-29.867 25.6-51.2 51.2-51.2h68.267c8.533 64 64 115.2 132.267 115.2 64 0 123.733-51.2 132.267-115.2h268.8c8.533 64 64 115.2 132.267 115.2s128-51.2 136.533-115.2h51.2c29.867 0 51.2 25.6 51.2 51.2v260.267c0 17.067-8.533 34.133-17.067 46.933v0zM725.333 684.8c0 4.267 4.267 8.533 8.533 8.533h34.133c0 0 4.267 0 4.267-4.267l153.6-145.067c4.267 0 0-12.8-4.267-12.8h-187.733c-8.533 0-8.533 4.267-8.533 8.533v145.067zM509.013 556.373c0-113.92-92.16-206.080-206.080-206.080s-206.080 92.16-206.080 206.080 92.16 206.507 206.080 206.507 206.080-92.587 206.080-206.507zM342.613 494.080h-87.893l-15.36-40.107h-78.933l100.693 230.827h76.373l100.693-230.827h-80.213l-15.36 40.107zM321.28 550.4l-22.613 58.027-22.187-58.027h44.8z" />
<glyph unicode="&#xe951;" glyph-name="apps" d="M0 704h256v256h-256v-256zM384-64h256v256h-256v-256zM0-64h256v256h-256v-256zM0 320h256v256h-256v-256zM384 320h256v256h-256v-256zM768 960v-256h256v256h-256zM384 704h256v256h-256v-256zM768 320h256v256h-256v-256zM768-64h256v256h-256v-256z" />
<glyph unicode="&#xe952;" glyph-name="info" d="M512 960c-281.6 0-512-230.4-512-512s230.4-512 512-512 512 230.4 512 512-230.4 512-512 512zM563.2 192h-102.4v307.2h102.4v-307.2zM563.2 601.6h-102.4v102.4h102.4v-102.4z" />

Before

Width:  |  Height:  |  Size: 160 KiB

After

Width:  |  Height:  |  Size: 157 KiB

View File

@ -19,6 +19,14 @@
</div>
<vn-slot name="topbar"></vn-slot>
<div class="side end">
<vn-icon-button
id="refresh"
icon="refresh"
ng-if="$ctrl.vnApp.hasNewVersion"
ng-click="$ctrl.refresh()"
class="refresh"
translate-attr="{title: 'There is a new version, click here to reload'}">
</vn-icon-button>
<vn-icon-button
id="apps"
icon="apps"
@ -39,7 +47,6 @@
translate-attr="{title: 'Account'}"
on-error-src/>
</button>
</div>
<vn-menu vn-id="apps-menu">
<vn-list class="modules-menu">

View File

@ -26,6 +26,10 @@ export class Layout extends Component {
const token = this.vnToken.token;
return `/api/Images/user/160x160/${userId}/download?access_token=${token}`;
}
refresh() {
window.location.reload();
}
}
Layout.$inject = ['$element', '$scope', 'vnModules'];

View File

@ -60,6 +60,9 @@ vn-layout {
font-size: 1.05rem;
padding: 0;
}
.vn-icon-button.refresh {
color: $color-alert;
}
}
& > vn-side-menu > .menu {
display: flex;

View File

@ -17,6 +17,7 @@ Go to module summary: Ir a la vista previa del módulo
Show summary: Mostrar vista previa
What is new: Novedades de la versión
Settings: Ajustes
There is a new version, click here to reload: Hay una nueva versión, pulse aquí para recargar
# Actions

View File

@ -54,7 +54,6 @@
"You can't delete a confirmed order": "You can't delete a confirmed order",
"Value has an invalid format": "Value has an invalid format",
"The postcode doesn't exist. Please enter a correct one": "The postcode doesn't exist. Please enter a correct one",
"Can't create stowaway for this ticket": "Can't create stowaway for this ticket",
"Swift / BIC can't be empty": "Swift / BIC can't be empty",
"Deleted sales from ticket": "I have deleted the following lines from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{deletions}}}",
"Added sale to ticket": "I have added the following line to the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{addition}}}",
@ -70,7 +69,6 @@
"Claim will be picked": "The product from the claim [({{claimId}})]({{{claimUrl}}}) from the client *{{clientName}}* will be picked",
"Claim state has changed to incomplete": "The state of the claim [({{claimId}})]({{{claimUrl}}}) from client *{{clientName}}* has changed to *incomplete*",
"Claim state has changed to canceled": "The state of the claim [({{claimId}})]({{{claimUrl}}}) from client *{{clientName}}* has changed to *canceled*",
"This ticket is not an stowaway anymore": "The ticket id [{{ticketId}}]({{{ticketUrl}}}) is not an stowaway anymore",
"Customs agent is required for a non UEE member": "Customs agent is required for a non UEE member",
"Incoterms is required for a non UEE member": "Incoterms is required for a non UEE member",
"Client checked as validated despite of duplication": "Client checked as validated despite of duplication from client id {{clientId}}",
@ -123,5 +121,14 @@
"The worker has hours recorded that day": "The worker has hours recorded that day",
"isWithoutNegatives": "isWithoutNegatives",
"routeFk": "routeFk",
"Not enough privileges to edit a client with verified data": "Not enough privileges to edit a client with verified data"
"Not enough privileges to edit a client with verified data": "Not enough privileges to edit a client with verified data",
"Can't change the password of another worker": "Can't change the password of another worker",
"No hay un contrato en vigor": "There is no existing contract",
"No está permitido trabajar": "Not allowed to work",
"Dirección incorrecta": "Wrong direction",
"No se permite fichar a futuro": "It is not allowed to sign in the future",
"Descanso diario 12h.": "Daily rest 12h.",
"Fichadas impares": "Odd signs",
"Descanso diario 9h.": "Daily rest 9h.",
"Descanso semanal 36h. / 72h.": "Weekly rest 36h. / 72h."
}

View File

@ -1,229 +1,23 @@
{
"Phone format is invalid": "El formato del teléfono no es correcto",
"You are not allowed to change the credit": "No tienes privilegios para modificar el crédito",
"Unable to mark the equivalence surcharge": "No se puede marcar el recargo de equivalencia",
"The default consignee can not be unchecked": "No se puede desmarcar el consignatario predeterminado",
"Unable to default a disabled consignee": "No se puede poner predeterminado un consignatario desactivado",
"Can't be blank": "No puede estar en blanco",
"Invalid TIN": "NIF/CIF invalido",
"TIN must be unique": "El NIF/CIF debe ser único",
"A client with that Web User name already exists": "Ya existe un cliente con ese Usuario Web",
"Is invalid": "Is invalid",
"Quantity cannot be zero": "La cantidad no puede ser cero",
"Enter an integer different to zero": "Introduce un entero distinto de cero",
"Package cannot be blank": "El embalaje no puede estar en blanco",
"The company name must be unique": "La razón social debe ser única",
"Invalid email": "Correo electrónico inválido",
"The IBAN does not have the correct format": "El IBAN no tiene el formato correcto",
"That payment method requires an IBAN": "El método de pago seleccionado requiere un IBAN",
"That payment method requires a BIC": "El método de pago seleccionado requiere un BIC",
"State cannot be blank": "El estado no puede estar en blanco",
"Worker cannot be blank": "El trabajador no puede estar en blanco",
"Cannot change the payment method if no salesperson": "No se puede cambiar la forma de pago si no hay comercial asignado",
"can't be blank": "El campo no puede estar vacío",
"Observation type must be unique": "El tipo de observación no puede repetirse",
"Name cannot be blank": "Name cannot be blank",
"Swift / BIC cannot be empty": "Swift / BIC cannot be empty",
"Street cannot be empty": "Street cannot be empty",
"City cannot be empty": "City cannot be empty",
"Invalid email": "Invalid email",
"Phone cannot be blank": "Phone cannot be blank",
"The credit must be an integer greater than or equal to zero": "The credit must be an integer greater than or equal to zero",
"The grade must be similar to the last one": "El grade debe ser similar al último",
"Only manager can change the credit": "Solo el gerente puede cambiar el credito de este cliente",
"Name cannot be blank": "El nombre no puede estar en blanco",
"Phone cannot be blank": "El teléfono no puede estar en blanco",
"Period cannot be blank": "El periodo no puede estar en blanco",
"Choose a company": "Selecciona una empresa",
"Se debe rellenar el campo de texto": "Se debe rellenar el campo de texto",
"Description should have maximum of 45 characters": "La descripción debe tener maximo 45 caracteres",
"Cannot be blank": "El campo no puede estar en blanco",
"The grade must be an integer greater than or equal to zero": "El grade debe ser un entero mayor o igual a cero",
"Sample type cannot be blank": "El tipo de plantilla no puede quedar en blanco",
"Description cannot be blank": "Se debe rellenar el campo de texto",
"The new quantity should be smaller than the old one": "La nueva cantidad debe de ser menor que la anterior",
"The value should not be greater than 100%": "El valor no debe de ser mayor de 100%",
"The value should be a number": "El valor debe ser un numero",
"This order is not editable": "Esta orden no se puede modificar",
"You can't create an order for a frozen client": "No puedes crear una orden para un cliente congelado",
"You can't create an order for a client that has a debt": "No puedes crear una orden para un cliente con deuda",
"is not a valid date": "No es una fecha valida",
"Barcode must be unique": "El código de barras debe ser único",
"The warehouse can't be repeated": "El almacén no puede repetirse",
"The tag can't be repeated": "El tag no puede repetirse",
"The observation type can't be repeated": "El tipo de observación no puede repetirse",
"A claim with that sale already exists": "Ya existe una reclamación para esta línea",
"You don't have enough privileges to change that field": "No tienes permisos para cambiar ese campo",
"Warehouse cannot be blank": "El almacén no puede quedar en blanco",
"Agency cannot be blank": "La agencia no puede quedar en blanco",
"Not enough privileges to edit a client with verified data": "No tienes permisos para hacer cambios en un cliente con datos comprobados",
"This address doesn't exist": "Este consignatario no existe",
"You must delete the claim id %d first": "Antes debes borrar la reclamación %d",
"You don't have enough privileges": "No tienes suficientes permisos",
"Cannot check Equalization Tax in this NIF/CIF": "No se puede marcar RE en este NIF/CIF",
"You can't make changes on the basic data of an confirmed order or with rows": "No puedes cambiar los datos basicos de una orden con artículos",
"INVALID_USER_NAME": "El nombre de usuario solo debe contener letras minúsculas o, a partir del segundo carácter, números o subguiones, no esta permitido el uso de la letra ñ",
"You can't create a ticket for a frozen client": "No puedes crear un ticket para un cliente congelado",
"You can't create a ticket for a inactive client": "No puedes crear un ticket para un cliente inactivo",
"Tag value cannot be blank": "El valor del tag no puede quedar en blanco",
"ORDER_EMPTY": "Cesta vacía",
"You don't have enough privileges to do that": "No tienes permisos para cambiar esto",
"NO SE PUEDE DESACTIVAR EL CONSIGNAT": "NO SE PUEDE DESACTIVAR EL CONSIGNAT",
"Error. El NIF/CIF está repetido": "Error. El NIF/CIF está repetido",
"Street cannot be empty": "Dirección no puede estar en blanco",
"City cannot be empty": "Cuidad no puede estar en blanco",
"Code cannot be blank": "Código no puede estar en blanco",
"You cannot remove this department": "No puedes eliminar este departamento",
"The extension must be unique": "La extensión debe ser unica",
"The secret can't be blank": "La contraseña no puede estar en blanco",
"We weren't able to send this SMS": "No hemos podido enviar el SMS",
"This client can't be invoiced": "Este cliente no puede ser facturado",
"This ticket can't be invoiced": "Este ticket no puede ser facturado",
"You cannot add or modify services to an invoiced ticket": "No puedes añadir o modificar servicios a un ticket facturado",
"This ticket can not be modified": "Este ticket no puede ser modificado",
"The introduced hour already exists": "Esta hora ya ha sido introducida",
"INFINITE_LOOP": "Existe una dependencia entre dos Jefes",
"The sales of the current ticket can't be modified": "Las lineas de este ticket no pueden ser modificadas",
"The sales of the receiver ticket can't be modified": "Las lineas del ticket al que envias no pueden ser modificadas",
"NO_AGENCY_AVAILABLE": "No hay una zona de reparto disponible con estos parámetros",
"ERROR_PAST_SHIPMENT": "No puedes seleccionar una fecha de envío en pasado",
"The current ticket can't be modified": "El ticket actual no puede ser modificado",
"The current claim can't be modified": "La reclamación actual no puede ser modificada",
"The sales of this ticket can't be modified": "Las lineas de este ticket no pueden ser modificadas",
"Sale(s) blocked, contact production": "Linea(s) bloqueada(s), contacte con produccion",
"Please select at least one sale": "Por favor selecciona al menos una linea",
"All sales must belong to the same ticket": "Todas las lineas deben pertenecer al mismo ticket",
"NO_ZONE_FOR_THIS_PARAMETERS": "Para este día no hay ninguna zona configurada",
"This item doesn't exists": "El artículo no existe",
"NOT_ZONE_WITH_THIS_PARAMETERS": "Para este día no hay ninguna zona configurada",
"Extension format is invalid": "El formato de la extensión es inválido",
"Invalid parameters to create a new ticket": "Parámetros inválidos para crear un nuevo ticket",
"This item is not available": "Este artículo no está disponible",
"This postcode already exists": "Este código postal ya existe",
"Concept cannot be blank": "El concepto no puede quedar en blanco",
"File doesn't exists": "El archivo no existe",
"You don't have privileges to change the zone or for these parameters there are more than one shipping options, talk to agencies": "No tienes permisos para cambiar la zona o para esos parámetros hay más de una opción de envío, hable con las agencias",
"This ticket is already on weekly tickets": "Este ticket ya está en tickets programados",
"Ticket id cannot be blank": "El id de ticket no puede quedar en blanco",
"Weekday cannot be blank": "El día de la semana no puede quedar en blanco",
"You can't delete a confirmed order": "No puedes borrar un pedido confirmado",
"Can't create stowaway for this ticket": "No se puede crear un polizon para este ticket",
"The social name has an invalid format": "El nombre fiscal tiene un formato incorrecto",
"Invalid quantity": "Cantidad invalida",
"This postal code is not valid": "This postal code is not valid",
"is invalid": "is invalid",
"The postcode doesn't exist. Please enter a correct one": "El código postal no existe. Por favor, introduce uno correcto",
"The department name can't be repeated": "El nombre del departamento no puede repetirse",
"This phone already exists": "Este teléfono ya existe",
"You cannot move a parent to its own sons": "No puedes mover un elemento padre a uno de sus hijos",
"You can't create a claim for a removed ticket": "No puedes crear una reclamación para un ticket eliminado",
"You cannot delete a ticket that part of it is being prepared": "No puedes eliminar un ticket en el que una parte que está siendo preparada",
"You must delete all the buy requests first": "Debes eliminar todas las peticiones de compra primero",
"You should specify a date": "Debes especificar una fecha",
"You should specify at least a start or end date": "Debes especificar al menos una fecha de inicio o de fín",
"Start date should be lower than end date": "La fecha de inicio debe ser menor que la fecha de fín",
"You should mark at least one week day": "Debes marcar al menos un día de la semana",
"Swift / BIC can't be empty": "Swift / BIC no puede estar vacío",
"Customs agent is required for a non UEE member": "El agente de aduanas es requerido para los clientes extracomunitarios",
"Incoterms is required for a non UEE member": "El incoterms es requerido para los clientes extracomunitarios",
"Deleted sales from ticket": "He eliminado las siguientes lineas del ticket [{{ticketId}}]({{{ticketUrl}}}): {{{deletions}}}",
"Added sale to ticket": "He añadido la siguiente linea al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{addition}}}",
"Changed sale discount": "He cambiado el descuento de las siguientes lineas al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Created claim": "He creado la reclamación [{{claimId}}]({{{claimUrl}}}) de las siguientes lineas del ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Changed sale price": "He cambiado el precio de [{{itemId}} {{concept}}]({{{itemUrl}}}) ({{quantity}}) de {{oldPrice}}€ ➔ *{{newPrice}}€* del ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changed sale quantity": "He cambiado la cantidad de [{{itemId}} {{concept}}]({{{itemUrl}}}) de {{oldQuantity}} ➔ *{{newQuantity}}* del ticket [{{ticketId}}]({{{ticketUrl}}})",
"State": "Estado",
"regular": "normal",
"reserved": "reservado",
"Changed sale reserved state": "He cambiado el estado reservado de las siguientes lineas al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Bought units from buy request": "Se ha comprado {{quantity}} unidades de [{{itemId}} {{concept}}]({{{urlItem}}}) para el ticket id [{{ticketId}}]({{{url}}})",
"Deny buy request": "Se ha rechazado la petición de compra para el ticket id [{{ticketId}}]({{{url}}}). Motivo: {{observation}}",
"MESSAGE_INSURANCE_CHANGE": "He cambiado el crédito asegurado del cliente [{{clientName}} ({{clientId}})]({{{url}}}) a *{{credit}} €*",
"Changed client paymethod": "He cambiado la forma de pago del cliente [{{clientName}} ({{clientId}})]({{{url}}})",
"Sent units from ticket": "Envio *{{quantity}}* unidades de [{{concept}} ({{itemId}})]({{{itemUrl}}}) a *\"{{nickname}}\"* provenientes del ticket id [{{ticketId}}]({{{ticketUrl}}})",
"Claim will be picked": "Se recogerá el género de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}*",
"Claim state has changed to incomplete": "Se ha cambiado el estado de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}* a *incompleta*",
"Claim state has changed to canceled": "Se ha cambiado el estado de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}* a *anulado*",
"This ticket is not an stowaway anymore": "El ticket id [{{ticketId}}]({{{ticketUrl}}}) ha dejado de ser un polizón",
"Client checked as validated despite of duplication": "Cliente comprobado a pesar de que existe el cliente id {{clientId}}",
"ORDER_ROW_UNAVAILABLE": "No hay disponibilidad de este producto",
"Distance must be lesser than 1000": "La distancia debe ser inferior a 1000",
"This ticket is deleted": "Este ticket está eliminado",
"Unable to clone this travel": "No ha sido posible clonar este travel",
"This thermograph id already exists": "La id del termógrafo ya existe",
"Choose a date range or days forward": "Selecciona un rango de fechas o días en adelante",
"ORDER_ALREADY_CONFIRMED": "ORDER_ALREADY_CONFIRMED",
"Invalid password": "Invalid password",
"Password does not meet requirements": "Password does not meet requirements",
"Role already assigned": "Role already assigned",
"Invalid role name": "Invalid role name",
"Role name must be written in camelCase": "Role name must be written in camelCase",
"Email already exists": "Email already exists",
"User already exists": "User already exists",
"Absence change notification on the labour calendar": "Notificacion de cambio de ausencia en el calendario laboral",
"Created absence": "El empleado <strong>{{author}}</strong> ha añadido una ausencia de tipo '{{absenceType}}' a <a href='{{{workerUrl}}}'><strong>{{employee}}</strong></a> para el día {{dated}}.",
"Deleted absence": "El empleado <strong>{{author}}</strong> ha eliminado una ausencia de tipo '{{absenceType}}' a <a href='{{{workerUrl}}}'><strong>{{employee}}</strong></a> del día {{dated}}.",
"I have deleted the ticket id": "He eliminado el ticket id [{{id}}]({{{url}}})",
"I have restored the ticket id": "He restaurado el ticket id [{{id}}]({{{url}}})",
"You can only restore a ticket within the first hour after deletion": "Únicamente puedes restaurar el ticket dentro de la primera hora después de su eliminación",
"Changed this data from the ticket": "He cambiado estos datos del ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"agencyModeFk": "Agencia",
"clientFk": "Cliente",
"zoneFk": "Zona",
"warehouseFk": "Almacén",
"shipped": "F. envío",
"landed": "F. entrega",
"addressFk": "Consignatario",
"companyFk": "Empresa",
"The social name cannot be empty": "La razón social no puede quedar en blanco",
"The nif cannot be empty": "El NIF no puede quedar en blanco",
"You need to fill sage information before you check verified data": "Debes rellenar la información de sage antes de marcar datos comprobados",
"ASSIGN_ZONE_FIRST": "Asigna una zona primero",
"Amount cannot be zero": "El importe no puede ser cero",
"Company has to be official": "Empresa inválida",
"You can not select this payment method without a registered bankery account": "No se puede utilizar este método de pago si no has registrado una cuenta bancaria",
"Action not allowed on the test environment": "Esta acción no está permitida en el entorno de pruebas",
"The selected ticket is not suitable for this route": "El ticket seleccionado no es apto para esta ruta",
"Sorts whole route": "Reordena ruta entera",
"New ticket request has been created with price": "Se ha creado una nueva petición de compra '{{description}}' para el día *{{shipped}}*, con una cantidad de *{{quantity}}* y un precio de *{{price}} €*",
"New ticket request has been created": "Se ha creado una nueva petición de compra '{{description}}' para el día *{{shipped}}*, con una cantidad de *{{quantity}}*",
"Swift / BIC cannot be empty": "Swift / BIC no puede estar vacío",
"This BIC already exist.": "Este BIC ya existe.",
"That item doesn't exists": "Ese artículo no existe",
"There's a new urgent ticket:": "Hay un nuevo ticket urgente:",
"Invalid account": "Cuenta inválida",
"Compensation account is empty": "La cuenta para compensar está vacia",
"This genus already exist": "Este genus ya existe",
"This specie already exist": "Esta especie ya existe",
"Client assignment has changed": "He cambiado el comercial ~*\"<{{previousWorkerName}}>\"*~ por *\"<{{currentWorkerName}}>\"* del cliente [{{clientName}} ({{clientId}})]({{{url}}})",
"None": "Ninguno",
"The contract was not active during the selected date": "El contrato no estaba activo durante la fecha seleccionada",
"Cannot add more than one '1/2 day vacation'": "No puedes añadir más de un 'Vacaciones 1/2 dia'",
"This document already exists on this ticket": "Este documento ya existe en el ticket",
"Some of the selected tickets are not billable": "Algunos de los tickets seleccionados no son facturables",
"You can't invoice tickets from multiple clients": "No puedes facturar tickets de multiples clientes",
"nickname": "nickname",
"INACTIVE_PROVIDER": "Proveedor inactivo",
"This client is not invoiceable": "Este cliente no es facturable",
"serial non editable": "Esta serie no permite asignar la referencia",
"Max shipped required": "La fecha límite es requerida",
"Can't invoice to future": "No se puede facturar a futuro",
"Can't invoice to past": "No se puede facturar a pasado",
"This ticket is already invoiced": "Este ticket ya está facturado",
"A ticket with an amount of zero can't be invoiced": "No se puede facturar un ticket con importe cero",
"A ticket with a negative base can't be invoiced": "No se puede facturar un ticket con una base negativa",
"Global invoicing failed": "[Facturación global] No se han podido facturar algunos clientes",
"Wasn't able to invoice the following clients": "No se han podido facturar los siguientes clientes",
"Can't verify data unless the client has a business type": "No se puede verificar datos de un cliente que no tiene tipo de negocio",
"You don't have enough privileges to set this credit amount": "No tienes suficientes privilegios para establecer esta cantidad de crédito",
"You can't change the credit set to zero from a manager": "No puedes cambiar el cŕedito establecido a cero por un gerente",
"Amounts do not match": "Las cantidades no coinciden",
"The PDF document does not exists": "El documento PDF no existe. Prueba a regenerarlo desde la opción 'Regenerar PDF factura'",
"The type of business must be filled in basic data": "El tipo de negocio debe estar rellenado en datos básicos",
"You can't create a claim from a ticket delivered more than seven days ago": "No puedes crear una reclamación de un ticket entregado hace más de siete días",
"The worker has hours recorded that day": "El trabajador tiene horas fichadas ese día",
"The worker has a marked absence that day": "El trabajador tiene marcada una ausencia ese día",
"You can not modify is pay method checked": "No se puede modificar el campo método de pago validado",
"Can't transfer claimed sales": "No puedes transferir lineas reclamadas",
"You don't have privileges to create refund": "No tienes permisos para crear un abono",
"The item is required": "El artículo es requerido",
"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",
"You must select a location": "Debes seleccionar una localización"
"The grade must be an integer greater than or equal to zero": "The grade must be an integer greater than or equal to zero",
"Description should have maximum of 45 characters": "Description should have maximum of 45 characters",
"Amount cannot be zero": "Amount cannot be zero",
"Period cannot be blank": "Period cannot be blank",
"Sample type cannot be blank": "Sample type cannot be blank",
"Cannot be blank": "Cannot be blank",
"The social name cannot be empty": "The social name cannot be empty",
"The nif cannot be empty": "The nif cannot be empty",
"Concept cannot be blank": "Concept cannot be blank",
"Enter an integer different to zero": "Enter an integer different to zero",
"Package cannot be blank": "Package cannot be blank",
"State cannot be blank": "State cannot be blank",
"Worker cannot be blank": "Worker cannot be blank",
"Agency cannot be blank": "Agency cannot be blank"
}

View File

@ -31,7 +31,8 @@
"loopback#token": {}
},
"auth:after": {
"./middleware/current-user": {}
"./middleware/current-user": {},
"./middleware/salix-version": {}
},
"parse": {
"body-parser#json":{}

View File

@ -0,0 +1,8 @@
const packageJson = require('../../../package.json');
module.exports = function(options) {
return function(req, res, next) {
res.set('Salix-Version', packageJson.version);
next();
};
};

View File

@ -1,6 +1,6 @@
module.exports = Self => {
Self.remoteMethodCtx('importToNewRefundTicket', {
description: 'Imports lines from claimBeginning to a new ticket with specific shipped, landed dates, agency and company',
description: 'Import lines of claimBeginning to new ticket with shipped, landed dates, agency and company',
accessType: 'WRITE',
accepts: [{
arg: 'id',

View File

@ -84,6 +84,20 @@ module.exports = Self => {
};
promises.push(Self.app.models.ClaimBeginning.find(filter, myOptions));
// Claim observations
filter = {
where: {claimFk: id},
include: [
{
relation: 'worker',
scope: {
fields: ['id', 'firstName', 'lastName']
}
}
]
};
promises.push(Self.app.models.ClaimObservation.find(filter, myOptions));
// Claim developments
filter = {
where: {claimFk: id},
@ -138,8 +152,9 @@ module.exports = Self => {
summary.isEditable = await Self.isEditable(ctx, id, myOptions);
[summary.claim] = res[0];
summary.salesClaimed = res[1];
summary.developments = res[2];
summary.actions = res[3];
summary.observations = res[2];
summary.developments = res[3];
summary.actions = res[4];
return summary;
};

View File

@ -19,6 +19,7 @@ describe('claim getSummary()', () => {
expect(keys).toContain('claim');
expect(keys).toContain('salesClaimed');
expect(keys).toContain('developments');
expect(keys).toContain('observations');
expect(keys).toContain('actions');
expect(keys).toContain('isEditable');

View File

@ -38,6 +38,9 @@
"ClaimLog": {
"dataSource": "vn"
},
"ClaimObservation": {
"dataSource": "vn"
},
"ClaimContainer": {
"dataSource": "claimStorage"
}

View File

@ -0,0 +1,43 @@
{
"name": "ClaimObservation",
"base": "Loggable",
"log": {
"model": "ClaimLog",
"relation": "claim"
},
"options": {
"mysql": {
"table": "claimObservation"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"description": "Identifier"
},
"claimFk": {
"type": "number",
"required": true
},
"text": {
"type": "string",
"required": true
},
"created": {
"type": "date"
}
},
"relations": {
"worker": {
"type": "belongsTo",
"model": "Worker",
"foreignKey": "workerFk"
},
"claim": {
"type": "belongsTo",
"model": "Claim",
"foreignKey": "claimFk"
}
}
}

View File

@ -51,15 +51,6 @@
label="Packages received"
ng-model="$ctrl.claim.packages">
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-textarea
label="Observation"
ng-model="$ctrl.claim.observation"
rule>
</vn-textarea>
</vn-horizontal>
<vn-horizontal>
<vn-check
class="vn-mr-md"
label="Pick up"

View File

@ -12,3 +12,5 @@ import './search-panel';
import './summary';
import './photos';
import './log';
import './note/index';
import './note/create';

View File

@ -6,6 +6,7 @@ Client Id: Id cliente
Claimed ticket: Ticket reclamado
Delete claim: Eliminar reclamación
Observation: Observación
Observations: Observaciones
Responsible: Responsable
Remove sale: Eliminar linea
Claim Id: Id reclamación

View File

@ -0,0 +1,30 @@
<vn-watcher
vn-id="watcher"
url="claimObservations"
id-field="id"
data="$ctrl.note"
insert-mode="true"
form="form">
</vn-watcher>
<form name="form" ng-submit="watcher.submitGo('claim.card.note.index')" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textarea
vn-one
label="Note"
ng-model="$ctrl.note.text"
vn-focus>
</vn-textarea>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit
ng-if="watcher.dataChanged()"
label="Save">
</vn-submit>
<vn-button
ng-click="$ctrl.cancel()"
label="Cancel">
</vn-button>
</vn-button-bar>
</form>

View File

@ -0,0 +1,22 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.note = {
claimFk: parseInt(this.$params.id),
workerFk: window.localStorage.currentUserWorkerId,
text: null
};
}
cancel() {
this.$state.go('claim.card.note.index', {id: this.$params.id});
}
}
ngModule.vnComponent('vnClaimNoteCreate', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,32 @@
<vn-crud-model
vn-id="model"
url="ClaimObservations"
filter="$ctrl.filter"
where="{claimFk: $ctrl.$params.id}"
include="$ctrl.include"
data="notes"
auto-load="true">
</vn-crud-model>
<vn-data-viewer
model="model"
class="vn-w-md">
<vn-card class="vn-pa-md">
<div
ng-repeat="note in notes"
class="note vn-pa-sm border-solid border-radius vn-mb-md">
<vn-horizontal class="vn-mb-sm" style="color: #666">
<vn-one>{{::note.worker.firstName}} {{::note.worker.lastName}}</vn-one>
<vn-auto>{{::note.created | date:'dd/MM/yyyy HH:mm'}}</vn-auto>
</vn-horizontal>
<vn-horizontal class="text">
{{::note.text}}
</vn-horizontal>
</div>
</vn-card>
</vn-data-viewer>
<a vn-tooltip="New note"
ui-sref="claim.card.note.create({id: $ctrl.$params.id})"
vn-bind="+"
fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>
</a>

View File

@ -0,0 +1,25 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
import './style.scss';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.filter = {
order: 'created DESC',
};
this.include = {
relation: 'worker',
scope: {
fields: ['id', 'firstName', 'lastName']
}
};
}
}
Controller.$inject = ['$element', '$scope'];
ngModule.vnComponent('vnClaimNote', {
template: require('./index.html'),
controller: Controller,
});

View File

@ -0,0 +1,5 @@
vn-client-note {
.note:last-child {
margin-bottom: 0;
}
}

View File

@ -12,6 +12,7 @@
{"state": "claim.card.basicData", "icon": "settings"},
{"state": "claim.card.detail", "icon": "icon-details"},
{"state": "claim.card.photos", "icon": "image"},
{"state": "claim.card.note.index", "icon": "insert_drive_file"},
{"state": "claim.card.development", "icon": "icon-traceability"},
{"state": "claim.card.action", "icon": "icon-actions"},
{"state": "claim.card.log", "icon": "history"}
@ -27,17 +28,20 @@
"abstract": true,
"component": "vn-claim",
"description": "Claims"
}, {
},
{
"url": "/index?q",
"state": "claim.index",
"component": "vn-claim-index",
"description": "Claims"
}, {
},
{
"url": "/:id",
"state": "claim.card",
"abstract": true,
"component": "vn-claim-card"
}, {
},
{
"url": "/summary",
"state": "claim.card.summary",
"component": "vn-claim-summary",
@ -45,7 +49,8 @@
"params": {
"claim": "$ctrl.claim"
}
}, {
},
{
"url": "/basic-data",
"state": "claim.card.basicData",
"component": "vn-claim-basic-data",
@ -54,7 +59,8 @@
"claim": "$ctrl.claim"
},
"acl": ["salesPerson"]
}, {
},
{
"url": "/detail",
"state": "claim.card.detail",
"component": "vn-claim-detail",
@ -63,7 +69,32 @@
"claim": "$ctrl.claim"
},
"acl": ["salesPerson"]
}, {
},
{
"url": "/note",
"state": "claim.card.note",
"component": "ui-view",
"abstract": true,
"acl": ["salesPerson"]
},
{
"url": "/index",
"state": "claim.card.note.index",
"component": "vn-claim-note",
"description": "Notes",
"params": {
"claim": "$ctrl.claim"
},
"acl": ["salesPerson"]
},
{
"url": "/create",
"state": "claim.card.note.create",
"component": "vn-claim-note-create",
"description": "New note",
"acl": ["salesPerson"]
},
{
"url": "/development",
"state": "claim.card.development",
"component": "vn-claim-development",
@ -72,7 +103,8 @@
"claim": "$ctrl.claim"
},
"acl": ["claimManager"]
}, {
},
{
"url": "/action",
"state": "claim.card.action",
"component": "vn-claim-action",
@ -81,7 +113,8 @@
"claim": "$ctrl.claim"
},
"acl": ["claimManager"]
}, {
},
{
"url": "/photos",
"state": "claim.card.photos",
"component": "vn-claim-photos",
@ -89,7 +122,8 @@
"params": {
"claim": "$ctrl.claim"
}
}, {
},
{
"url" : "/log",
"state": "claim.card.log",
"component": "vn-claim-log",

View File

@ -1,10 +1,12 @@
<vn-crud-model vn-id="model"
<vn-crud-model
vn-id="model"
url="ClaimDms"
filter="::$ctrl.filter"
data="photos">
</vn-crud-model>
<vn-card class="summary">
<h5>
<a
<a
ng-if="::$ctrl.summary.claim.id"
vn-tooltip="Go to the claim"
ui-sref="claim.card.summary({id: {{::$ctrl.summary.claim.id}}})"
@ -12,7 +14,7 @@
<vn-icon-button icon="launch"></vn-icon-button>
</a>
<span>{{::$ctrl.summary.claim.id}} - {{::$ctrl.summary.claim.client.name}}</span>
<vn-button-menu
<vn-button-menu
disabled="!$ctrl.summary.isEditable"
class="message"
label="Change state"
@ -25,30 +27,47 @@
<vn-horizontal>
<vn-one>
<vn-label-value
label="Created"
label="Created"
value="{{$ctrl.summary.claim.created | date: 'dd/MM/yyyy'}}">
</vn-label-value>
<vn-label-value
label="State"
label="State"
value="{{$ctrl.summary.claim.claimState.description}}">
</vn-label-value>
<vn-label-value
label="Salesperson"
label="Salesperson"
value="{{$ctrl.summary.claim.client.salesPersonUser.name}}">
</vn-label-value>
<vn-label-value
label="Attended by"
label="Attended by"
value="{{$ctrl.summary.claim.worker.user.nickname}}">
</vn-label-value>
</vn-one>
<vn-two>
<vn-textarea
vn-three
disabled="true"
label="Observation"
ng-model="$ctrl.summary.claim.observation">
</vn-textarea>
</vn-two>
<vn-auto>
<h4 ng-show="$ctrl.isSalesPerson && $ctrl.summary.observations.length">
<a
ui-sref="claim.card.note.index({id:$ctrl.claim.id})"
target="_self">
<span translate vn-tooltip="Go to">Observations</span>
</a>
</h4>
<h4
ng-show="!$ctrl.isSalesPerson && $ctrl.summary.observations.length"
translate>
Observations
</h4>
<div
ng-repeat="note in $ctrl.summary.observations"
class="note vn-pa-sm border-solid border-radius vn-mb-md">
<vn-horizontal class="vn-mb-sm" style="color: #666">
<vn-one>{{::note.worker.firstName}} {{::note.worker.lastName}}</vn-one>
<vn-auto>{{::note.created | date:'dd/MM/yyyy HH:mm'}}</vn-auto>
</vn-horizontal>
<vn-horizontal class="text">
{{::note.text}}
</vn-horizontal>
</div>
</vn-auto>
<vn-auto>
<h4 ng-show="$ctrl.isSalesPerson">
<a
@ -106,8 +125,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>

View File

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

View File

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

View File

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

View File

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

View File

@ -62,7 +62,7 @@ module.exports = function(Self) {
{
relation: 'account',
scope: {
fields: ['id', 'name', 'active']
fields: ['id', 'name', 'email', 'active']
}
},
{
@ -74,8 +74,10 @@ module.exports = function(Self) {
]
}, myOptions);
const query = `SELECT vn.clientGetDebt(?, CURDATE()) AS debt`;
const data = await Self.rawSql(query, [id], myOptions);
const date = new Date();
date.setHours(0, 0, 0, 0);
const query = `SELECT vn.clientGetDebt(?, ?) AS debt`;
const data = await Self.rawSql(query, [id, date], myOptions);
client.debt = data[0].debt;

View File

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

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