Merge branch 'dev' into 4866-detalle-faltas
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Alexandre Riera 2023-01-17 07:31:45 +00:00
commit fea76f29e7
82 changed files with 1863 additions and 719 deletions

View File

@ -9,9 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- [General](Inicio) Permite recuperar la contraseña - [General](Inicio) Permite recuperar la contraseña
- [Ticket](Opciones) Subir albarán a Docuware
- [Ticket](Opciones) Enviar correo con PDF de Docuware
- [Artículo](Datos Básicos) Añadido campo Unidades/Caja - [Artículo](Datos Básicos) Añadido campo Unidades/Caja
### Changed ### Changed
- [Tickets](Líneas preparadas) Actualizada sección para que sea más visual
### Fixed ### Fixed
- [General] Al utilizar el traductor de Google se descuadraban los iconos - [General] Al utilizar el traductor de Google se descuadraban los iconos

View File

@ -1,4 +1,4 @@
const got = require('got'); const axios = require('axios');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('checkFile', { Self.remoteMethodCtx('checkFile', {
@ -8,7 +8,7 @@ module.exports = Self => {
{ {
arg: 'id', arg: 'id',
type: 'number', type: 'number',
description: 'The id', description: 'The id',
http: {source: 'path'} http: {source: 'path'}
}, },
{ {
@ -18,14 +18,14 @@ module.exports = Self => {
description: 'The fileCabinet name' description: 'The fileCabinet name'
}, },
{ {
arg: 'dialog', arg: 'signed',
type: 'string', type: 'boolean',
required: true, required: true,
description: 'The dialog name' description: 'If pdf is necessary to be signed'
} }
], ],
returns: { returns: {
type: 'boolean', type: 'object',
root: true root: true
}, },
http: { http: {
@ -34,58 +34,51 @@ module.exports = Self => {
} }
}); });
Self.checkFile = async function(ctx, id, fileCabinet, dialog) { Self.checkFile = async function(ctx, id, fileCabinet, signed) {
const myUserId = ctx.req.accessToken.userId;
if (!myUserId)
return false;
const models = Self.app.models; const models = Self.app.models;
const docuwareConfig = await models.DocuwareConfig.findOne(); const action = 'find';
const docuwareInfo = await models.Docuware.findOne({ const docuwareInfo = await models.Docuware.findOne({
where: { where: {
code: fileCabinet, code: fileCabinet,
dialogName: dialog action: action
} }
}); });
const docuwareUrl = docuwareConfig.url;
const cookie = docuwareConfig.token;
const fileCabinetName = docuwareInfo.fileCabinetName;
const find = docuwareInfo.find;
const options = {
'headers': {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Cookie': cookie
}
};
const searchFilter = { const searchFilter = {
condition: [ condition: [
{ {
DBName: find, DBName: docuwareInfo.findById,
Value: [id] Value: [id]
} }
],
sortOrder: [
{
Field: 'FILENAME',
Direction: 'Desc'
}
] ]
}; };
try { try {
// get fileCabinetId const options = await Self.getOptions();
const fileCabinetResponse = await got.get(`${docuwareUrl}/FileCabinets`, options);
const fileCabinetJson = JSON.parse(fileCabinetResponse.body).FileCabinet;
const fileCabinetId = fileCabinetJson.find(dialogs => dialogs.Name === fileCabinetName).Id;
// get dialog const fileCabinetId = await Self.getFileCabinet(fileCabinet);
const dialogResponse = await got.get(`${docuwareUrl}/FileCabinets/${fileCabinetId}/dialogs`, options); const dialogId = await Self.getDialog(fileCabinet, action, fileCabinetId);
const dialogJson = JSON.parse(dialogResponse.body).Dialog;
const dialogId = dialogJson.find(dialogs => dialogs.DisplayName === 'find').Id;
// get docuwareID const response = await axios.post(
Object.assign(options, {'body': JSON.stringify(searchFilter)}); `${options.url}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`,
const response = await got.post( searchFilter,
`${docuwareUrl}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`, options); options.headers
JSON.parse(response.body).Items[0].Id; );
const [documents] = response.data.Items;
if (!documents) return false;
return true; const state = documents.Fields.find(field => field.FieldName == 'ESTADO');
if (signed && state.Item != 'Firmado') return false;
return {id: documents.Id};
} catch (error) { } catch (error) {
return false; return false;
} }

View File

@ -0,0 +1,78 @@
const axios = require('axios');
module.exports = Self => {
/**
* Returns the dialog id
*
* @param {string} code - The fileCabinet name
* @param {string} action - The fileCabinet name
* @param {string} fileCabinetId - Optional The fileCabinet name
* @return {number} - The fileCabinet id
*/
Self.getDialog = async(code, action, fileCabinetId) => {
const docuwareInfo = await Self.app.models.Docuware.findOne({
where: {
code: code,
action: action
}
});
if (!fileCabinetId) fileCabinetId = await Self.getFileCabinet(code);
const options = await Self.getOptions();
if (!process.env.NODE_ENV)
return Math.round();
const response = await axios.get(`${options.url}/FileCabinets/${fileCabinetId}/dialogs`, options.headers);
const dialogs = response.data.Dialog;
const dialogId = dialogs.find(dialogs => dialogs.DisplayName === docuwareInfo.dialogName).Id;
return dialogId;
};
/**
* Returns the fileCabinetId
*
* @param {string} code - The fileCabinet code
* @return {number} - The fileCabinet id
*/
Self.getFileCabinet = async code => {
const options = await Self.getOptions();
const docuwareInfo = await Self.app.models.Docuware.findOne({
where: {
code: code
}
});
if (!process.env.NODE_ENV)
return Math.round();
const fileCabinetResponse = await axios.get(`${options.url}/FileCabinets`, options.headers);
const fileCabinets = fileCabinetResponse.data.FileCabinet;
const fileCabinetId = fileCabinets.find(fileCabinet => fileCabinet.Name === docuwareInfo.fileCabinetName).Id;
return fileCabinetId;
};
/**
* Returns basic headers
*
* @param {string} cookie - The docuware cookie
* @return {object} - The headers
*/
Self.getOptions = async() => {
const docuwareConfig = await Self.app.models.DocuwareConfig.findOne();
const headers = {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Cookie': docuwareConfig.cookie
}
};
return {
url: docuwareConfig.url,
headers
};
};
};

View File

@ -0,0 +1,72 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('deliveryNoteEmail', {
description: 'Sends the delivery note email with an docuware attached PDF',
accessType: 'WRITE',
accepts: [
{
arg: 'id',
type: 'string',
required: true,
description: 'The ticket id',
http: {source: 'path'}
},
{
arg: 'recipient',
type: 'string',
description: 'The recipient email',
required: true,
},
{
arg: 'recipientId',
type: 'number',
description: 'The client id',
required: false
}
],
returns: [
{
arg: 'body',
type: 'file',
root: true
}, {
arg: 'Content-Type',
type: 'String',
http: {target: 'header'}
}, {
arg: 'Content-Disposition',
type: 'String',
http: {target: 'header'}
}
],
http: {
path: '/:id/delivery-note-email',
verb: 'POST'
}
});
Self.deliveryNoteEmail = async(ctx, id) => {
const args = Object.assign({}, ctx.args);
const params = {
recipient: args.recipient,
lang: ctx.req.getLocale()
};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const email = new Email('delivery-note', params);
const docuwareFile = await Self.app.models.Docuware.download(ctx, id, 'deliveryNote');
return email.send({
overrideAttachments: true,
attachments: [{
filename: `${id}.pdf`,
content: docuwareFile[0]
}]
});
};
};

View File

@ -1,5 +1,5 @@
/* eslint max-len: ["error", { "code": 180 }]*/ /* eslint max-len: ["error", { "code": 180 }]*/
const got = require('got'); const axios = require('axios');
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
@ -10,19 +10,13 @@ module.exports = Self => {
{ {
arg: 'id', arg: 'id',
type: 'number', type: 'number',
description: 'The id', description: 'The ticket id',
http: {source: 'path'} http: {source: 'path'}
}, },
{ {
arg: 'fileCabinet', arg: 'fileCabinet',
type: 'string', type: 'string',
description: 'The id', description: 'The file cabinet',
http: {source: 'path'}
},
{
arg: 'dialog',
type: 'string',
description: 'The id',
http: {source: 'path'} http: {source: 'path'}
} }
], ],
@ -42,79 +36,26 @@ module.exports = Self => {
} }
], ],
http: { http: {
path: `/:id/download/:fileCabinet/:dialog`, path: `/:id/download/:fileCabinet`,
verb: 'GET' verb: 'GET'
} }
}); });
Self.download = async function(ctx, id, fileCabinet, dialog) { Self.download = async function(ctx, id, fileCabinet) {
const myUserId = ctx.req.accessToken.userId;
if (!myUserId)
throw new UserError(`You don't have enough privileges`);
const models = Self.app.models; const models = Self.app.models;
const docuwareConfig = await models.DocuwareConfig.findOne(); const docuwareFile = await models.Docuware.checkFile(ctx, id, fileCabinet, true);
const docuwareInfo = await models.Docuware.findOne({ if (!docuwareFile) throw new UserError('The DOCUWARE PDF document does not exists');
where: {
code: fileCabinet,
dialogName: dialog
}
});
const docuwareUrl = docuwareConfig.url; const fileCabinetId = await Self.getFileCabinet(fileCabinet);
const cookie = docuwareConfig.token; const options = await Self.getOptions();
const fileCabinetName = docuwareInfo.fileCabinetName; options.headers.responseType = 'stream';
const find = docuwareInfo.find;
const options = {
'headers': {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Cookie': cookie
}
};
const searchFilter = {
condition: [
{
DBName: find,
Value: [id]
}
]
};
try { const fileName = `filename="${id}.pdf"`;
// get fileCabinetId const contentType = 'application/pdf';
const fileCabinetResponse = await got.get(`${docuwareUrl}/FileCabinets`, options); const downloadUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents/${docuwareFile.id}/FileDownload?targetFileType=Auto&keepAnnotations=false`;
const fileCabinetJson = JSON.parse(fileCabinetResponse.body).FileCabinet;
const fileCabinetId = fileCabinetJson.find(dialogs => dialogs.Name === fileCabinetName).Id;
// get dialog const stream = await axios.get(downloadUri, options.headers);
const dialogResponse = await got.get(`${docuwareUrl}/FileCabinets/${fileCabinetId}/dialogs`, options);
const dialogJson = JSON.parse(dialogResponse.body).Dialog;
const dialogId = dialogJson.find(dialogs => dialogs.DisplayName === 'find').Id;
// get docuwareID return [stream.data, contentType, fileName];
Object.assign(options, {'body': JSON.stringify(searchFilter)});
const response = await got.post(`${docuwareUrl}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`, options);
const docuwareId = JSON.parse(response.body).Items[0].Id;
// download & save file
const fileName = `filename="${id}.pdf"`;
const contentType = 'application/pdf';
const downloadUri = `${docuwareUrl}/FileCabinets/${fileCabinetId}/Documents/${docuwareId}/FileDownload?targetFileType=Auto&keepAnnotations=false`;
const downloadOptions = {
'headers': {
'Cookie': cookie
}
};
const stream = got.stream(downloadUri, downloadOptions);
return [stream, contentType, fileName];
} catch (error) {
if (error.code === 'ENOENT')
throw new UserError('The DOCUWARE PDF document does not exists');
throw error;
}
}; };
}; };

View File

@ -1,5 +1,5 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const got = require('got'); const axios = require('axios');
describe('docuware download()', () => { describe('docuware download()', () => {
const ticketId = 1; const ticketId = 1;
@ -12,53 +12,71 @@ describe('docuware download()', () => {
} }
}; };
const fileCabinetName = 'deliveryClient'; const docuwareModel = models.Docuware;
const dialogDisplayName = 'find'; const fileCabinetName = 'deliveryNote';
const dialogName = 'findTicket';
const gotGetResponse = { beforeAll(() => {
body: JSON.stringify( spyOn(docuwareModel, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
{ spyOn(docuwareModel, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
FileCabinet: [
{Id: 12, Name: fileCabinetName}
],
Dialog: [
{Id: 34, DisplayName: dialogDisplayName}
]
})
};
it('should return exist file in docuware', async() => {
const gotPostResponse = {
body: JSON.stringify(
{
Items: [
{Id: 56}
],
})
};
spyOn(got, 'get').and.returnValue(new Promise(resolve => resolve(gotGetResponse)));
spyOn(got, 'post').and.returnValue(new Promise(resolve => resolve(gotPostResponse)));
const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, dialogName);
expect(result).toEqual(true);
}); });
it('should return not exist file in docuware', async() => { it('should return false if there are no documents', async() => {
const gotPostResponse = { const response = {
body: JSON.stringify( data: {
{ Items: []
Items: [], }
})
}; };
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response)));
spyOn(got, 'get').and.returnValue(new Promise(resolve => resolve(gotGetResponse))); const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true);
spyOn(got, 'post').and.returnValue(new Promise(resolve => resolve(gotPostResponse)));
const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, dialogName);
expect(result).toEqual(false); expect(result).toEqual(false);
}); });
it('should return false if the document is unsigned', async() => {
const response = {
data: {
Items: [
{
Id: 1,
Fields: [
{
FieldName: 'ESTADO',
Item: 'Unsigned'
}
]
}
]
}
};
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response)));
const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true);
expect(result).toEqual(false);
});
it('should return the document data', async() => {
const docuwareId = 1;
const response = {
data: {
Items: [
{
Id: docuwareId,
Fields: [
{
FieldName: 'ESTADO',
Item: 'Firmado'
}
]
}
]
}
};
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response)));
const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true);
expect(result.id).toEqual(docuwareId);
});
}); });

View File

@ -1,5 +1,5 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const got = require('got'); const axios = require('axios');
const stream = require('stream'); const stream = require('stream');
describe('docuware download()', () => { describe('docuware download()', () => {
@ -13,36 +13,33 @@ describe('docuware download()', () => {
} }
}; };
it('should return the downloaded file name', async() => { const docuwareModel = models.Docuware;
const fileCabinetName = 'deliveryClient'; const fileCabinetName = 'deliveryNote';
const dialogDisplayName = 'find';
const dialogName = 'findTicket';
const gotGetResponse = {
body: JSON.stringify(
{
FileCabinet: [
{Id: 12, Name: fileCabinetName}
],
Dialog: [
{Id: 34, DisplayName: dialogDisplayName}
]
})
};
const gotPostResponse = { beforeAll(() => {
body: JSON.stringify( spyOn(docuwareModel, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
{ spyOn(docuwareModel, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
Items: [ });
{Id: 56}
],
})
};
spyOn(got, 'get').and.returnValue(new Promise(resolve => resolve(gotGetResponse))); it('should return error if file not exist', async() => {
spyOn(got, 'post').and.returnValue(new Promise(resolve => resolve(gotPostResponse))); spyOn(docuwareModel, 'checkFile').and.returnValue(false);
spyOn(got, 'stream').and.returnValue(new stream.PassThrough({objectMode: true})); spyOn(axios, 'get').and.returnValue(new stream.PassThrough({objectMode: true}));
const result = await models.Docuware.download(ctx, ticketId, fileCabinetName, dialogName); let error;
try {
await models.Docuware.download(ctx, ticketId, fileCabinetName);
} catch (e) {
error = e.message;
}
expect(error).toEqual('The DOCUWARE PDF document does not exists');
});
it('should return the downloaded file if exist file ', async() => {
spyOn(docuwareModel, 'checkFile').and.returnValue({});
spyOn(axios, 'get').and.returnValue(new stream.PassThrough({objectMode: true}));
const result = await models.Docuware.download(ctx, ticketId, fileCabinetName);
expect(result[1]).toEqual('application/pdf'); expect(result[1]).toEqual('application/pdf');
expect(result[2]).toEqual(`filename="${ticketId}.pdf"`); expect(result[2]).toEqual(`filename="${ticketId}.pdf"`);

View File

@ -0,0 +1,37 @@
const models = require('vn-loopback/server/server').models;
describe('docuware upload()', () => {
const userId = 9;
const ticketId = 10;
const ctx = {
req: {
getLocale: () => {
return 'en';
},
accessToken: {userId: userId},
headers: {origin: 'http://localhost:5000'},
}
};
const docuwareModel = models.Docuware;
const ticketModel = models.Ticket;
const fileCabinetName = 'deliveryNote';
beforeAll(() => {
spyOn(docuwareModel, 'getFileCabinet').and.returnValue(new Promise(resolve => resolve(Math.random())));
spyOn(docuwareModel, 'getDialog').and.returnValue(new Promise(resolve => resolve(Math.random())));
});
it('should try upload file', async() => {
spyOn(ticketModel, 'deliveryNotePdf').and.returnValue(new Promise(resolve => resolve({})));
let error;
try {
await models.Docuware.upload(ctx, ticketId, fileCabinetName);
} catch (e) {
error = e.message;
}
expect(error).toEqual('Action not allowed on the test environment');
});
});

View File

@ -0,0 +1,141 @@
const UserError = require('vn-loopback/util/user-error');
const axios = require('axios');
module.exports = Self => {
Self.remoteMethodCtx('upload', {
description: 'Upload an docuware PDF',
accessType: 'WRITE',
accepts: [
{
arg: 'id',
type: 'number',
description: 'The ticket id',
http: {source: 'path'}
},
{
arg: 'fileCabinet',
type: 'string',
description: 'The file cabinet'
},
{
arg: 'dialog',
type: 'string',
description: 'The dialog'
}
],
returns: [],
http: {
path: `/:id/upload`,
verb: 'POST'
}
});
Self.upload = async function(ctx, id, fileCabinet) {
const models = Self.app.models;
const action = 'store';
const options = await Self.getOptions();
const fileCabinetId = await Self.getFileCabinet(fileCabinet);
const dialogId = await Self.getDialog(fileCabinet, action, fileCabinetId);
// get delivery note
const deliveryNote = await models.Ticket.deliveryNotePdf(ctx, {
id,
type: 'deliveryNote'
});
// get ticket data
const ticket = await models.Ticket.findById(id, {
include: [{
relation: 'client',
scope: {
fields: ['id', 'socialName', 'fi']
}
}]
});
// upload file
const templateJson = {
'Fields': [
{
'FieldName': 'N__ALBAR_N',
'ItemElementName': 'string',
'Item': id,
},
{
'FieldName': 'CIF_PROVEEDOR',
'ItemElementName': 'string',
'Item': ticket.client().fi,
},
{
'FieldName': 'CODIGO_PROVEEDOR',
'ItemElementName': 'string',
'Item': ticket.client().id,
},
{
'FieldName': 'NOMBRE_PROVEEDOR',
'ItemElementName': 'string',
'Item': ticket.client().socialName,
},
{
'FieldName': 'FECHA_FACTURA',
'ItemElementName': 'date',
'Item': ticket.shipped,
},
{
'FieldName': 'TOTAL_FACTURA',
'ItemElementName': 'Decimal',
'Item': ticket.totalWithVat,
},
{
'FieldName': 'ESTADO',
'ItemElementName': 'string',
'Item': 'Pendiente procesar',
},
{
'FieldName': 'FIRMA_',
'ItemElementName': 'string',
'Item': 'Si',
},
{
'FieldName': 'FILTRO_TABLET',
'ItemElementName': 'string',
'Item': 'Tablet1',
}
]
};
if (process.env.NODE_ENV != 'production')
throw new UserError('Action not allowed on the test environment');
// delete old
const docuwareFile = await models.Docuware.checkFile(ctx, id, fileCabinet, false);
if (docuwareFile) {
const deleteJson = {
'Field': [{'FieldName': 'ESTADO', 'Item': 'Pendiente eliminar', 'ItemElementName': 'String'}]
};
const deleteUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents/${docuwareFile.id}/Fields`;
await axios.put(deleteUri, deleteJson, options.headers);
}
const uploadUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents?StoreDialogId=${dialogId}`;
const FormData = require('form-data');
const data = new FormData();
data.append('document', JSON.stringify(templateJson), 'schema.json');
data.append('file[]', deliveryNote[0], 'file.pdf');
const uploadOptions = {
headers: {
'Content-Type': 'multipart/form-data',
'X-File-ModifiedDate': new Date(),
'Cookie': options.headers.headers.Cookie,
...data.getHeaders()
},
};
return await axios.post(uploadUri, data, uploadOptions)
.catch(() => {
throw new UserError('Failed to upload file');
});
};
};

View File

@ -25,36 +25,36 @@ module.exports = Self => {
return false; return false;
const con = mysql.createConnection({ const con = mysql.createConnection({
host: `${config.hostDb}`, host: config.hostDb,
user: `${config.userDb}`, user: config.userDb,
password: `${config.passwordDb}`, password: config.passwordDb,
port: `${config.portDb}` port: config.portDb
}); });
const sql = `SELECT ot.ticket_id, ot.number const sql = `SELECT ot.ticket_id, ot.number
FROM osticket.ost_ticket ot FROM osticket.ost_ticket ot
JOIN osticket.ost_ticket_status ots ON ots.id = ot.status_id JOIN osticket.ost_ticket_status ots ON ots.id = ot.status_id
JOIN osticket.ost_thread ot2 ON ot2.object_id = ot.ticket_id AND ot2.object_type = 'T' JOIN osticket.ost_thread ot2 ON ot2.object_id = ot.ticket_id AND ot2.object_type = 'T'
JOIN ( JOIN (
SELECT ote.thread_id, MAX(ote.created) created, MAX(ote.updated) updated SELECT ote.thread_id, MAX(ote.created) created, MAX(ote.updated) updated
FROM osticket.ost_thread_entry ote FROM osticket.ost_thread_entry ote
WHERE ote.staff_id != 0 AND ote.type = 'R' WHERE ote.staff_id AND ote.type = 'R'
GROUP BY ote.thread_id GROUP BY ote.thread_id
) sub ON sub.thread_id = ot2.id ) sub ON sub.thread_id = ot2.id
WHERE ot.isanswered = 1 WHERE ot.isanswered
AND ots.state = '${config.oldStatus}' AND ots.state = ?
AND IF(sub.updated > sub.created, sub.updated, sub.created) < DATE_SUB(CURDATE(), INTERVAL ${config.day} DAY)`; AND IF(sub.updated > sub.created, sub.updated, sub.created) < DATE_SUB(CURDATE(), INTERVAL ? DAY)`;
let ticketsId = []; const ticketsId = [];
con.connect(err => { con.connect(err => {
if (err) throw err; if (err) throw err;
con.query(sql, (err, results) => { con.query(sql, [config.oldStatus, config.day],
if (err) throw err; (err, results) => {
for (const result of results) if (err) throw err;
ticketsId.push(result.ticket_id); for (const result of results)
}); ticketsId.push(result.ticket_id);
});
}); });
await getRequestToken(); await getRequestToken();
async function getRequestToken() { async function getRequestToken() {
@ -94,6 +94,44 @@ module.exports = Self => {
await close(token, secondCookie); await close(token, secondCookie);
} }
async function close(token, secondCookie) {
for (const ticketId of ticketsId) {
try {
const lock = await getLockCode(token, secondCookie, ticketId);
if (!lock.code) {
let error = `Can't get lock code`;
if (lock.msg) error += `: ${lock.msg}`;
throw new Error(error);
}
let form = new FormData();
form.append('__CSRFToken__', token);
form.append('id', ticketId);
form.append('a', config.responseType);
form.append('lockCode', lock.code);
form.append('from_email_id', config.fromEmailId);
form.append('reply-to', config.replyTo);
form.append('cannedResp', 0);
form.append('response', config.comment);
form.append('signature', 'none');
form.append('reply_status_id', config.newStatusId);
const ostUri = `${config.host}/tickets.php?id=${ticketId}`;
const params = {
method: 'POST',
body: form,
headers: {
'Cookie': secondCookie
}
};
await fetch(ostUri, params);
} catch (e) {
const err = new Error(`${ticketId} Ticket close failed: ${e.message}`);
err.stack += e.stack;
console.error(err);
}
}
}
async function getLockCode(token, secondCookie, ticketId) { async function getLockCode(token, secondCookie, ticketId) {
const ostUri = `${config.host}/ajax.php/lock/ticket/${ticketId}`; const ostUri = `${config.host}/ajax.php/lock/ticket/${ticketId}`;
const params = { const params = {
@ -107,34 +145,7 @@ module.exports = Self => {
const body = await response.text(); const body = await response.text();
const json = JSON.parse(body); const json = JSON.parse(body);
return json.code; return json;
}
async function close(token, secondCookie) {
for (const ticketId of ticketsId) {
const lockCode = await getLockCode(token, secondCookie, ticketId);
let form = new FormData();
form.append('__CSRFToken__', token);
form.append('id', ticketId);
form.append('a', config.responseType);
form.append('lockCode', lockCode);
form.append('from_email_id', config.fromEmailId);
form.append('reply-to', config.replyTo);
form.append('cannedResp', 0);
form.append('response', config.comment);
form.append('signature', 'none');
form.append('reply_status_id', config.newStatusId);
const ostUri = `${config.host}/tickets.php?id=${ticketId}`;
const params = {
method: 'POST',
body: form,
headers: {
'Cookie': secondCookie
}
};
return fetch(ostUri, params);
}
} }
}; };
}; };

View File

@ -16,7 +16,7 @@
"url": { "url": {
"type": "string" "type": "string"
}, },
"token": { "cookie": {
"type": "string" "type": "string"
} }
}, },
@ -29,4 +29,4 @@
"permission": "ALLOW" "permission": "ALLOW"
} }
] ]
} }

View File

@ -1,4 +1,7 @@
module.exports = Self => { module.exports = Self => {
require('../methods/docuware/download')(Self); require('../methods/docuware/download')(Self);
require('../methods/docuware/upload')(Self);
require('../methods/docuware/checkFile')(Self); require('../methods/docuware/checkFile')(Self);
require('../methods/docuware/deliveryNoteEmail')(Self);
require('../methods/docuware/core')(Self);
}; };

View File

@ -19,20 +19,14 @@
"fileCabinetName": { "fileCabinetName": {
"type": "string" "type": "string"
}, },
"action": {
"type": "string"
},
"dialogName": { "dialogName": {
"type": "string" "type": "string"
}, },
"find": { "findById": {
"type": "string" "type": "string"
} }
}, }
"acls": [ }
{
"property": "*",
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
]
}

View File

@ -1 +0,0 @@
insert into `util`.`notification` (`id`, `name`,`description`) values (2, 'invoiceElectronic', 'A electronic invoice has been generated');

View File

@ -0,0 +1,225 @@
DROP PROCEDURE IF EXISTS `vn`.`invoiceOut_new`;
DELIMITER $$
CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`invoiceOut_new`(
vSerial VARCHAR(255),
vInvoiceDate DATETIME,
vTaxArea VARCHAR(25),
OUT vNewInvoiceId INT)
BEGIN
/**
* Creación de facturas emitidas.
* requiere previamente tabla ticketToInvoice(id).
*
* @param vSerial serie a la cual se hace la factura
* @param vInvoiceDate fecha de la factura
* @param vTaxArea tipo de iva en relacion a la empresa y al cliente
* @param vNewInvoiceId id de la factura que se acaba de generar
* @return vNewInvoiceId
*/
DECLARE vSpainCountryCode INT DEFAULT 1;
DECLARE vIsAnySaleToInvoice BOOL;
DECLARE vIsAnyServiceToInvoice BOOL;
DECLARE vNewRef VARCHAR(255);
DECLARE vWorker INT DEFAULT account.myUser_getId();
DECLARE vCompany INT;
DECLARE vSupplier INT;
DECLARE vClient INT;
DECLARE vCplusStandardInvoiceTypeFk INT DEFAULT 1;
DECLARE vCplusCorrectingInvoiceTypeFk INT DEFAULT 6;
DECLARE vCplusSimplifiedInvoiceTypeFk INT DEFAULT 2;
DECLARE vCorrectingSerial VARCHAR(1) DEFAULT 'R';
DECLARE vSimplifiedSerial VARCHAR(1) DEFAULT 'S';
DECLARE vNewInvoiceInId INT;
DECLARE vIsInterCompany BOOL;
SET vInvoiceDate = IFNULL(vInvoiceDate,CURDATE());
SELECT t.clientFk, t.companyFk
INTO vClient, vCompany
FROM ticketToInvoice tt
JOIN ticket t ON t.id = tt.id
LIMIT 1;
-- Eliminem de ticketToInvoice els tickets que no han de ser facturats
DELETE ti.*
FROM ticketToInvoice ti
JOIN ticket t ON t.id = ti.id
JOIN sale s ON s.ticketFk = t.id
JOIN item i ON i.id = s.itemFk
JOIN supplier su ON su.id = t.companyFk
JOIN client c ON c.id = t.clientFk
LEFT JOIN itemTaxCountry itc ON itc.itemFk = i.id AND itc.countryFk = su.countryFk
WHERE YEAR(t.shipped) < 2001
OR c.isTaxDataChecked = FALSE
OR t.isDeleted
OR c.hasToInvoice = FALSE
OR itc.id IS NULL;
SELECT SUM(s.quantity * s.price * (100 - s.discount)/100), ts.id
INTO vIsAnySaleToInvoice, vIsAnyServiceToInvoice
FROM ticketToInvoice t
LEFT JOIN sale s ON s.ticketFk = t.id
LEFT JOIN ticketService ts ON ts.ticketFk = t.id;
IF (vIsAnySaleToInvoice OR vIsAnyServiceToInvoice)
AND (vCorrectingSerial = vSerial OR NOT hasAnyNegativeBase())
THEN
-- el trigger añade el siguiente Id_Factura correspondiente a la vSerial
INSERT INTO invoiceOut
(
ref,
serial,
issued,
clientFk,
dued,
companyFk,
cplusInvoiceType477Fk
)
SELECT
1,
vSerial,
vInvoiceDate,
vClient,
getDueDate(vInvoiceDate, dueDay),
vCompany,
IF(vSerial = vCorrectingSerial,
vCplusCorrectingInvoiceTypeFk,
IF(vSerial = vSimplifiedSerial,
vCplusSimplifiedInvoiceTypeFk,
vCplusStandardInvoiceTypeFk))
FROM client
WHERE id = vClient;
SET vNewInvoiceId = LAST_INSERT_ID();
SELECT `ref`
INTO vNewRef
FROM invoiceOut
WHERE id = vNewInvoiceId;
UPDATE ticket t
JOIN ticketToInvoice ti ON ti.id = t.id
SET t.refFk = vNewRef;
DROP TEMPORARY TABLE IF EXISTS tmp.updateInter;
CREATE TEMPORARY TABLE tmp.updateInter ENGINE = MEMORY
SELECT s.id,ti.id ticket_id,vWorker Id_Trabajador
FROM ticketToInvoice ti
LEFT JOIN ticketState ts ON ti.id = ts.ticket
JOIN state s
WHERE IFNULL(ts.alertLevel,0) < 3 and s.`code` = getAlert3State(ti.id);
INSERT INTO vncontrol.inter(state_id,Id_Ticket,Id_Trabajador)
SELECT * FROM tmp.updateInter;
INSERT INTO ticketLog (action, userFk, originFk, description)
SELECT 'UPDATE', account.myUser_getId(), ti.id, CONCAT('Crea factura ', vNewRef)
FROM ticketToInvoice ti;
CALL invoiceExpenceMake(vNewInvoiceId);
CALL invoiceTaxMake(vNewInvoiceId,vTaxArea);
UPDATE invoiceOut io
JOIN (
SELECT SUM(amount) AS total
FROM invoiceOutExpence
WHERE invoiceOutFk = vNewInvoiceId
) base
JOIN (
SELECT SUM(vat) AS total
FROM invoiceOutTax
WHERE invoiceOutFk = vNewInvoiceId
) vat
SET io.amount = base.total + vat.total
WHERE io.id = vNewInvoiceId;
DROP TEMPORARY TABLE tmp.updateInter;
SELECT ios.isCEE INTO vIsInterCompany
FROM vn.ticket t
JOIN vn.invoiceOut io ON io.`ref` = t.refFk
JOIN vn.invoiceOutSerial ios ON ios.code = io.serial
WHERE t.refFk = vNewRef
LIMIT 1;
IF (vIsInterCompany) THEN
SELECT vCompany INTO vSupplier;
SELECT id INTO vCompany FROM company WHERE clientFk = vClient;
INSERT INTO invoiceIn(supplierFk, supplierRef, issued, companyFk)
SELECT vSupplier, vNewRef, vInvoiceDate, vCompany;
SET vNewInvoiceInId = LAST_INSERT_ID();
DROP TEMPORARY TABLE IF EXISTS tmp.ticket;
CREATE TEMPORARY TABLE tmp.ticket
(KEY (ticketFk))
ENGINE = MEMORY
SELECT id ticketFk
FROM ticketToInvoice;
CALL `ticket_getTax`('NATIONAL');
SET @vTaxableBaseServices := 0.00;
SET @vTaxCodeGeneral := NULL;
INSERT INTO vn.invoiceInTax(invoiceInFk, taxableBase, expenceFk, taxTypeSageFk, transactionTypeSageFk)
SELECT vNewInvoiceInId, @vTaxableBaseServices, sub.expenceFk, sub.taxTypeSageFk , sub.transactionTypeSageFk
FROM (
SELECT @vTaxableBaseServices := SUM(tst.taxableBase) taxableBase, i.expenceFk, i.taxTypeSageFk , i.transactionTypeSageFk, @vTaxCodeGeneral := i.taxClassCodeFk
FROM tmp.ticketServiceTax tst
JOIN vn.invoiceOutTaxConfig i ON i.taxClassCodeFk = tst.code
WHERE i.isService
HAVING taxableBase
) sub;
INSERT INTO vn.invoiceInTax(invoiceInFk, taxableBase, expenceFk, taxTypeSageFk, transactionTypeSageFk)
SELECT vNewInvoiceInId, SUM(tt.taxableBase) - IF(tt.code = @vTaxCodeGeneral, @vTaxableBaseServices, 0) taxableBase, i.expenceFk, i.taxTypeSageFk , i.transactionTypeSageFk
FROM tmp.ticketTax tt
JOIN vn.invoiceOutTaxConfig i ON i.taxClassCodeFk = tt.code
WHERE !i.isService
GROUP BY tt.pgcFk
HAVING taxableBase
ORDER BY tt.priority;
CALL invoiceInDueDay_calculate(vNewInvoiceInId);
INSERT INTO invoiceInIntrastat (
invoiceInFk,
intrastatFk,
amount,
stems,
countryFk,
net)
SELECT
vNewInvoiceInId invoiceInFk,
i.intrastatFk,
CAST(SUM((s.quantity * s.price * (100 - s.discount) / 100 )) AS DECIMAL(10,2)) subtotal,
CAST(SUM(IFNULL(i.stems, 1) * s.quantity) AS DECIMAL(10,2)) stems,
su.countryFk,
CAST(SUM(IFNULL(i.stems, 1)
* s.quantity
* IF(ic.grams, ic.grams, i.weightByPiece) / 1000) AS DECIMAL(10,2)) netKg
FROM sale s
JOIN ticket t ON s.ticketFk = t.id
JOIN supplier su ON su.id = t.companyFk
JOIN item i ON i.id = s.itemFk
JOIN vn.itemCost ic ON ic.itemFk = i.id AND ic.warehouseFk = t.warehouseFk
JOIN intrastat ir ON ir.id = i.intrastatFk
WHERE t.refFk = vNewRef;
DROP TEMPORARY TABLE tmp.ticket;
DROP TEMPORARY TABLE tmp.ticketAmount;
DROP TEMPORARY TABLE tmp.ticketTax;
DROP TEMPORARY TABLE tmp.ticketServiceTax;
END IF;
END IF;
DROP TEMPORARY TABLE `ticketToInvoice`;
END$$
DELIMITER ;

View File

@ -43,7 +43,7 @@ SET t.code = 'claim'
WHERE t.code LIKE 'Claims' ESCAPE '#'; WHERE t.code LIKE 'Claims' ESCAPE '#';
UPDATE salix.module t UPDATE salix.module t
SET t.code = 'user' SET t.code = 'account'
WHERE t.code LIKE 'Users' ESCAPE '#'; WHERE t.code LIKE 'Users' ESCAPE '#';
UPDATE salix.module t UPDATE salix.module t

View File

@ -0,0 +1,28 @@
ALTER TABLE `vn`.`mdbApp` DROP PRIMARY KEY;
ALTER TABLE `vn`.`mdbApp` ADD CONSTRAINT mdbApp_PK PRIMARY KEY (app,baselineBranchFk);
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('com','master');
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('enc','master');
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('ent','master');
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('eti','master');
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('lab','master');
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('tpv','master');
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('com','dev');
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('enc','dev');
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('ent','dev');
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('eti','dev');
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('lab','dev');
INSERT INTO `vn`.`mdbApp` (app,baselineBranchFk)
VALUES ('tpv','dev');

View File

@ -0,0 +1,2 @@
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`)
VALUES ('ItemShelvingSale','*','*','ALLOW','employee');

View File

@ -0,0 +1,23 @@
CREATE OR REPLACE TABLE `vn`.`docuware` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`code` varchar(50) COLLATE utf8mb3_unicode_ci NOT NULL,
`fileCabinetName` varchar(50) COLLATE utf8mb3_unicode_ci NOT NULL,
`action` varchar(255) COLLATE utf8mb3_unicode_ci NOT NULL,
`dialogName` varchar(100) COLLATE utf8mb3_unicode_ci NOT NULL,
`findById` varchar(50) COLLATE utf8mb3_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
INSERT INTO `vn`.`docuware` (`code`, `fileCabinetName`, `action`, `dialogName`, `findById`)
VALUES
('deliveryNote', 'Albaranes cliente', 'find', 'find', 'N__ALBAR_N'),
('deliveryNote', 'Albaranes cliente', 'store', 'Archivar', 'N__ALBAR_N');
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`)
VALUES
('Docuware','checkFile','READ','ALLOW','employee'),
('Docuware','download','READ','ALLOW','salesPerson'),
('Docuware','upload','WRITE','ALLOW','productionAssi'),
('Docuware','deliveryNoteEmail','WRITE','ALLOW','salesPerson');
ALTER TABLE `vn`.`docuwareConfig` CHANGE token cookie varchar(1000) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL NULL;

View File

@ -0,0 +1,73 @@
DROP TRIGGER IF EXISTS vn.XDiario_beforeUpdate;
USE vn;
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` TRIGGER `vn`.`XDiario_beforeUpdate`
BEFORE UPDATE ON `XDiario`
FOR EACH ROW
BEGIN
IF NOT NEW.SUBCTA <=> OLD.SUBCTA THEN
IF NEW.SUBCTA <=> '' THEN
SET NEW.SUBCTA = NULL;
END IF;
IF NEW.SUBCTA IS NOT NULL AND NOT LENGTH(NEW.SUBCTA) <=> 10 THEN
CALL util.throw('INVALID_STRING_LENGTH');
END IF;
END IF;
IF NOT NEW.CONTRA <=> OLD.CONTRA THEN
IF NEW.CONTRA <=> '' THEN
SET NEW.CONTRA = NULL;
END IF;
IF NEW.CONTRA IS NOT NULL AND NOT LENGTH(NEW.CONTRA) <=> 10 THEN
CALL util.throw('INVALID_STRING_LENGTH');
END IF;
END IF;
IF NOT NEW.FECHA <=> OLD.FECHA THEN
CALL XDiario_checkDate(NEW.FECHA);
END IF;
IF NOT NEW.FECHA_EX <=> OLD.FECHA_EX THEN
CALL XDiario_checkDate(NEW.FECHA_EX);
END IF;
IF NOT NEW.FECHA_OP <=> OLD.FECHA_OP THEN
CALL XDiario_checkDate(NEW.FECHA_OP);
END IF;
IF NOT NEW.FECHA_RT <=> OLD.FECHA_RT THEN
CALL XDiario_checkDate(NEW.FECHA_RT);
END IF;
IF NOT NEW.FECREGCON <=> OLD.FECREGCON THEN
CALL XDiario_checkDate(NEW.FECREGCON);
END IF;
END$$
DELIMITER ;
DROP TRIGGER IF EXISTS vn.XDiario_beforeInsert;
USE vn;
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` TRIGGER `vn`.`XDiario_beforeInsert`
BEFORE INSERT ON `XDiario`
FOR EACH ROW
BEGIN
IF NEW.SUBCTA <=> '' THEN
SET NEW.SUBCTA = NULL;
END IF;
IF NEW.SUBCTA IS NOT NULL AND NOT LENGTH(NEW.SUBCTA) <=> 10 THEN
CALL util.throw('INVALID_STRING_LENGTH');
END IF;
IF NEW.CONTRA <=> '' THEN
SET NEW.CONTRA = NULL;
END IF;
IF NEW.CONTRA IS NOT NULL AND NOT LENGTH(NEW.CONTRA) <=> 10 THEN
CALL util.throw('INVALID_STRING_LENGTH');
END IF;
CALL XDiario_checkDate(NEW.FECHA);
CALL XDiario_checkDate(NEW.FECHA_EX);
CALL XDiario_checkDate(NEW.FECHA_OP);
CALL XDiario_checkDate(NEW.FECHA_RT);
CALL XDiario_checkDate(NEW.FECREGCON);
END$$
DELIMITER ;

View File

@ -1143,10 +1143,10 @@ INSERT INTO `vn`.`itemShelving` (`itemFk`, `shelvingFk`, `visible`, `grouping`,
INSERT INTO `vn`.`itemShelvingSale` (`itemShelvingFk`, `saleFk`, `quantity`, `created`, `userFk`) INSERT INTO `vn`.`itemShelvingSale` (`itemShelvingFk`, `saleFk`, `quantity`, `created`, `userFk`)
VALUES VALUES
('1', '1', '1', '', '1106'), ('1', '1', '1', util.VN_CURDATE(), '1106'),
('2', '2', '5', '', '1106'), ('2', '2', '5', util.VN_CURDATE(), '1106'),
('1', '7', '1', '', '1106'), ('1', '7', '1', util.VN_CURDATE(), '1106'),
('2', '8', '5', '', '1106'); ('2', '8', '5', util.VN_CURDATE(), '1106');
INSERT INTO `vncontrol`.`accion`(`accion_id`, `accion`) INSERT INTO `vncontrol`.`accion`(`accion_id`, `accion`)
VALUES VALUES
@ -1779,7 +1779,7 @@ INSERT INTO `vn`.`claimDestination`(`id`, `description`, `addressFk`)
INSERT INTO `vn`.`claimDevelopment`(`id`, `claimFk`, `claimResponsibleFk`, `workerFk`, `claimReasonFk`, `claimResultFk`, `claimRedeliveryFk`, `claimDestinationFk`) INSERT INTO `vn`.`claimDevelopment`(`id`, `claimFk`, `claimResponsibleFk`, `workerFk`, `claimReasonFk`, `claimResultFk`, `claimRedeliveryFk`, `claimDestinationFk`)
VALUES VALUES
(1, 1, 1, 21, 1, 1, 2, 5), (1, 1, 1, 21, 1, 1, 2, 5),
(2, 1, 1, 21, 7, 2, 2, 5), (2, 1, 2, 21, 7, 2, 2, 5),
(3, 2, 7, 21, 9, 3, 2, 5), (3, 2, 7, 21, 9, 3, 2, 5),
(4, 3, 7, 21, 15, 8, 2, 5), (4, 3, 7, 21, 15, 8, 2, 5),
(5, 4, 7, 21, 7, 8, 2, 5); (5, 4, 7, 21, 7, 8, 2, 5);
@ -2580,13 +2580,9 @@ INSERT INTO `bs`.`sale` (`saleFk`, `amount`, `dated`, `typeFk`, `clientFk`)
(4, 33.8, util.VN_CURDATE(), 1, 1101), (4, 33.8, util.VN_CURDATE(), 1, 1101),
(30, 34.4, util.VN_CURDATE(), 1, 1108); (30, 34.4, util.VN_CURDATE(), 1, 1108);
INSERT INTO `vn`.`docuware` (`code`, `fileCabinetName`, `dialogName` , `find`)
VALUES
('deliveryClient', 'deliveryClient', 'findTicket', 'word');
INSERT INTO `vn`.`docuwareConfig` (`url`) INSERT INTO `vn`.`docuwareConfig` (`url`)
VALUES VALUES
('https://verdnatura.docuware.cloud/docuware/platform'); ('http://docuware.url/');
INSERT INTO `vn`.`calendarHolidaysName` (`id`, `name`) INSERT INTO `vn`.`calendarHolidaysName` (`id`, `name`)
VALUES VALUES
@ -2692,6 +2688,7 @@ INSERT INTO `util`.`notificationConfig`
INSERT INTO `util`.`notification` (`id`, `name`, `description`) INSERT INTO `util`.`notification` (`id`, `name`, `description`)
VALUES VALUES
(1, 'print-email', 'notification fixture one'), (1, 'print-email', 'notification fixture one'),
(2, 'invoice-electronic', 'A electronic invoice has been generated'),
(4, 'supplier-pay-method-update', 'A supplier pay method has been updated'); (4, 'supplier-pay-method-update', 'A supplier pay method has been updated');
INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`) INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`)

View File

@ -80202,3 +80202,4 @@ USE `vncontrol`;
-- Dump completed on 2022-11-21 7:57:28 -- Dump completed on 2022-11-21 7:57:28

View File

@ -59,6 +59,7 @@ TABLES=(
componentType componentType
continent continent
department department
docuware
itemPackingType itemPackingType
pgc pgc
sample sample

View File

@ -15,7 +15,7 @@ describe('SmartTable SearchBar integration', () => {
await browser.close(); await browser.close();
}); });
describe('as filters', () => { describe('as filters in smart-table section', () => {
it('should search by type in searchBar', async() => { it('should search by type in searchBar', async() => {
await page.waitToClick(selectors.itemsIndex.openAdvancedSearchButton); await page.waitToClick(selectors.itemsIndex.openAdvancedSearchButton);
await page.autocompleteSearch(selectors.itemsIndex.advancedSearchItemType, 'Anthurium'); await page.autocompleteSearch(selectors.itemsIndex.advancedSearchItemType, 'Anthurium');
@ -47,6 +47,34 @@ describe('SmartTable SearchBar integration', () => {
}); });
}); });
describe('as filters in section without smart-table', () => {
it('go to zone section', async() => {
await page.loginAndModule('salesPerson', 'zone');
await page.waitToClick(selectors.globalItems.searchButton);
});
it('should search in searchBar first time', async() => {
await page.doSearch('A');
const count = await page.countElement(selectors.zoneIndex.searchResult);
expect(count).toEqual(7);
});
it('should search in searchBar second time', async() => {
await page.doSearch('A');
const count = await page.countElement(selectors.zoneIndex.searchResult);
expect(count).toEqual(7);
});
it('should search in searchBar third time', async() => {
await page.doSearch('A');
const count = await page.countElement(selectors.zoneIndex.searchResult);
expect(count).toEqual(7);
});
});
describe('as orders', () => { describe('as orders', () => {
it('should order by first id', async() => { it('should order by first id', async() => {
await page.loginAndModule('developer', 'item'); await page.loginAndModule('developer', 'item');

View File

@ -2,68 +2,32 @@ import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer'; import getBrowser from '../../helpers/puppeteer';
describe('Worker summary path', () => { describe('Worker summary path', () => {
const workerId = 3;
let browser; let browser;
let page; let page;
beforeAll(async() => { beforeAll(async() => {
browser = await getBrowser(); browser = await getBrowser();
page = browser.page; page = browser.page;
await page.loginAndModule('employee', 'worker'); await page.loginAndModule('employee', 'worker');
const httpDataResponse = page.waitForResponse(response => {
return response.status() === 200 && response.url().includes(`Workers/${workerId}`);
});
await page.accessToSearchResult('agencyNick'); await page.accessToSearchResult('agencyNick');
await httpDataResponse;
}); });
afterAll(async() => { afterAll(async() => {
await browser.close(); await browser.close();
}); });
it('should reach the employee summary section', async() => { it('should reach the employee summary section and check all properties', async() => {
await page.waitForState('worker.card.summary'); expect(await page.getProperty(selectors.workerSummary.header, 'innerText')).toEqual('agency agency');
}); expect(await page.getProperty(selectors.workerSummary.id, 'innerText')).toEqual('3');
expect(await page.getProperty(selectors.workerSummary.email, 'innerText')).toEqual('agency@verdnatura.es');
it('should check the summary contains the name and userName on the header', async() => { expect(await page.getProperty(selectors.workerSummary.department, 'innerText')).toEqual('CAMARA');
const result = await page.waitToGetProperty(selectors.workerSummary.header, 'innerText'); expect(await page.getProperty(selectors.workerSummary.userId, 'innerText')).toEqual('3');
expect(await page.getProperty(selectors.workerSummary.userName, 'innerText')).toEqual('agency');
expect(result).toEqual('agency agency'); expect(await page.getProperty(selectors.workerSummary.role, 'innerText')).toEqual('agency');
}); expect(await page.getProperty(selectors.workerSummary.extension, 'innerText')).toEqual('1101');
it('should check the summary contains the basic data id', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.id, 'innerText');
expect(result).toEqual('3');
});
it('should check the summary contains the basic data email', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.email, 'innerText');
expect(result).toEqual('agency@verdnatura.es');
});
it('should check the summary contains the basic data department', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.department, 'innerText');
expect(result).toEqual('CAMARA');
});
it('should check the summary contains the user data id', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.userId, 'innerText');
expect(result).toEqual('3');
});
it('should check the summary contains the user data name', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.userName, 'innerText');
expect(result).toEqual('agency');
});
it('should check the summary contains the user data role', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.role, 'innerText');
expect(result).toEqual('agency');
});
it('should check the summary contains the user data extension', async() => {
const result = await page.waitToGetProperty(selectors.workerSummary.extension, 'innerText');
expect(result).toEqual('1101');
}); });
}); });

View File

@ -2,13 +2,18 @@ import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer'; import getBrowser from '../../helpers/puppeteer';
describe('Worker basic data path', () => { describe('Worker basic data path', () => {
const workerId = 1106;
let browser; let browser;
let page; let page;
beforeAll(async() => { beforeAll(async() => {
browser = await getBrowser(); browser = await getBrowser();
page = browser.page; page = browser.page;
await page.loginAndModule('hr', 'worker'); await page.loginAndModule('hr', 'worker');
const httpDataResponse = page.waitForResponse(response => {
return response.status() === 200 && response.url().includes(`Workers/${workerId}`);
});
await page.accessToSearchResult('David Charles Haller'); await page.accessToSearchResult('David Charles Haller');
await httpDataResponse;
await page.accessToSection('worker.card.basicData'); await page.accessToSection('worker.card.basicData');
}); });
@ -16,35 +21,20 @@ describe('Worker basic data path', () => {
await browser.close(); await browser.close();
}); });
it('should edit the form', async() => { it('should edit the form and then reload the section and check the data was edited', async() => {
await page.clearInput(selectors.workerBasicData.name); await page.overwrite(selectors.workerBasicData.name, 'David C.');
await page.write(selectors.workerBasicData.name, 'David C.'); await page.overwrite(selectors.workerBasicData.surname, 'H.');
await page.clearInput(selectors.workerBasicData.surname); await page.overwrite(selectors.workerBasicData.phone, '444332211');
await page.write(selectors.workerBasicData.surname, 'H.'); await page.click(selectors.workerBasicData.saveButton);
await page.clearInput(selectors.workerBasicData.phone);
await page.write(selectors.workerBasicData.phone, '444332211');
await page.waitToClick(selectors.workerBasicData.saveButton);
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!'); expect(message.text).toContain('Data saved!');
});
it('should reload the section then check the name was edited', async() => {
await page.reloadSection('worker.card.basicData'); await page.reloadSection('worker.card.basicData');
const result = await page.waitToGetProperty(selectors.workerBasicData.name, 'value');
expect(result).toEqual('David C.'); expect(await page.waitToGetProperty(selectors.workerBasicData.name, 'value')).toEqual('David C.');
}); expect(await page.waitToGetProperty(selectors.workerBasicData.surname, 'value')).toEqual('H.');
expect(await page.waitToGetProperty(selectors.workerBasicData.phone, 'value')).toEqual('444332211');
it('should the surname was edited', async() => {
const result = await page.waitToGetProperty(selectors.workerBasicData.surname, 'value');
expect(result).toEqual('H.');
});
it('should the phone was edited', async() => {
const result = await page.waitToGetProperty(selectors.workerBasicData.phone, 'value');
expect(result).toEqual('444332211');
}); });
}); });

View File

@ -16,19 +16,16 @@ describe('Worker pbx path', () => {
await browser.close(); await browser.close();
}); });
it('should receive an error when the extension exceeds 4 characters', async() => { it('should receive an error when the extension exceeds 4 characters and then sucessfully save the changes', async() => {
await page.write(selectors.workerPbx.extension, '55555'); await page.write(selectors.workerPbx.extension, '55555');
await page.waitToClick(selectors.workerPbx.saveButton); await page.click(selectors.workerPbx.saveButton);
const message = await page.waitForSnackbar(); let message = await page.waitForSnackbar();
expect(message.text).toContain('Extension format is invalid'); expect(message.text).toContain('Extension format is invalid');
});
it('should sucessfully save the changes', async() => { await page.overwrite(selectors.workerPbx.extension, '4444');
await page.clearInput(selectors.workerPbx.extension); await page.click(selectors.workerPbx.saveButton);
await page.write(selectors.workerPbx.extension, '4444'); message = await page.waitForSnackbar();
await page.waitToClick(selectors.workerPbx.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved! User must access web'); expect(message.text).toContain('Data saved! User must access web');
}); });

View File

@ -21,38 +21,34 @@ describe('Worker time control path', () => {
const fourPm = '16:00'; const fourPm = '16:00';
const hankPymId = 1107; const hankPymId = 1107;
it('should go to the next month', async() => { it('should go to the next month, go to current month and go 1 month in the past', async() => {
const date = new Date(); let date = new Date();
date.setMonth(date.getMonth() + 1); date.setMonth(date.getMonth() + 1);
const month = date.toLocaleString('default', {month: 'long'}); let month = date.toLocaleString('default', {month: 'long'});
await page.waitToClick(selectors.workerTimeControl.nextMonthButton); await page.click(selectors.workerTimeControl.nextMonthButton);
const result = await page.waitToGetProperty(selectors.workerTimeControl.monthName, 'innerText'); let result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText');
expect(result).toContain(month); expect(result).toContain(month);
});
it('should go to current month', async() => { date = new Date();
const date = new Date(); month = date.toLocaleString('default', {month: 'long'});
const month = date.toLocaleString('default', {month: 'long'});
await page.waitToClick(selectors.workerTimeControl.previousMonthButton); await page.click(selectors.workerTimeControl.previousMonthButton);
const result = await page.waitToGetProperty(selectors.workerTimeControl.monthName, 'innerText'); result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText');
expect(result).toContain(month); expect(result).toContain(month);
});
it('should go 1 month in the past', async() => { date = new Date();
const date = new Date();
date.setMonth(date.getMonth() - 1); date.setMonth(date.getMonth() - 1);
const timestamp = Math.round(date.getTime() / 1000); const timestamp = Math.round(date.getTime() / 1000);
const month = date.toLocaleString('default', {month: 'long'}); month = date.toLocaleString('default', {month: 'long'});
await page.loginAndModule('salesBoss', 'worker'); await page.loginAndModule('salesBoss', 'worker');
await page.goto(`http://localhost:5000/#!/worker/${hankPymId}/time-control?timestamp=${timestamp}`); await page.goto(`http://localhost:5000/#!/worker/${hankPymId}/time-control?timestamp=${timestamp}`);
await page.waitToClick(selectors.workerTimeControl.secondWeekDay); await page.click(selectors.workerTimeControl.secondWeekDay);
const result = await page.waitToGetProperty(selectors.workerTimeControl.monthName, 'innerText'); result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText');
expect(result).toContain(month); expect(result).toContain(month);
}); });
@ -115,7 +111,9 @@ describe('Worker time control path', () => {
}); });
it('should change week of month', async() => { it('should change week of month', async() => {
await page.waitToClick(selectors.workerTimeControl.thrirdWeekDay); await page.click(selectors.workerTimeControl.thrirdWeekDay);
await page.waitForTextInElement(selectors.workerTimeControl.mondayWorkedHours, '00:00 h.'); const result = await page.getProperty(selectors.workerTimeControl.mondayWorkedHours, 'innerText');
expect(result).toEqual('00:00 h.');
}); });
}); });

View File

@ -1,16 +1,25 @@
/* eslint-disable max-len */
import selectors from '../../helpers/selectors.js'; import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer'; import getBrowser from '../../helpers/puppeteer';
describe('Worker calendar path', () => { describe('Worker calendar path', () => {
let reasonableTimeBetweenClicks = 400; const reasonableTimeBetweenClicks = 300;
const date = new Date();
const lastYear = (date.getFullYear() - 1).toString();
let browser; let browser;
let page; let page;
async function accessAs(user) {
await page.loginAndModule(user, 'worker');
await page.accessToSearchResult('Charles Xavier');
await page.accessToSection('worker.card.calendar');
}
beforeAll(async() => { beforeAll(async() => {
browser = await getBrowser(); browser = await getBrowser();
page = browser.page; page = browser.page;
await page.loginAndModule('hr', 'worker'); accessAs('hr');
await page.accessToSearchResult('Charles Xavier');
await page.accessToSection('worker.card.calendar');
}); });
afterAll(async() => { afterAll(async() => {
@ -21,48 +30,40 @@ describe('Worker calendar path', () => {
it('should set two days as holidays on the calendar and check the total holidays increased by 1.5', async() => { it('should set two days as holidays on the calendar and check the total holidays increased by 1.5', async() => {
await page.waitToClick(selectors.workerCalendar.holidays); await page.waitToClick(selectors.workerCalendar.holidays);
await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.penultimateMondayOfJanuary); await page.click(selectors.workerCalendar.penultimateMondayOfJanuary);
await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.absence); await page.click(selectors.workerCalendar.absence);
await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.lastMondayOfMarch); await page.click(selectors.workerCalendar.lastMondayOfMarch);
await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.halfHoliday); await page.click(selectors.workerCalendar.halfHoliday);
await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.fistMondayOfMay); await page.click(selectors.workerCalendar.fistMondayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.furlough); await page.click(selectors.workerCalendar.furlough);
await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.secondTuesdayOfMay); await page.click(selectors.workerCalendar.secondTuesdayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.secondWednesdayOfMay); await page.click(selectors.workerCalendar.secondWednesdayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.secondThursdayOfMay); await page.click(selectors.workerCalendar.secondThursdayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.halfFurlough); await page.click(selectors.workerCalendar.halfFurlough);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.secondFridayOfJun);
await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.secondFridayOfJun);
const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText'); expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 1.5 ');
expect(result).toContain(' 1.5 ');
}); });
}); });
describe(`as salesBoss`, () => { describe(`as salesBoss`, () => {
it(`should log in and get to Charles Xavier's calendar`, async() => { it(`should log in, get to Charles Xavier's calendar, undo what was done here, and check the total holidays used are back to what it was`, async() => {
await page.loginAndModule('salesBoss', 'worker'); accessAs('salesBoss');
await page.accessToSearchResult('Charles Xavier');
await page.accessToSection('worker.card.calendar');
});
it('should undo what was done here', async() => {
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.holidays); await page.waitToClick(selectors.workerCalendar.holidays);
await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.penultimateMondayOfJanuary); await page.waitToClick(selectors.workerCalendar.penultimateMondayOfJanuary);
@ -90,45 +91,24 @@ describe('Worker calendar path', () => {
await page.waitToClick(selectors.workerCalendar.halfFurlough); await page.waitToClick(selectors.workerCalendar.halfFurlough);
await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.secondFridayOfJun); await page.waitToClick(selectors.workerCalendar.secondFridayOfJun);
});
it('should check the total holidays used are back to what it was', async() => { expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 0 ');
const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText');
expect(result).toContain(' 0 ');
}); });
}); });
describe(`as Charles Xavier`, () => { describe(`as Charles Xavier`, () => {
it(`should log in and get to his calendar`, async() => { it('should log in and get to his calendar, make a futile attempt to add holidays, check the total holidays used are now the initial ones and use the year selector to go to the previous year', async() => {
await page.loginAndModule('CharlesXavier', 'worker'); accessAs('CharlesXavier');
await page.accessToSearchResult('Charles Xavier');
await page.accessToSection('worker.card.calendar');
});
it('should make a futile attempt to add holidays', async() => {
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.holidays); await page.waitToClick(selectors.workerCalendar.holidays);
await page.waitForTimeout(reasonableTimeBetweenClicks); await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.penultimateMondayOfJanuary);
});
it('should check the total holidays used are now the initial ones', async() => { await page.click(selectors.workerCalendar.penultimateMondayOfJanuary);
const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText');
expect(result).toContain(' 0 '); expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 0 ');
});
it('should use the year selector to go to the previous year', async() => {
const date = new Date();
const lastYear = (date.getFullYear() - 1).toString();
await page.autocompleteSearch(selectors.workerCalendar.year, lastYear); await page.autocompleteSearch(selectors.workerCalendar.year, lastYear);
await page.waitForTimeout(reasonableTimeBetweenClicks); expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 0 ');
const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText');
expect(result).toContain(' 0 ');
}); });
}); });
}); });

View File

@ -55,7 +55,7 @@ describe('Ticket Future path', () => {
await page.autocompleteSearch(selectors.ticketFuture.ipt, 'Horizontal'); await page.autocompleteSearch(selectors.ticketFuture.ipt, 'Horizontal');
await page.waitToClick(selectors.ticketFuture.submit); await page.waitToClick(selectors.ticketFuture.submit);
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0); await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
}); });
it('should search with the destination IPT', async() => { it('should search with the destination IPT', async() => {
@ -68,7 +68,7 @@ describe('Ticket Future path', () => {
await page.autocompleteSearch(selectors.ticketFuture.futureIpt, 'Horizontal'); await page.autocompleteSearch(selectors.ticketFuture.futureIpt, 'Horizontal');
await page.waitToClick(selectors.ticketFuture.submit); await page.waitToClick(selectors.ticketFuture.submit);
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0); await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
}); });
it('should search with the origin grouped state', async() => { it('should search with the origin grouped state', async() => {
@ -152,50 +152,6 @@ describe('Ticket Future path', () => {
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4); await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
}); });
it('should search in smart-table with especified Lines', async() => {
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
await page.write(selectors.ticketFuture.tableLines, '0');
await page.keyboard.press('Enter');
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.waitToClick(selectors.ticketFuture.submit);
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
await page.write(selectors.ticketFuture.tableLines, '1');
await page.keyboard.press('Enter');
await page.waitForNumberOfElements(selectors.ticketFuture.table, 5);
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.waitToClick(selectors.ticketFuture.submit);
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
});
it('should search in smart-table with especified Liters', async() => {
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
await page.write(selectors.ticketFuture.tableLiters, '0');
await page.keyboard.press('Enter');
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.waitToClick(selectors.ticketFuture.submit);
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
await page.write(selectors.ticketFuture.tableLiters, '28');
await page.keyboard.press('Enter');
await page.waitForNumberOfElements(selectors.ticketFuture.table, 5);
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.waitToClick(selectors.ticketFuture.submit);
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
});
it('should check the three last tickets and move to the future', async() => { it('should check the three last tickets and move to the future', async() => {
await page.waitToClick(selectors.ticketFuture.multiCheck); await page.waitToClick(selectors.ticketFuture.multiCheck);
await page.waitToClick(selectors.ticketFuture.firstCheck); await page.waitToClick(selectors.ticketFuture.firstCheck);

View File

@ -308,7 +308,7 @@ export default class Searchbar extends Component {
this.tableQ = null; this.tableQ = null;
const hasParams = this.$params.q && Object.keys(JSON.parse(this.$params.q)).length; const hasParams = this.$params.q && JSON.parse(this.$params.q).tableQ;
if (hasParams) { if (hasParams) {
const stateFilter = JSON.parse(this.$params.q); const stateFilter = JSON.parse(this.$params.q);
for (let param in stateFilter) { for (let param in stateFilter) {
@ -325,8 +325,8 @@ export default class Searchbar extends Component {
for (let param in stateFilter.tableQ) for (let param in stateFilter.tableQ)
params[param] = stateFilter.tableQ[param]; params[param] = stateFilter.tableQ[param];
Object.assign(stateFilter, params); const newParams = Object.assign(stateFilter, params);
return this.model.applyParams(params) return this.model.applyParams(newParams)
.then(() => this.model.data); .then(() => this.model.data);
} }

View File

@ -174,14 +174,12 @@ describe('Component vnSearchbar', () => {
jest.spyOn(controller, 'doSearch'); jest.spyOn(controller, 'doSearch');
controller.model = { controller.model = {
refresh: jest.fn(), refresh: jest.fn(),
applyFilter: jest.fn().mockReturnValue(Promise.resolve()),
userParams: { userParams: {
id: 1 id: 1
} }
}; };
controller.model.applyParams = jest.fn().mockReturnValue(Promise.resolve());
jest.spyOn(controller.model, 'applyParams');
controller.filter = filter; controller.filter = filter;
controller.removeParam(0); controller.removeParam(0);

View File

@ -29,7 +29,7 @@ vn-table {
& > tbody { & > tbody {
display: table-row-group; display: table-row-group;
} }
& > vn-tfoot, & > vn-tfoot,
& > .vn-tfoot, & > .vn-tfoot,
& > tfoot { & > tfoot {
border-top: $border; border-top: $border;
@ -42,7 +42,7 @@ vn-table {
height: 48px; height: 48px;
} }
vn-thead, .vn-thead, vn-thead, .vn-thead,
vn-tbody, .vn-tbody, vn-tbody, .vn-tbody,
vn-tfoot, .vn-tfoot, vn-tfoot, .vn-tfoot,
thead, tbody, tfoot { thead, tbody, tfoot {
& > * { & > * {
@ -153,6 +153,18 @@ vn-table {
background-color: $color-font-bg-dark; background-color: $color-font-bg-dark;
color: $color-font-bg; color: $color-font-bg;
} }
&.dark-notice {
background-color: $color-notice;
color: $color-font-bg;
}
&.yellow {
background-color: $color-yellow;
color: $color-font-bg;
}
&.pink {
background-color: $color-pink;
color: $color-font-bg;
}
} }
vn-icon-menu { vn-icon-menu {
display: inline-block; display: inline-block;
@ -194,7 +206,7 @@ vn-table.scrollable > .vn-table,
} }
vn-thead th, vn-thead th,
vn-thead vn-th, vn-thead vn-th,
thead vn-th, thead vn-th,
thead th { thead th {
border-bottom: $border; border-bottom: $border;
@ -217,4 +229,4 @@ vn-table.scrollable.lg,
.tableWrapper { .tableWrapper {
overflow-x: auto; overflow-x: auto;
} }

View File

@ -101,6 +101,8 @@ $color-marginal: #222;
$color-success: #a3d131; $color-success: #a3d131;
$color-notice: #32b1ce; $color-notice: #32b1ce;
$color-alert: #fa3939; $color-alert: #fa3939;
$color-pink: #ff99cc;
$color-yellow: #ffff00;
$color-button: $color-secondary; $color-button: $color-secondary;
$color-spacer: rgba(255, 255, 255, .3); $color-spacer: rgba(255, 255, 255, .3);

View File

@ -1,7 +1,7 @@
Send SMS: Enviar SMS Send SMS: Enviar SMS
Destination: Destinatario Destination: Destinatario
Message: Mensaje Message: Mensaje
SMS sent!: ¡SMS enviado! SMS sent: ¡SMS enviado!
Characters remaining: Carácteres restantes Characters remaining: Carácteres restantes
The destination can't be empty: El destinatario no puede estar vacio The destination can't be empty: El destinatario no puede estar vacio
The message can't be empty: El mensaje no puede estar vacio The message can't be empty: El mensaje no puede estar vacio

View File

@ -135,7 +135,7 @@
"Changed client paymethod": "He cambiado la forma de pago del cliente [{{clientName}} ({{clientId}})]({{{url}}})", "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}}})", "Sent units from ticket": "Envio *{{quantity}}* unidades de [{{concept}} ({{itemId}})]({{{itemUrl}}}) a *\"{{nickname}}\"* provenientes del ticket id [{{ticketId}}]({{{ticketUrl}}})",
"Change quantity": "{{concept}} cambia de {{oldQuantity}} a {{newQuantity}}", "Change quantity": "{{concept}} cambia de {{oldQuantity}} a {{newQuantity}}",
"Claim will be picked": "Se recogerá el género de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}*", "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 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*", "Claim state has changed to canceled": "Se ha cambiado el estado de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}* a *anulado*",
"Client checked as validated despite of duplication": "Cliente comprobado a pesar de que existe el cliente id {{clientId}}", "Client checked as validated despite of duplication": "Cliente comprobado a pesar de que existe el cliente id {{clientId}}",
@ -252,5 +252,9 @@
"Receipt's bank was not found": "No se encontró el banco del recibo", "Receipt's bank was not found": "No se encontró el banco del recibo",
"This receipt was not compensated": "Este recibo no ha sido compensado", "This receipt was not compensated": "Este recibo no ha sido compensado",
"Client's email was not found": "No se encontró el email del cliente", "Client's email was not found": "No se encontró el email del cliente",
"Aplicación bloqueada por el usuario 9": "Aplicación bloqueada por el usuario 9" "App name does not exist": "El nombre de aplicación no es válido",
"Try again": "Vuelve a intentarlo",
"Aplicación bloqueada por el usuario 9": "Aplicación bloqueada por el usuario 9",
"Failed to upload file": "Error al subir archivo",
"The DOCUWARE PDF document does not exists": "The DOCUWARE PDF document does not exists"
} }

View File

@ -23,7 +23,7 @@ module.exports = Self => {
{ {
arg: 'search', arg: 'search',
type: 'string', type: 'string',
description: `If it's and integer searchs by id, otherwise it searchs by client name`, description: `If it's a number searchs by id, otherwise it searchs by client name`,
http: {source: 'query'} http: {source: 'query'}
}, },
{ {
@ -34,31 +34,31 @@ module.exports = Self => {
}, },
{ {
arg: 'id', arg: 'id',
type: 'integer', type: 'number',
description: 'The claim id', description: 'The claim id',
http: {source: 'query'} http: {source: 'query'}
}, },
{ {
arg: 'clientFk', arg: 'clientFk',
type: 'integer', type: 'number',
description: 'The client id', description: 'The client id',
http: {source: 'query'} http: {source: 'query'}
}, },
{ {
arg: 'claimStateFk', arg: 'claimStateFk',
type: 'integer', type: 'number',
description: 'The claim state id', description: 'The claim state id',
http: {source: 'query'} http: {source: 'query'}
}, },
{ {
arg: 'salesPersonFk', arg: 'salesPersonFk',
type: 'integer', type: 'number',
description: 'The salesPerson id', description: 'The salesPerson id',
http: {source: 'query'} http: {source: 'query'}
}, },
{ {
arg: 'attenderFk', arg: 'attenderFk',
type: 'integer', type: 'number',
description: 'The attender worker id', description: 'The attender worker id',
http: {source: 'query'} http: {source: 'query'}
}, },
@ -67,6 +67,18 @@ module.exports = Self => {
type: 'date', type: 'date',
description: 'The to date filter', description: 'The to date filter',
http: {source: 'query'} http: {source: 'query'}
},
{
arg: 'itemFk',
type: 'number',
description: 'The item id',
http: {source: 'query'}
},
{
arg: 'claimResponsibleFk',
type: 'number',
description: 'The claimResponsible id',
http: {source: 'query'}
} }
], ],
returns: { returns: {
@ -80,33 +92,58 @@ module.exports = Self => {
}); });
Self.filter = async(ctx, filter, options) => { Self.filter = async(ctx, filter, options) => {
const models = Self.app.models;
const conn = Self.dataSource.connector; const conn = Self.dataSource.connector;
const args = ctx.args;
const myOptions = {}; const myOptions = {};
let to; let to;
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const where = buildFilter(ctx.args, (param, value) => { let claimIdsByItemFk = [];
let claimIdsByClaimResponsibleFk = [];
if (args.itemFk) {
query = `SELECT cb.claimFk
FROM claimBeginning cb
LEFT JOIN sale s ON s.id = cb.saleFk
WHERE s.itemFk = ?`;
const claims = await Self.rawSql(query, [args.itemFk], myOptions);
claimIdsByItemFk = claims.map(claim => claim.claimFk);
}
if (args.claimResponsibleFk) {
const claims = await models.ClaimDevelopment.find({
fields: ['claimFk'],
where: {claimResponsibleFk: args.claimResponsibleFk}
}, myOptions);
claimIdsByClaimResponsibleFk = claims.map(claim => claim.claimFk);
}
const where = buildFilter(args, (param, value) => {
switch (param) { switch (param) {
case 'search': case 'search':
return /^\d+$/.test(value) return /^\d+$/.test(value)
? {'cl.id': value} ? {'cl.id': value}
: { : {
or: [ or: [
{'cl.clientName': {like: `%${value}%`}} {'c.name': {like: `%${value}%`}}
] ]
}; };
case 'clientName': case 'clientName':
return {'cl.clientName': {like: `%${value}%`}}; return {'c.name': {like: `%${value}%`}};
case 'clientFk': case 'clientFk':
return {'cl.clientFk': value};
case 'id': case 'id':
case 'claimStateFk': case 'claimStateFk':
case 'priority': case 'priority':
return {[`cl.${param}`]: value}; return {[`cl.${param}`]: value};
case 'itemFk':
return {'cl.id': {inq: claimIdsByItemFk}};
case 'claimResponsibleFk':
return {'cl.id': {inq: claimIdsByClaimResponsibleFk}};
case 'salesPersonFk': case 'salesPersonFk':
return {'cl.salesPersonFk': value}; return {'c.salesPersonFk': value};
case 'attenderFk': case 'attenderFk':
return {'cl.workerFk': value}; return {'cl.workerFk': value};
case 'created': case 'created':
@ -118,29 +155,23 @@ module.exports = Self => {
} }
}); });
filter = mergeFilters(ctx.args.filter, {where}); filter = mergeFilters(args.filter, {where});
const stmts = []; const stmts = [];
const stmt = new ParameterizedSQL( const stmt = new ParameterizedSQL(
`SELECT * `SELECT
FROM ( cl.id,
SELECT cl.clientFk,
cl.id, c.name AS clientName,
cl.clientFk, cl.workerFk,
c.name AS clientName, u.name AS workerName,
cl.workerFk, cs.description,
u.name AS workerName, cl.created
cs.description, FROM claim cl
cl.created, LEFT JOIN client c ON c.id = cl.clientFk
cs.priority, LEFT JOIN account.user u ON u.id = cl.workerFk
cl.claimStateFk, LEFT JOIN claimState cs ON cs.id = cl.claimStateFk`
c.salesPersonFk
FROM claim cl
LEFT JOIN client c ON c.id = cl.clientFk
LEFT JOIN worker w ON w.id = cl.workerFk
LEFT JOIN account.user u ON u.id = w.userFk
LEFT JOIN claimState cs ON cs.id = cl.claimStateFk ) cl`
); );
stmt.merge(conn.makeSuffix(filter)); stmt.merge(conn.makeSuffix(filter));

View File

@ -57,4 +57,44 @@ describe('claim filter()', () => {
throw e; throw e;
} }
}); });
it('should return 3 results filtering by item id', async() => {
const tx = await app.models.Claim.beginTransaction({});
try {
const options = {transaction: tx};
const result = await app.models.Claim.filter({args: {filter: {}, itemFk: 2}}, null, options);
expect(result.length).toEqual(3);
expect(result[0].id).toEqual(1);
expect(result[1].id).toEqual(2);
expect(result[2].id).toEqual(4);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return 3 results filtering by claimResponsible id', async() => {
const tx = await app.models.Claim.beginTransaction({});
try {
const options = {transaction: tx};
const result = await app.models.Claim.filter({args: {filter: {}, claimResponsibleFk: 7}}, null, options);
expect(result.length).toEqual(3);
expect(result[0].id).toEqual(2);
expect(result[1].id).toEqual(3);
expect(result[2].id).toEqual(4);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
}); });

View File

@ -58,8 +58,28 @@
ng-model="filter.created"> ng-model="filter.created">
</vn-date-picker> </vn-date-picker>
</vn-horizontal> </vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one class="dense"
label="Item"
url="Items/withName"
ng-model="filter.itemFk"
show-field="name"
value-field="id"
search-function="$ctrl.itemSearchFunc($search)"
order="id DESC">
<tpl-item>{{::id}} - {{::name}}</tpl-item>
</vn-autocomplete>
<vn-autocomplete
vn-one
ng-model="filter.claimResponsibleFk"
url="ClaimResponsibles"
show-field="description"
value-field="id"
label="Responsible">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal class="vn-mt-lg"> <vn-horizontal class="vn-mt-lg">
<vn-submit label="Search"></vn-submit> <vn-submit label="Search"></vn-submit>
</vn-horizontal> </vn-horizontal>
</form> </form>
</div> </div>

View File

@ -1,7 +1,14 @@
import ngModule from '../module'; import ngModule from '../module';
import SearchPanel from 'core/components/searchbar/search-panel'; import SearchPanel from 'core/components/searchbar/search-panel';
class Controller extends SearchPanel {
itemSearchFunc($search) {
return /^\d+$/.test($search)
? {id: $search}
: {name: {like: '%' + $search + '%'}};
}
}
ngModule.vnComponent('vnClaimSearchPanel', { ngModule.vnComponent('vnClaimSearchPanel', {
template: require('./index.html'), template: require('./index.html'),
controller: SearchPanel controller: Controller
}); });

View File

@ -11,7 +11,7 @@ describe('InvoiceOut createPdf()', () => {
const ctx = {req: activeCtx}; const ctx = {req: activeCtx};
it('should create a new PDF file and set true the hasPdf property', async() => { it('should create a new PDF file and set true the hasPdf property', async() => {
pending('https://redmine.verdnatura.es/issues/4875'); pending('https://redmine.verdnatura.es/issues/5035');
const invoiceId = 1; const invoiceId = 1;
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx active: activeCtx

View File

@ -13,7 +13,6 @@ describe('InvoiceOut downloadZip()', () => {
}; };
it('should return part of link to dowloand the zip', async() => { it('should return part of link to dowloand the zip', async() => {
pending('https://redmine.verdnatura.es/issues/4875');
const tx = await models.InvoiceOut.beginTransaction({}); const tx = await models.InvoiceOut.beginTransaction({});
try { try {
@ -31,6 +30,8 @@ describe('InvoiceOut downloadZip()', () => {
}); });
it('should return an error if the size of the files is too large', async() => { it('should return an error if the size of the files is too large', async() => {
pending('https://redmine.verdnatura.es/issues/5035');
const tx = await models.InvoiceOut.beginTransaction({}); const tx = await models.InvoiceOut.beginTransaction({});
let error; let error;

View File

@ -51,7 +51,6 @@ describe('InvoiceOut filter()', () => {
}); });
it('should return the invoice out matching hasPdf', async() => { it('should return the invoice out matching hasPdf', async() => {
pending('https://redmine.verdnatura.es/issues/4875');
const tx = await models.InvoiceOut.beginTransaction({}); const tx = await models.InvoiceOut.beginTransaction({});
const options = {transaction: tx}; const options = {transaction: tx};
@ -67,7 +66,7 @@ describe('InvoiceOut filter()', () => {
const result = await models.InvoiceOut.filter(ctx, {id: invoiceOut.id}, options); const result = await models.InvoiceOut.filter(ctx, {id: invoiceOut.id}, options);
expect(result.length).toEqual(1); expect(result.length).toBeGreaterThanOrEqual(1);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -0,0 +1,49 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => {
Self.remoteMethod('filter', {
description: 'Returns all item shelving sale matching with the filter',
accessType: 'READ',
accepts: [{
arg: 'filter',
type: 'object',
description: 'Filter defining where and paginated data'
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/filter`,
verb: 'GET'
}
});
Self.filter = async(filter, options) => {
const conn = Self.dataSource.connector;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const stmt = new ParameterizedSQL(`
SELECT iss.created,
iss.saleFk,
iss.quantity,
iss.userFk,
ish.shelvingFk,
p.code,
u.name
FROM itemShelvingSale iss
LEFT JOIN itemShelving ish ON iss.itemShelvingFk = ish.id
LEFT JOIN shelving s ON ish.shelvingFk = s.code
LEFT JOIN parking p ON s.parkingFk = p.id
LEFT JOIN account.user u ON u.id = iss.userFk`
);
stmt.merge(conn.makeSuffix(filter));
return conn.executeStmt(stmt);
};
};

View File

@ -0,0 +1,3 @@
module.exports = Self => {
require('../methods/item-shelving-sale/filter')(Self);
};

View File

@ -12,21 +12,12 @@
"id": true, "id": true,
"description": "Identifier" "description": "Identifier"
}, },
"shelve": {
"type": "string"
},
"shelvingFk": { "shelvingFk": {
"type": "string" "type": "string"
}, },
"itemFk": { "itemFk": {
"type": "number" "type": "number"
}, },
"deep": {
"type": "number"
},
"quantity": {
"type": "number"
},
"created": { "created": {
"type": "date" "type": "date"
} }
@ -41,6 +32,11 @@
"type": "belongsTo", "type": "belongsTo",
"model": "Account", "model": "Account",
"foreignKey": "userFk" "foreignKey": "userFk"
} },
"shelving": {
"type": "belongsTo",
"model": "Shelving",
"foreignKey": "shelvingFk"
}
} }
} }

View File

@ -0,0 +1,46 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('last', {
description: 'Gets the latest version of a access file',
accepts: [
{
arg: 'appName',
type: 'string',
required: true,
description: 'The app name'
}
],
returns: {
type: 'number',
root: true
},
http: {
path: `/:appName/last`,
verb: 'GET'
}
});
Self.last = async(ctx, appName) => {
const models = Self.app.models;
const versions = await models.MdbVersion.find({
where: {app: appName},
fields: ['version']
});
if (!versions.length)
throw new UserError('App name does not exist');
let maxNumber = 0;
for (let mdb of versions) {
if (mdb.version > maxNumber)
maxNumber = mdb.version;
}
let response = {
version: maxNumber
};
return response;
};
};

View File

@ -11,20 +11,22 @@ module.exports = Self => {
type: 'string', type: 'string',
required: true, required: true,
description: 'The app name' description: 'The app name'
}, }, {
{ arg: 'toVersion',
arg: 'newVersion',
type: 'number', type: 'number',
required: true, required: true,
description: `The new version number` description: `The new version number`
}, }, {
{
arg: 'branch', arg: 'branch',
type: 'string', type: 'string',
required: true, required: true,
description: `The branch name` description: `The branch name`
}, }, {
{ arg: 'fromVersion',
type: 'string',
required: true,
description: `The old version number`
}, {
arg: 'unlock', arg: 'unlock',
type: 'boolean', type: 'boolean',
required: false, required: false,
@ -41,16 +43,13 @@ module.exports = Self => {
} }
}); });
Self.upload = async(ctx, appName, newVersion, branch, unlock, options) => { Self.upload = async(ctx, appName, toVersion, branch, fromVersion, unlock, options) => {
const models = Self.app.models; const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const myOptions = {}; const myOptions = {};
const $t = ctx.req.__; // $translate const $t = ctx.req.__; // $translate
const TempContainer = models.TempContainer; const TempContainer = models.TempContainer;
const AccessContainer = models.AccessContainer; const AccessContainer = models.AccessContainer;
const fileOptions = {}; const fileOptions = {};
let tx; let tx;
if (typeof options == 'object') if (typeof options == 'object')
@ -63,14 +62,28 @@ module.exports = Self => {
let srcFile; let srcFile;
try { try {
const userId = ctx.req.accessToken.userId;
const mdbApp = await models.MdbApp.findById(appName, null, myOptions); const mdbApp = await models.MdbApp.findById(appName, null, myOptions);
if (mdbApp.locked && mdbApp.userFk != userId) { if (mdbApp && mdbApp.locked && mdbApp.userFk != userId) {
throw new UserError($t('App locked', { throw new UserError($t('App locked', {
userId: mdbApp.userFk userId: mdbApp.userFk
})); }));
} }
const existBranch = await models.MdbBranch.findOne({
where: {name: branch}
}, myOptions);
if (!existBranch)
throw new UserError('Not exist this branch');
let lastMethod = await Self.last(ctx, appName, myOptions);
lastMethod.version++;
if (lastMethod.version != toVersion)
throw new UserError('Try again');
const tempContainer = await TempContainer.container('access'); const tempContainer = await TempContainer.container('access');
const uploaded = await TempContainer.upload(tempContainer.name, ctx.req, ctx.result, fileOptions); const uploaded = await TempContainer.upload(tempContainer.name, ctx.req, ctx.result, fileOptions);
const files = Object.values(uploaded.files).map(file => { const files = Object.values(uploaded.files).map(file => {
@ -83,7 +96,7 @@ module.exports = Self => {
const accessContainer = await AccessContainer.container('.archive'); const accessContainer = await AccessContainer.container('.archive');
const destinationFile = path.join( const destinationFile = path.join(
accessContainer.client.root, accessContainer.name, appName, `${newVersion}.7z`); accessContainer.client.root, accessContainer.name, appName, `${toVersion}.7z`);
if (process.env.NODE_ENV == 'test') if (process.env.NODE_ENV == 'test')
await fs.unlink(srcFile); await fs.unlink(srcFile);
@ -104,7 +117,7 @@ module.exports = Self => {
await fs.mkdir(branchPath, {recursive: true}); await fs.mkdir(branchPath, {recursive: true});
const destinationBranch = path.join(branchPath, `${appName}.7z`); const destinationBranch = path.join(branchPath, `${appName}.7z`);
const destinationRelative = `../../.archive/${appName}/${newVersion}.7z`; const destinationRelative = `../../.archive/${appName}/${toVersion}.7z`;
try { try {
await fs.unlink(destinationBranch); await fs.unlink(destinationBranch);
} catch (e) {} } catch (e) {}
@ -112,7 +125,7 @@ module.exports = Self => {
if (branch == 'master') { if (branch == 'master') {
const destinationRoot = path.join(accessContainer.client.root, `${appName}.7z`); const destinationRoot = path.join(accessContainer.client.root, `${appName}.7z`);
const rootRelative = `./.archive/${appName}/${newVersion}.7z`; const rootRelative = `./.archive/${appName}/${toVersion}.7z`;
try { try {
await fs.unlink(destinationRoot); await fs.unlink(destinationRoot);
} catch (e) {} } catch (e) {}
@ -120,10 +133,18 @@ module.exports = Self => {
} }
} }
await models.MdbVersionTree.create({
app: appName,
version: toVersion,
branchFk: branch,
fromVersion,
userFk: userId
}, myOptions);
await models.MdbVersion.upsert({ await models.MdbVersion.upsert({
app: appName, app: appName,
branchFk: branch, branchFk: branch,
version: newVersion version: toVersion
}, myOptions); }, myOptions);
if (unlock) await models.MdbApp.unlock(ctx, appName, myOptions); if (unlock) await models.MdbApp.unlock(ctx, appName, myOptions);
@ -133,7 +154,7 @@ module.exports = Self => {
if (tx) await tx.rollback(); if (tx) await tx.rollback();
if (fs.existsSync(srcFile)) if (fs.existsSync(srcFile))
await fs.unlink(srcFile); fs.unlink(srcFile);
throw e; throw e;
} }

View File

@ -8,6 +8,9 @@
"MdbVersion": { "MdbVersion": {
"dataSource": "vn" "dataSource": "vn"
}, },
"MdbVersionTree": {
"dataSource": "vn"
},
"AccessContainer": { "AccessContainer": {
"dataSource": "accessStorage" "dataSource": "accessStorage"
} }

View File

@ -1,3 +1,4 @@
module.exports = Self => { module.exports = Self => {
require('../methods/mdbVersion/upload')(Self); require('../methods/mdbVersion/upload')(Self);
require('../methods/mdbVersion/last')(Self);
}; };

View File

@ -0,0 +1,35 @@
{
"name": "MdbVersionTree",
"base": "VnModel",
"options": {
"mysql": {
"table": "mdbVersionTree"
}
},
"properties": {
"app": {
"type": "string",
"description": "The app name",
"id": true
},
"version": {
"type": "number"
},
"branchFk": {
"type": "string"
},
"fromVersion": {
"type": "number"
},
"userFk": {
"type": "number"
}
},
"relations": {
"branch": {
"type": "belongsTo",
"model": "MdbBranch",
"foreignKey": "branchFk"
}
}
}

View File

@ -79,51 +79,51 @@
</thead> </thead>
<tbody> <tbody>
<tr ng-repeat="ticket in model.data track by ticket.id" <tr ng-repeat="ticket in model.data track by ticket.id"
vn-anchor="::{ vn-anchor="{
state: 'ticket.card.summary', state: 'ticket.card.summary',
params: {id: ticket.id}, params: {id: ticket.id},
target: '_blank' target: '_blank'
}"> }">
<td> <td>
<vn-icon <vn-icon
ng-show="::ticket.isTaxDataChecked === 0" ng-show="ticket.isTaxDataChecked === 0"
translate-attr="{title: 'No verified data'}" translate-attr="{title: 'No verified data'}"
class="bright" class="bright"
icon="icon-no036"> icon="icon-no036">
</vn-icon> </vn-icon>
<vn-icon <vn-icon
ng-show="::ticket.hasTicketRequest" ng-show="ticket.hasTicketRequest"
translate-attr="{title: 'Purchase request'}" translate-attr="{title: 'Purchase request'}"
class="bright" class="bright"
icon="icon-buyrequest"> icon="icon-buyrequest">
</vn-icon> </vn-icon>
<vn-icon <vn-icon
ng-show="::ticket.itemShortage" ng-show="ticket.itemShortage"
translate-attr="{title: 'Not visible'}" translate-attr="{title: 'Not visible'}"
class="bright" class="bright"
icon="icon-unavailable"> icon="icon-unavailable">
</vn-icon> </vn-icon>
<vn-icon <vn-icon
ng-show="::ticket.isFreezed" ng-show="ticket.isFreezed"
translate-attr="{title: 'Client frozen'}" translate-attr="{title: 'Client frozen'}"
class="bright" class="bright"
icon="icon-frozen"> icon="icon-frozen">
</vn-icon> </vn-icon>
<vn-icon <vn-icon
ng-show="::ticket.risk" ng-show="ticket.risk"
ng-class="::{'highRisk': ticket.hasHighRisk}" ng-class="{'highRisk': ticket.hasHighRisk}"
title="{{::$ctrl.$t('Risk')}}: {{ticket.risk}}" title="{{$ctrl.$t('Risk')}}: {{ticket.risk}}"
class="bright" class="bright"
icon="icon-risk"> icon="icon-risk">
</vn-icon> </vn-icon>
<vn-icon <vn-icon
ng-show="::ticket.hasComponentLack" ng-show="ticket.hasComponentLack"
translate-attr="{title: 'Component lack'}" translate-attr="{title: 'Component lack'}"
class="bright" class="bright"
icon="icon-components"> icon="icon-components">
</vn-icon> </vn-icon>
<vn-icon <vn-icon
ng-show="::ticket.isTooLittle" ng-show="ticket.isTooLittle"
translate-attr="{title: 'Ticket too little'}" translate-attr="{title: 'Ticket too little'}"
class="bright" class="bright"
icon="icon-isTooLittle"> icon="icon-isTooLittle">
@ -133,64 +133,64 @@
<span <span
vn-click-stop="ticketDescriptor.show($event, ticket.id)" vn-click-stop="ticketDescriptor.show($event, ticket.id)"
class="link"> class="link">
{{::ticket.id}} {{ticket.id}}
</span> </span>
</td> </td>
<td name="nickname"> <td name="nickname">
<span <span
title="{{::ticket.nickname}}" title="{{ticket.nickname}}"
vn-click-stop="clientDescriptor.show($event, ticket.clientFk)" vn-click-stop="clientDescriptor.show($event, ticket.clientFk)"
class="link"> class="link">
{{::ticket.nickname}} {{ticket.nickname}}
</span> </span>
</td> </td>
<td> <td>
<span <span
title="{{::ticket.userName}}" title="{{ticket.userName}}"
vn-click-stop="workerDescriptor.show($event, ticket.salesPersonFk)" vn-click-stop="workerDescriptor.show($event, ticket.salesPersonFk)"
class="link"> class="link">
{{::ticket.userName | dashIfEmpty}} {{ticket.userName | dashIfEmpty}}
</span> </span>
</td> </td>
<td> <td>
<span class="chip {{::$ctrl.compareDate(ticket.shippedDate)}}"> <span class="chip {{$ctrl.compareDate(ticket.shippedDate)}}">
{{::ticket.shippedDate | date: 'dd/MM/yyyy'}} {{ticket.shippedDate | date: 'dd/MM/yyyy'}}
</span> </span>
</td> </td>
<td>{{::ticket.zoneLanding | date: 'HH:mm'}}</td> <td>{{ticket.zoneLanding | date: 'HH:mm'}}</td>
<td>{{::ticket.practicalHour | date: 'HH:mm'}}</td> <td>{{ticket.practicalHour | date: 'HH:mm'}}</td>
<td>{{::ticket.shipped | date: 'HH:mm'}}</td> <td>{{ticket.shipped | date: 'HH:mm'}}</td>
<td>{{::ticket.province}}</td> <td>{{ticket.province}}</td>
<td> <td>
<span <span
ng-show="::ticket.refFk" ng-show="ticket.refFk"
title="{{::ticket.refFk}}" title="{{ticket.refFk}}"
vn-click-stop="invoiceOutDescriptor.show($event, ticket.invoiceOutId)" vn-click-stop="invoiceOutDescriptor.show($event, ticket.invoiceOutId)"
class="link"> class="link">
{{::ticket.refFk}} {{ticket.refFk}}
</span> </span>
<span <span
ng-show="::!ticket.refFk" ng-show="!ticket.refFk"
class="chip {{::ticket.classColor}}"> class="chip {{ticket.classColor}}">
{{::ticket.state}} {{ticket.state}}
</span> </span>
</td> </td>
<td name="zone"> <td name="zone">
<span <span
title="{{::ticket.zoneName}}" title="{{ticket.zoneName}}"
vn-click-stop="zoneDescriptor.show($event, ticket.zoneFk)" vn-click-stop="zoneDescriptor.show($event, ticket.zoneFk)"
class="link"> class="link">
{{::ticket.zoneName | dashIfEmpty}} {{ticket.zoneName | dashIfEmpty}}
</span> </span>
</td> </td>
<td number> <td number>
<span class="chip {{::$ctrl.totalPriceColor(ticket)}}"> <span class="chip {{$ctrl.totalPriceColor(ticket)}}">
{{::(ticket.totalWithVat ? ticket.totalWithVat : 0) | currency: 'EUR': 2}} {{(ticket.totalWithVat ? ticket.totalWithVat : 0) | currency: 'EUR': 2}}
</span> </span>
</td> </td>
<td actions> <td actions>
<vn-icon-button <vn-icon-button
vn-anchor="::{ vn-anchor="{
state: 'ticket.card.sale', state: 'ticket.card.sale',
params: {id: ticket.id}, params: {id: ticket.id},
target: '_blank' target: '_blank'

View File

@ -26,7 +26,7 @@ class Controller extends Component {
throw new Error(`The message it's too long`); throw new Error(`The message it's too long`);
this.$http.post(`Routes/sendSms`, this.sms).then(res => { this.$http.post(`Routes/sendSms`, this.sms).then(res => {
this.vnApp.showMessage(this.$t('SMS sent!')); this.vnApp.showMessage(this.$t('SMS sent'));
if (res.data) this.emit('send', {response: res.data}); if (res.data) this.emit('send', {response: res.data});
}); });

View File

@ -30,7 +30,7 @@ describe('Route', () => {
controller.onResponse(); controller.onResponse();
$httpBackend.flush(); $httpBackend.flush();
expect(controller.vnApp.showMessage).toHaveBeenCalledWith('SMS sent!'); expect(controller.vnApp.showMessage).toHaveBeenCalledWith('SMS sent');
}); });
it('should call onResponse without the destination and show an error snackbar', () => { it('should call onResponse without the destination and show an error snackbar', () => {

View File

@ -0,0 +1,33 @@
module.exports = Self => {
Self.remoteMethodCtx('salePreparingList', {
description: 'Returns a list with the lines of a ticket and its different states of preparation',
accessType: 'READ',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'The ticket id',
http: {source: 'path'}
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/:id/salePreparingList`,
verb: 'GET'
}
});
Self.salePreparingList = async(ctx, id, options) => {
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
query = `CALL vn.salePreparingList(?)`;
const [sales] = await Self.rawSql(query, [id], myOptions);
return sales;
};
};

View File

@ -24,6 +24,8 @@ module.exports = Self => {
const salesDepartment = await models.Department.findOne({where: {code: 'VT'}, fields: 'id'}, myOptions); const salesDepartment = await models.Department.findOne({where: {code: 'VT'}, fields: 'id'}, myOptions);
const departments = await models.Department.getLeaves(salesDepartment.id, null, myOptions); const departments = await models.Department.getLeaves(salesDepartment.id, null, myOptions);
const workerDepartment = await models.WorkerDepartment.findById(userId, null, myOptions); const workerDepartment = await models.WorkerDepartment.findById(userId, null, myOptions);
if (!workerDepartment) return false;
const usesMana = departments.find(department => department.id == workerDepartment.departmentFk); const usesMana = departments.find(department => department.id == workerDepartment.departmentFk);
return usesMana ? true : false; return usesMana ? true : false;

View File

@ -108,16 +108,26 @@ module.exports = Self => {
switch (param) { switch (param) {
case 'id': case 'id':
return {'f.id': value}; return {'f.id': value};
case 'lines': case 'linesMax':
return {'f.lines': {lte: value}}; return {'f.lines': {lte: value}};
case 'liters': case 'litersMax':
return {'f.liters': {lte: value}}; return {'f.liters': {lte: value}};
case 'futureId': case 'futureId':
return {'f.futureId': value}; return {'f.futureId': value};
case 'ipt': case 'ipt':
return {'f.ipt': value}; return {or:
[
{'f.ipt': {like: `%${value}%`}},
{'f.ipt': null}
]
};
case 'futureIpt': case 'futureIpt':
return {'f.futureIpt': value}; return {or:
[
{'f.futureIpt': {like: `%${value}%`}},
{'f.futureIpt': null}
]
};
case 'state': case 'state':
return {'f.stateCode': {like: `%${value}%`}}; return {'f.stateCode': {like: `%${value}%`}};
case 'futureState': case 'futureState':
@ -203,7 +213,6 @@ module.exports = Self => {
tmp.ticket_problems`); tmp.ticket_problems`);
const sql = ParameterizedSQL.join(stmts, ';'); const sql = ParameterizedSQL.join(stmts, ';');
const result = await conn.executeStmt(sql, myOptions); const result = await conn.executeStmt(sql, myOptions);
return result[ticketsIndex]; return result[ticketsIndex];

View File

@ -19,7 +19,7 @@ describe('ticket getTicketsFuture()', () => {
const ctx = {req: {accessToken: {userId: 9}}, args}; const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options); const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4); expect(result.length).toBeGreaterThan(0);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback(); await tx.rollback();
@ -43,7 +43,7 @@ describe('ticket getTicketsFuture()', () => {
const ctx = {req: {accessToken: {userId: 9}}, args}; const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options); const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4); expect(result.length).toBeGreaterThan(0);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -93,7 +93,7 @@ describe('ticket getTicketsFuture()', () => {
const ctx = {req: {accessToken: {userId: 9}}, args}; const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options); const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4); expect(result.length).toBeGreaterThan(0);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -118,7 +118,7 @@ describe('ticket getTicketsFuture()', () => {
const ctx = {req: {accessToken: {userId: 9}}, args}; const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options); const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(1); expect(result.length).toBeGreaterThan(0);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -143,7 +143,7 @@ describe('ticket getTicketsFuture()', () => {
const ctx = {req: {accessToken: {userId: 9}}, args}; const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options); const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4); expect(result.length).toBeGreaterThan(0);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -168,7 +168,7 @@ describe('ticket getTicketsFuture()', () => {
const ctx = {req: {accessToken: {userId: 9}}, args}; const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options); const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4); expect(result.length).toBeGreaterThan(0);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -187,13 +187,13 @@ describe('ticket getTicketsFuture()', () => {
originDated: today, originDated: today,
futureDated: today, futureDated: today,
warehouseFk: 1, warehouseFk: 1,
ipt: 0 ipt: 'H'
}; };
const ctx = {req: {accessToken: {userId: 9}}, args}; const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options); const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(0); expect(result.length).toBeGreaterThan(0);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -218,7 +218,7 @@ describe('ticket getTicketsFuture()', () => {
const ctx = {req: {accessToken: {userId: 9}}, args}; const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options); const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4); expect(result.length).toBeGreaterThan(0);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -237,13 +237,13 @@ describe('ticket getTicketsFuture()', () => {
originDated: today, originDated: today,
futureDated: today, futureDated: today,
warehouseFk: 1, warehouseFk: 1,
futureIpt: 0 futureIpt: 'H'
}; };
const ctx = {req: {accessToken: {userId: 9}}, args}; const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options); const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(0); expect(result.length).toBeGreaterThan(0);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -268,7 +268,7 @@ describe('ticket getTicketsFuture()', () => {
const ctx = {req: {accessToken: {userId: 9}}, args}; const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options); const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(1); expect(result.length).toBeGreaterThan(0);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -293,7 +293,7 @@ describe('ticket getTicketsFuture()', () => {
const ctx = {req: {accessToken: {userId: 9}}, args}; const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options); const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4); expect(result.length).toBeGreaterThan(0);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -1,5 +1,6 @@
module.exports = Self => { module.exports = Self => {
require('../methods/sale/getClaimableFromTicket')(Self); require('../methods/sale/getClaimableFromTicket')(Self);
require('../methods/sale/salePreparingList')(Self);
require('../methods/sale/reserve')(Self); require('../methods/sale/reserve')(Self);
require('../methods/sale/deleteSales')(Self); require('../methods/sale/deleteSales')(Self);
require('../methods/sale/updatePrice')(Self); require('../methods/sale/updatePrice')(Self);

View File

@ -21,30 +21,28 @@
Add turn Add turn
</vn-item> </vn-item>
<vn-item class="dropdown" <vn-item class="dropdown"
vn-click-stop="showDeliveryNoteMenu.show($event, 'left')" vn-click-stop="showDeliveryNoteMenu.show($event, 'left'); $ctrl.hasDocuware()"
translate> translate>
Show Delivery Note... Show Delivery Note...
<vn-menu vn-id="showDeliveryNoteMenu"> <vn-menu vn-id="showDeliveryNoteMenu">
<vn-list> <vn-list>
<vn-item <vn-item
ng-if="!$ctrl.hasDocuwareFile"
ng-click="$ctrl.showPdfDeliveryNote('deliveryNote')" ng-click="$ctrl.showPdfDeliveryNote('deliveryNote')"
translate> translate>
as PDF as PDF
</vn-item> </vn-item>
<a class="vn-item"
ng-if="$ctrl.hasDocuwareFile"
href='api/Docuwares/{{$ctrl.ticket.id}}/download/deliveryClient/findTicket?access_token={{$ctrl.vnToken.token}}'
target="_blank"
translate>
as PDF
</a>
<vn-item <vn-item
ng-if="!$ctrl.hasDocuwareFile"
ng-click="$ctrl.showPdfDeliveryNote('withoutPrices')" ng-click="$ctrl.showPdfDeliveryNote('withoutPrices')"
translate> translate>
as PDF without prices as PDF without prices
</vn-item> </vn-item>
<a class="vn-item"
ng-if="$ctrl.hasDocuwareFile"
href='api/Docuwares/{{$ctrl.ticket.id}}/download/deliveryNote?access_token={{$ctrl.vnToken.token}}'
target="_blank"
translate>
as PDF signed
</a>
<vn-item <vn-item
ng-click="$ctrl.showCsvDeliveryNote()" ng-click="$ctrl.showCsvDeliveryNote()"
translate> translate>
@ -54,7 +52,7 @@
</vn-menu> </vn-menu>
</vn-item> </vn-item>
<vn-item class="dropdown" <vn-item class="dropdown"
vn-click-stop="sendDeliveryNoteMenu.show($event, 'left')" vn-click-stop="sendDeliveryNoteMenu.show($event, 'left'); $ctrl.hasDocuware()"
translate> translate>
Send Delivery Note... Send Delivery Note...
<vn-menu vn-id="sendDeliveryNoteMenu"> <vn-menu vn-id="sendDeliveryNoteMenu">
@ -64,6 +62,11 @@
translate> translate>
Send PDF Send PDF
</vn-item> </vn-item>
<vn-item
ng-click="$ctrl.uploadDocuware(!$ctrl.hasDocuwareFile)"
translate>
Send PDF to tablet
</vn-item>
<vn-item <vn-item
ng-click="sendCsvConfirmation.show({email: $ctrl.ticket.client.email})" ng-click="sendCsvConfirmation.show({email: $ctrl.ticket.client.email})"
translate> translate>
@ -323,3 +326,18 @@
question="Are you sure you want to refund all?" question="Are you sure you want to refund all?"
message="Refund all"> message="Refund all">
</vn-confirm> </vn-confirm>
<!-- Client balance popup-->
<vn-client-balance-create
vn-id="balance-create"
company-fk="$ctrl.vnConfig.companyFk"
client-fk="$ctrl.ticket.client.id">
</vn-client-balance-create>
<!-- Send pdf to tablet -->
<vn-confirm
vn-id="pdfToTablet"
on-accept="$ctrl.uploadDocuware(true)"
question="Are you sure you want to replace this delivery note?"
message="Already exist signed delivery note">
</vn-confirm>

View File

@ -85,7 +85,6 @@ class Controller extends Section {
.then(res => this.ticket = res.data) .then(res => this.ticket = res.data)
.then(() => { .then(() => {
this.isTicketEditable(); this.isTicketEditable();
this.hasDocuware();
}); });
} }
@ -134,15 +133,6 @@ class Controller extends Section {
}); });
} }
hasDocuware() {
const params = {
fileCabinet: 'deliveryClient',
dialog: 'findTicket'
};
this.$http.post(`Docuwares/${this.id}/checkFile`, params)
.then(res => this.hasDocuwareFile = res.data);
}
showPdfDeliveryNote(type) { showPdfDeliveryNote(type) {
this.vnReport.show(`tickets/${this.id}/delivery-note-pdf`, { this.vnReport.show(`tickets/${this.id}/delivery-note-pdf`, {
recipientId: this.ticket.client.id, recipientId: this.ticket.client.id,
@ -151,7 +141,10 @@ class Controller extends Section {
} }
sendPdfDeliveryNote($data) { sendPdfDeliveryNote($data) {
return this.vnEmail.send(`tickets/${this.id}/delivery-note-email`, { let query = `tickets/${this.id}/delivery-note-email`;
if (this.hasDocuwareFile) query = `docuwares/${this.id}/delivery-note-email`;
return this.vnEmail.send(query, {
recipientId: this.ticket.client.id, recipientId: this.ticket.client.id,
recipient: $data.email recipient: $data.email
}); });
@ -267,8 +260,15 @@ class Controller extends Section {
if (client.hasElectronicInvoice) { if (client.hasElectronicInvoice) {
this.$http.post(`NotificationQueues`, { this.$http.post(`NotificationQueues`, {
notificationFk: 'invoiceElectronic', notificationFk: 'invoice-electronic',
authorFk: client.id, authorFk: client.id,
params: JSON.stringify(
{
'name': client.name,
'email': client.email,
'ticketId': this.id,
'url': window.location.href
})
}).then(() => { }).then(() => {
this.vnApp.showSuccess(this.$t('Invoice sent')); this.vnApp.showSuccess(this.$t('Invoice sent'));
}); });
@ -312,6 +312,24 @@ class Controller extends Section {
return this.$http.post(`Tickets/${this.id}/sendSms`, sms) return this.$http.post(`Tickets/${this.id}/sendSms`, sms)
.then(() => this.vnApp.showSuccess(this.$t('SMS sent'))); .then(() => this.vnApp.showSuccess(this.$t('SMS sent')));
} }
hasDocuware() {
this.$http.post(`Docuwares/${this.id}/checkFile`, {fileCabinet: 'deliveryNote', signed: true})
.then(res => {
this.hasDocuwareFile = res.data;
});
}
uploadDocuware(force) {
if (!force)
return this.$.pdfToTablet.show();
return this.$http.post(`Docuwares/${this.id}/upload`, {fileCabinet: 'deliveryNote'})
.then(() => {
this.vnApp.showSuccess(this.$t('PDF sent!'));
this.$.balanceCreate.show();
});
}
} }
Controller.$inject = ['$element', '$scope', 'vnReport', 'vnEmail']; Controller.$inject = ['$element', '$scope', 'vnReport', 'vnEmail'];

View File

@ -286,9 +286,34 @@ describe('Ticket Component vnTicketDescriptorMenu', () => {
describe('hasDocuware()', () => { describe('hasDocuware()', () => {
it('should call hasDocuware method', () => { it('should call hasDocuware method', () => {
$httpBackend.whenPOST(`Docuwares/${ticket.id}/checkFile`).respond(); $httpBackend.whenPOST(`Docuwares/${ticket.id}/checkFile`).respond(true);
controller.hasDocuware(); controller.hasDocuware();
$httpBackend.flush(); $httpBackend.flush();
expect(controller.hasDocuwareFile).toBe(true);
});
});
describe('uploadDocuware()', () => {
it('should open dialog if not force', () => {
controller.$.pdfToTablet = {show: () => {}};
jest.spyOn(controller.$.pdfToTablet, 'show');
controller.uploadDocuware(false);
expect(controller.$.pdfToTablet.show).toHaveBeenCalled();
});
it('should make a query and show balance create', () => {
controller.$.balanceCreate = {show: () => {}};
jest.spyOn(controller.$.balanceCreate, 'show');
jest.spyOn(controller.vnApp, 'showSuccess');
$httpBackend.whenPOST(`Docuwares/${ticket.id}/upload`).respond(true);
controller.uploadDocuware(true);
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
expect(controller.$.balanceCreate.show).toHaveBeenCalled();
}); });
}); });

View File

@ -1,9 +1,11 @@
Show Delivery Note...: Ver albarán... Show Delivery Note...: Ver albarán...
Send Delivery Note...: Enviar albarán... Send Delivery Note...: Enviar albarán...
as PDF: como PDF as PDF: como PDF
as PDF signed: como PDF firmado
as CSV: como CSV as CSV: como CSV
as PDF without prices: como PDF sin precios as PDF without prices: como PDF sin precios
Send PDF: Enviar PDF Send PDF: Enviar PDF
Send PDF to tablet: Enviar PDF a tablet
Send CSV: Enviar CSV Send CSV: Enviar CSV
Send CSV Delivery Note: Enviar albarán en CSV Send CSV Delivery Note: Enviar albarán en CSV
Send PDF Delivery Note: Enviar albarán en PDF Send PDF Delivery Note: Enviar albarán en PDF
@ -13,3 +15,6 @@ Invoice sent: Factura enviada
The following refund ticket have been created: "Se ha creado siguiente ticket de abono: {{ticketId}}" The following refund ticket have been created: "Se ha creado siguiente ticket de abono: {{ticketId}}"
Transfer client: Transferir cliente Transfer client: Transferir cliente
SMS Notify changes: SMS Notificar cambios SMS Notify changes: SMS Notificar cambios
PDF sent!: ¡PDF enviado!
Already exist signed delivery note: Ya existe albarán de entrega firmado
Are you sure you want to replace this delivery note?: ¿Seguro que quieres reemplazar este albarán de entrega?

View File

@ -129,9 +129,9 @@
class="link"> class="link">
{{::ticket.id}} {{::ticket.id}}
</span></td> </span></td>
<td shrink-date> <td>
<span class="chip {{$ctrl.compareDate(ticket.shipped)}}"> <span class="chip {{$ctrl.compareDate(ticket.shipped)}}">
{{::ticket.shipped | date: 'dd/MM/yyyy'}} {{::ticket.shipped | date: 'dd/MM/yyyy HH:mm'}}
</span> </span>
</td> </td>
<td>{{::ticket.ipt}}</td> <td>{{::ticket.ipt}}</td>
@ -150,9 +150,9 @@
{{::ticket.futureId}} {{::ticket.futureId}}
</span> </span>
</td> </td>
<td shrink-date> <td>
<span class="chip {{$ctrl.compareDate(ticket.futureShipped)}}"> <span class="chip {{$ctrl.compareDate(ticket.futureShipped)}}">
{{::ticket.futureShipped | date: 'dd/MM/yyyy'}} {{::ticket.futureShipped | date: 'dd/MM/yyyy HH:mm'}}
</span> </span>
</td> </td>
<td>{{::ticket.futureIpt}}</td> <td>{{::ticket.futureIpt}}</td>

View File

@ -1,10 +1,11 @@
<vn-crud-model <vn-crud-model
vn-id="model" vn-id="model"
url="SaleTrackings/listSaleTracking" url="sales"
filter="::$ctrl.filter"
link="{ticketFk: $ctrl.$params.id}" link="{ticketFk: $ctrl.$params.id}"
limit="20" limit="20"
data="sales" data="$ctrl.sales"
order="itemFk DESC" order="concept ASC"
auto-load="true"> auto-load="true">
</vn-crud-model> </vn-crud-model>
<vn-data-viewer model="model"> <vn-data-viewer model="model">
@ -12,31 +13,27 @@
<vn-table model="model"> <vn-table model="model">
<vn-thead> <vn-thead>
<vn-tr> <vn-tr>
<vn-th shrink></vn-th> <vn-th field="isChecked" center>Is checked</vn-th>
<vn-th field="itemFk" number>Item</vn-th> <vn-th field="itemFk" number>Item</vn-th>
<vn-th expand>Description</vn-th> <vn-th field="concept">Description</vn-th>
<vn-th field="quantity" number>Quantity</vn-th> <vn-th field="quantity" number>Quantity</vn-th>
<vn-th field="originalQuantity" number>Original</vn-th> <vn-th></vn-th>
<vn-th field="workerFk">Worker</vn-th>
<vn-th field="state" shrink>State</vn-th>
<vn-th field="created" expand>Created</vn-th>
</vn-tr> </vn-tr>
</vn-thead> </vn-thead>
<vn-tbody> <vn-tbody>
<vn-tr ng-repeat="sale in sales"> <vn-tr ng-repeat="sale in $ctrl.sales">
<vn-td shrink> <vn-td center>
<vn-icon <span class="chip {{$ctrl.chipHasSaleGroupDetail(sale.preparingList.hasSaleGroupDetail)}} vn-mx-xs chip2" vn-tooltip="has saleGroupDetail"></span>
class="bright" <span class="chip {{$ctrl.chipIsPreviousSelected(sale.preparingList.isPreviousSelected)}} vn-ml-xs" vn-tooltip="is previousSelected"></span>
icon="warning" <span class="chip {{$ctrl.chipIsPrevious(sale.preparingList.isPrevious)}} vn-mr-xs" vn-tooltip="is previous"></span>
ng-if="sale.quantity != sale.originalQuantity" <span class="chip {{$ctrl.chipIsPrepared(sale.preparingList.isPrepared)}} vn-mx-xs" vn-tooltip="is prepared"></span>
vn-tooltip="The quantity do not match"> <span class="chip {{$ctrl.chipIsControled(sale.preparingList.isControled)}} vn-mx-xs" vn-tooltip="is controled"></span>
</vn-icon>
</vn-td> </vn-td>
<vn-td number> <vn-td number>
<span <span
ng-click="itemDescriptor.show($event, sale.itemFk, sale.id)" ng-click="$ctrl.showItemDescriptor($event, sale)"
class="link"> class="link">
{{sale.itemFk | zeroFill:6}} {{::sale.itemFk | zeroFill:6}}
</span> </span>
</vn-td> </vn-td>
<vn-td vn-fetched-tags> <vn-td vn-fetched-tags>
@ -53,16 +50,18 @@
</vn-fetched-tags> </vn-fetched-tags>
</vn-td> </vn-td>
<vn-td number>{{::sale.quantity}}</vn-td> <vn-td number>{{::sale.quantity}}</vn-td>
<vn-td number>{{::sale.originalQuantity}}</vn-td> <vn-td actions>
<vn-td expand> <vn-icon-button
<span vn-click-stop="$ctrl.showSaleTracking(sale)"
class="link" vn-tooltip="Sale tracking"
ng-click="workerDescriptor.show($event, sale.workerFk)"> icon="history">
{{::sale.userNickname | dashIfEmpty}} </vn-icon-button>
</span> <vn-icon-button
vn-click-stop="$ctrl.showItemShelvingSale(sale)"
vn-tooltip="ItemShelvings sale"
icon="icon-inventory">
</vn-icon-button>
</vn-td> </vn-td>
<vn-td shrink>{{::sale.state}}</vn-td>
<vn-td expand>{{::sale.created | date: 'dd/MM/yyyy HH:mm'}}</vn-td>
</vn-tr> </vn-tr>
</vn-tbody> </vn-tbody>
</vn-table> </vn-table>
@ -70,8 +69,99 @@
</vn-data-viewer> </vn-data-viewer>
<vn-item-descriptor-popover <vn-item-descriptor-popover
vn-id="item-descriptor" vn-id="item-descriptor"
warehouse-fk="$ctrl.ticket.warehouseFk"> warehouse-fk="$ctrl.ticket.warehouseFk"
ticket-fk="$ctrl.ticket.id">
</vn-item-descriptor-popover> </vn-item-descriptor-popover>
<vn-worker-descriptor-popover
vn-id="worker-descriptor"> <vn-popup vn-id="saleTracking">
</vn-worker-descriptor-popover> <vn-crud-model
vn-id="modelSaleTracking"
url="SaleTrackings/listSaleTracking"
link="{saleFk: $ctrl.saleId}"
limit="20"
data="saleTrackings"
order="itemFk DESC"
auto-load="true">
</vn-crud-model>
<vn-data-viewer model="modelSaleTracking">
<vn-card class="vn-w-lg">
<vn-table model="modelSaleTracking">
<vn-thead>
<vn-tr>
<vn-th field="quantity" number>Quantity</vn-th>
<vn-th field="originalQuantity" number>Original</vn-th>
<vn-th field="workerFk">Worker</vn-th>
<vn-th field="state" shrink>State</vn-th>
<vn-th field="created" expand>Created</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="sale in saleTrackings">
<vn-td number>{{::sale.quantity}}</vn-td>
<vn-td number>{{::sale.originalQuantity}}</vn-td>
<vn-td expand>
<span
class="link"
ng-click="workerDescriptor.show($event, sale.workerFk)">
{{::sale.userNickname | dashIfEmpty}}
</span>
</vn-td>
<vn-td shrink>{{::sale.state}}</vn-td>
<vn-td expand>{{::sale.created | date: 'dd/MM/yyyy HH:mm'}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<vn-item-descriptor-popover
vn-id="item-descriptor"
warehouse-fk="$ctrl.ticket.warehouseFk">
</vn-item-descriptor-popover>
<vn-worker-descriptor-popover
vn-id="worker-descriptor">
</vn-worker-descriptor-popover>
</vn-popup>
<vn-popup vn-id="itemShelvingSale">
<vn-crud-model
vn-id="modelSaleTracking"
url="ItemShelvingSales/filter"
link="{saleFk: $ctrl.saleId}"
limit="20"
data="$ctrl.itemShelvingSales"
auto-load="true">
</vn-crud-model>
<vn-data-viewer model="modelSaleTracking">
<vn-card class="vn-w-lg">
<vn-table model="modelSaleTracking">
<vn-thead>
<vn-tr>
<vn-th field="quantity" number>Quantity</vn-th>
<vn-th field="workerFk">Worker</vn-th>
<vn-th field="shelving" shrink>Shelving</vn-th>
<vn-th field="parking" shrink>Parking</vn-th>
<vn-th field="created" expand>Created</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="itemShelvingSale in $ctrl.itemShelvingSales">
<vn-td number>{{::itemShelvingSale.quantity}}</vn-td>
<vn-td expand>
<span
class="link"
ng-click="workerDescriptor.show($event, itemShelvingSale.userFk)">
{{::itemShelvingSale.name | dashIfEmpty}}
</span>
</vn-td>
<vn-td shrink>{{::itemShelvingSale.shelvingFk}}</vn-td>
<vn-td shrink>{{::itemShelvingSale.code}}</vn-td>
<vn-td expand>{{::itemShelvingSale.created | date: 'dd/MM/yyyy HH:mm'}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<vn-worker-descriptor-popover
vn-id="worker-descriptor">
</vn-worker-descriptor-popover>
</vn-popup>

View File

@ -1,12 +1,100 @@
import ngModule from '../module'; import ngModule from '../module';
import Section from 'salix/components/section'; import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {} class Controller extends Section {
constructor($element, $) {
super($element, $);
this.filter = {
include: [
{
relation: 'item'
}, {
relation: 'saleTracking',
scope: {
fields: ['isChecked']
}
}
]
};
}
get sales() {
return this._sales;
}
set sales(value) {
this._sales = value;
if (value) {
const query = `Sales/${this.$params.id}/salePreparingList`;
this.$http.get(query)
.then(res => {
this.salePreparingList = res.data;
for (const salePreparing of this.salePreparingList) {
for (const sale of this.sales) {
if (salePreparing.saleFk == sale.id)
sale.preparingList = salePreparing;
}
}
});
}
}
showItemDescriptor(event, sale) {
this.quicklinks = {
btnThree: {
icon: 'icon-transaction',
state: `item.card.diary({
id: ${sale.itemFk},
warehouseFk: ${this.ticket.warehouseFk},
lineFk: ${sale.id}
})`,
tooltip: 'Item diary'
}
};
this.$.itemDescriptor.show(event.target, sale.itemFk);
}
chipHasSaleGroupDetail(hasSaleGroupDetail) {
if (hasSaleGroupDetail) return 'pink';
else return 'message';
}
chipIsPreviousSelected(isPreviousSelected) {
if (isPreviousSelected) return 'notice';
else return 'message';
}
chipIsPrevious(isPrevious) {
if (isPrevious) return 'dark-notice';
else return 'message';
}
chipIsPrepared(isPrepared) {
if (isPrepared) return 'warning';
else return 'message';
}
chipIsControled(isControled) {
if (isControled) return 'yellow';
else return 'message';
}
showSaleTracking(sale) {
this.saleId = sale.id;
this.$.saleTracking.show();
}
showItemShelvingSale(sale) {
this.saleId = sale.id;
this.$.itemShelvingSale.show();
}
}
ngModule.vnComponent('vnTicketSaleTracking', { ngModule.vnComponent('vnTicketSaleTracking', {
template: require('./index.html'), template: require('./index.html'),
controller: Controller, controller: Controller,
bindings: { bindings: {
ticket: '<', ticket: '<'
}, }
}); });

View File

@ -0,0 +1,6 @@
ItemShelvings sale: Carros línea
has saleGroupDetail: tiene detalle grupo lineas
is previousSelected: es previa seleccionada
is previous: es previa
is prepared: esta preparado
is controled: esta controlado

View File

@ -0,0 +1,7 @@
@import "variables";
.chip {
display: inline-block;
min-width: 15px;
min-height: 25px;
}

View File

@ -458,10 +458,11 @@
</vn-popover> </vn-popover>
<!-- SMS Dialog --> <!-- SMS Dialog -->
<vn-ticket-sms <vn-sms-dialog
vn-id="sms" vn-id="sms"
sms="$ctrl.newSMS"> sms="$ctrl.newSMS"
</vn-ticket-sms> on-send="$ctrl.onSmsSend($sms)">
</vn-sms-dialog>
<vn-confirm <vn-confirm
vn-id="delete-lines" vn-id="delete-lines"

View File

@ -389,6 +389,11 @@ class Controller extends Section {
this.$.sms.open(); this.$.sms.open();
} }
onSmsSend(sms) {
return this.$http.post(`Tickets/${this.ticket.id}/sendSms`, sms)
.then(() => this.vnApp.showSuccess(this.$t('SMS sent')));
}
/** /**
* Inserts a new instance * Inserts a new instance
*/ */

View File

@ -27,7 +27,7 @@
<section> <section>
<vn-tool-bar class="vn-mb-md"> <vn-tool-bar class="vn-mb-md">
<vn-button <vn-button
disabled="!$ctrl.hasDateRange" disabled="!travels.length"
icon="picture_as_pdf" icon="picture_as_pdf"
ng-click="$ctrl.showReport()" ng-click="$ctrl.showReport()"
vn-tooltip="Open as PDF"> vn-tooltip="Open as PDF">

View File

@ -43,16 +43,6 @@ class Controller extends Section {
this.smartTableOptions = {}; this.smartTableOptions = {};
} }
get hasDateRange() {
const userParams = this.$.model.userParams;
const hasLanded = userParams.landedTo;
const hasShipped = userParams.shippedFrom;
const hasContinent = userParams.continent;
const hasWarehouseOut = userParams.warehouseOutFk;
return hasLanded || hasShipped || hasContinent || hasWarehouseOut;
}
onDragInterval() { onDragInterval() {
if (this.dragClientY > 0 && this.dragClientY < 75) if (this.dragClientY > 0 && this.dragClientY < 75)
this.$window.scrollTo(0, this.$window.scrollY - 10); this.$window.scrollTo(0, this.$window.scrollY - 10);

View File

@ -14,17 +14,6 @@ describe('Travel Component vnTravelExtraCommunity', () => {
controller.$.model.refresh = jest.fn(); controller.$.model.refresh = jest.fn();
})); }));
describe('hasDateRange()', () => {
it('should return truthy when shippedFrom or landedTo are set as userParams', () => {
const now = new Date();
controller.$.model.userParams = {shippedFrom: now, landedTo: now};
const result = controller.hasDateRange;
expect(result).toBeTruthy();
});
});
describe('findDraggable()', () => { describe('findDraggable()', () => {
it('should find the draggable element', () => { it('should find the draggable element', () => {
const draggable = document.createElement('tr'); const draggable = document.createElement('tr');

28
package-lock.json generated
View File

@ -9,7 +9,7 @@
"version": "9.0.0", "version": "9.0.0",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"axios": "^0.25.0", "axios": "^1.2.2",
"bcrypt": "^5.0.1", "bcrypt": "^5.0.1",
"bmp-js": "^0.1.0", "bmp-js": "^0.1.0",
"compression": "^1.7.3", "compression": "^1.7.3",
@ -3893,10 +3893,13 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "0.25.0", "version": "1.2.2",
"license": "MIT", "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.2.tgz",
"integrity": "sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==",
"dependencies": { "dependencies": {
"follow-redirects": "^1.14.7" "follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
} }
}, },
"node_modules/babel-jest": { "node_modules/babel-jest": {
@ -8401,14 +8404,15 @@
} }
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.14.9", "version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh" "url": "https://github.com/sponsors/RubenVerborgh"
} }
], ],
"license": "MIT",
"engines": { "engines": {
"node": ">=4.0" "node": ">=4.0"
}, },
@ -28842,9 +28846,13 @@
"version": "1.11.0" "version": "1.11.0"
}, },
"axios": { "axios": {
"version": "0.25.0", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.2.2.tgz",
"integrity": "sha512-bz/J4gS2S3I7mpN/YZfGFTqhXTYzRho8Ay38w2otuuDR322KzFIWm/4W2K6gIwvWaws5n+mnb7D1lN9uD+QH6Q==",
"requires": { "requires": {
"follow-redirects": "^1.14.7" "follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
} }
}, },
"babel-jest": { "babel-jest": {
@ -31964,7 +31972,9 @@
} }
}, },
"follow-redirects": { "follow-redirects": {
"version": "1.14.9" "version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
}, },
"for-in": { "for-in": {
"version": "1.0.2", "version": "1.0.2",

View File

@ -12,7 +12,7 @@
"node": ">=14" "node": ">=14"
}, },
"dependencies": { "dependencies": {
"axios": "^0.25.0", "axios": "^1.2.2",
"bcrypt": "^5.0.1", "bcrypt": "^5.0.1",
"bmp-js": "^0.1.0", "bmp-js": "^0.1.0",
"compression": "^1.7.3", "compression": "^1.7.3",

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html v-bind="$props">
<head>
<meta name="viewport" content="width=device-width">
<meta name="format-detection" content="telephone=no">
<title>{{ $t('subject') }}</title>
</head>
<body>
<h1>{{ $t('title') }} {{name}}</h1>
<p>{{ $t('clientMail') }} {{email}}</p>
<p>{{ $t('ticketId') }} <a :href='url'>{{ticketId}}</a>
</body>
</html>

View File

@ -0,0 +1,21 @@
module.exports = {
name: 'invoice-electronic',
props: {
name: {
type: [String],
required: true
},
email: {
type: [String],
required: true
},
ticketId: {
type: [Number],
required: true
},
url: {
type: [String],
required: true
}
},
};

View File

@ -0,0 +1,4 @@
subject: A electronic invoice has been created
title: A new electronic invoice has been created for the client
clientMail: The client's email is
ticketId: The invoice's ticket is

View File

@ -0,0 +1,4 @@
subject: Se ha creado una factura electrónica
title: Se ha creado una nueva factura electrónica para el cliente
clientMail: El correo del cliente es
ticketId: El ticket de la factura es

View File

@ -82,7 +82,7 @@ module.exports = {
return this.rawSqlFromDef(`taxes`, [reference]); return this.rawSqlFromDef(`taxes`, [reference]);
}, },
fetchIntrastat(reference) { fetchIntrastat(reference) {
return this.rawSqlFromDef(`intrastat`, [reference, reference, reference, reference, reference]); return this.rawSqlFromDef(`intrastat`, [reference, reference, reference]);
}, },
fetchRectified(reference) { fetchRectified(reference) {
return this.rawSqlFromDef(`rectified`, [reference]); return this.rawSqlFromDef(`rectified`, [reference]);

View File

@ -1,39 +1,26 @@
SELECT * SELECT *
FROM invoiceOut io FROM invoiceOut io
JOIN invoiceOutSerial ios ON io.serial = ios.code JOIN invoiceOutSerial ios ON io.serial = ios.code
JOIN JOIN(
(SELECT SELECT ir.id code,
t.refFk, ir.description,
ir.id code, iii.stems,
ir.description description, iii.net netKg,
CAST(SUM(IFNULL(i.stems, 1) * s.quantity) AS DECIMAL(10,2)) stems, iii.amount subtotal
CAST(SUM(CAST(IFNULL(i.stems, 1) * s.quantity * IF(ic.grams, ic.grams, i.density * ic.cm3delivery / 1000) / 1000 AS DECIMAL(10,2)) * FROM vn.invoiceInIntrastat iii
IF(sub.weight, sub.weight / vn.invoiceOut_getWeight(?), 1)) AS DECIMAL(10,2)) netKg, LEFT JOIN vn.invoiceIn ii ON ii.id = iii.invoiceInFk
CAST(SUM((s.quantity * s.price * (100 - s.discount) / 100 )) AS DECIMAL(10,2)) subtotal LEFT JOIN vn.invoiceOut io ON io.ref = ii.supplierRef
FROM vn.ticket t LEFT JOIN vn.intrastat ir ON ir.id = iii.intrastatFk
JOIN vn.sale s ON s.ticketFk = t.id WHERE io.`ref` = ?
JOIN vn.item i ON i.id = s.itemFk UNION ALL
JOIN vn.itemCost ic ON ic.itemFk = i.id AND ic.warehouseFk = t.warehouseFk SELECT NULL code,
JOIN vn.intrastat ir ON ir.id = i.intrastatFk 'Servicios' description,
LEFT JOIN ( 0 stems,
SELECT t2.weight 0 netKg,
FROM vn.ticket t2 IF(CAST(SUM((ts.quantity * ts.price)) AS DECIMAL(10,2)), CAST(SUM((ts.quantity * ts.price)) AS DECIMAL(10,2)), 0) subtotal
WHERE refFk = ? AND weight FROM vn.ticketService ts
LIMIT 1 JOIN vn.ticket t ON ts.ticketFk = t.id
) sub ON TRUE WHERE t.refFk = ?
WHERE t.refFk = ? ) sub
AND i.intrastatFk WHERE io.ref = ? AND ios.isCEE
GROUP BY i.intrastatFk ORDER BY sub.code;
UNION ALL
SELECT
NULL AS refFk,
NULL AS code,
NULL AS description,
0 AS stems,
0 AS netKg,
IF(CAST(SUM((ts.quantity * ts.price)) AS DECIMAL(10,2)), CAST(SUM((ts.quantity * ts.price)) AS DECIMAL(10,2)), 0) AS subtotal
FROM vn.ticketService ts
JOIN vn.ticket t ON ts.ticketFk = t.id
WHERE t.refFk = ?) sub
WHERE io.`ref` = ? AND ios.isCEE
ORDER BY sub.code;