Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 4873-zone.delivery-day
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Vicent Llopis 2023-01-27 08:59:52 +01:00
commit fe3d81ddd3
197 changed files with 3625 additions and 5075 deletions

View File

@ -5,17 +5,31 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2302.01] - 2023-01-12 ## [2304.01] - 2023-02-09
### Added ### Added
- [General](Inicio) Permite recuperar la contraseña - (Trabajadores -> Nuevo trabajador) Nueva sección
- [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 -
## [2302.01] - 2023-01-26
### Added
- (General -> Inicio) Permite recuperar la contraseña
- (Tickets -> Opciones) Subir albarán a Docuware
- (Tickets -> Opciones) Enviar correo con PDF de Docuware
- (Artículos -> Datos Básicos) Añadido campo Unidades/Caja
### Changed
- (Reclamaciones -> Descriptor) Cambiado el campo Agencia por Zona
- (Tickets -> Líneas preparadas) Actualizada sección para que sea más visual
### Fixed
- (General) Al utilizar el traductor de Google se descuadraban los iconos
### Removed ### Removed
- [Tickets](Control clientes) Eliminada sección - (Tickets -> Control clientes) Eliminada sección

View File

@ -0,0 +1,49 @@
const {Report} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('previousLabel', {
description: 'Returns the previa label pdf',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The item id',
http: {source: 'path'}
}],
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/previousLabel',
verb: 'GET'
}
});
Self.previousLabel = async(ctx, id) => {
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('previa-label', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="previa-${id}.pdf"`];
};
};

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', {
@ -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 {
// get fileCabinetId
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 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
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 fileName = `filename="${id}.pdf"`;
const contentType = 'application/pdf'; const contentType = 'application/pdf';
const downloadUri = `${docuwareUrl}/FileCabinets/${fileCabinetId}/Documents/${docuwareId}/FileDownload?targetFileType=Auto&keepAnnotations=false`; const downloadUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents/${docuwareFile.id}/FileDownload?targetFileType=Auto&keepAnnotations=false`;
const downloadOptions = {
'headers': {
'Cookie': cookie
}
};
const stream = got.stream(downloadUri, downloadOptions); const stream = await axios.get(downloadUri, options.headers);
return [stream, contentType, fileName]; return [stream.data, 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,10 +25,10 @@ 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
@ -36,25 +36,32 @@ module.exports = Self => {
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 sub2.thread_id, sub2.type, sub2.updated, sub2.created
FROM (
SELECT ote.thread_id, ote.created, ote.updated, ote.type
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
GROUP BY ote.thread_id ORDER BY ote.id DESC
LIMIT 10000000000000000000) sub2
GROUP BY sub2.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.id IN (?)
AND IF(sub.updated > sub.created, sub.updated, sub.created) < DATE_SUB(CURDATE(), INTERVAL ${config.day} DAY)`; AND sub.type = 'R'
AND IF(sub.updated > sub.created, sub.updated, sub.created) < DATE_SUB(CURDATE(), INTERVAL ? DAY);`;
const ticketsId = [];
const statusIdToClose = config.oldStatus.split(',');
let ticketsId = [];
con.connect(err => { con.connect(err => {
if (err) throw err; if (err) throw err;
con.query(sql, (err, results) => { con.query(sql, [statusIdToClose, config.day],
(err, results) => {
if (err) throw err; if (err) throw err;
for (const result of results) for (const result of results)
ticketsId.push(result.ticket_id); ticketsId.push(result.ticket_id);
}); });
}); });
await getRequestToken(); await getRequestToken();
async function getRequestToken() { async function getRequestToken() {
@ -94,30 +101,20 @@ module.exports = Self => {
await close(token, secondCookie); await close(token, secondCookie);
} }
async function getLockCode(token, secondCookie, ticketId) {
const ostUri = `${config.host}/ajax.php/lock/ticket/${ticketId}`;
const params = {
method: 'POST',
headers: {
'X-CSRFToken': token,
'Cookie': secondCookie
}
};
const response = await fetch(ostUri, params);
const body = await response.text();
const json = JSON.parse(body);
return json.code;
}
async function close(token, secondCookie) { async function close(token, secondCookie) {
for (const ticketId of ticketsId) { for (const ticketId of ticketsId) {
const lockCode = await getLockCode(token, secondCookie, ticketId); 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(); let form = new FormData();
form.append('__CSRFToken__', token); form.append('__CSRFToken__', token);
form.append('id', ticketId); form.append('id', ticketId);
form.append('a', config.responseType); form.append('a', config.responseType);
form.append('lockCode', lockCode); form.append('lockCode', lock.code);
form.append('from_email_id', config.fromEmailId); form.append('from_email_id', config.fromEmailId);
form.append('reply-to', config.replyTo); form.append('reply-to', config.replyTo);
form.append('cannedResp', 0); form.append('cannedResp', 0);
@ -133,8 +130,29 @@ module.exports = Self => {
'Cookie': secondCookie 'Cookie': secondCookie
} }
}; };
return fetch(ostUri, params); 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) {
const ostUri = `${config.host}/ajax.php/lock/ticket/${ticketId}`;
const params = {
method: 'POST',
headers: {
'X-CSRFToken': token,
'Cookie': secondCookie
}
};
const response = await fetch(ostUri, params);
const body = await response.text();
const json = JSON.parse(body);
return json;
}
}; };
}; };

View File

@ -3,4 +3,5 @@ module.exports = Self => {
require('../methods/collection/newCollection')(Self); require('../methods/collection/newCollection')(Self);
require('../methods/collection/getSectors')(Self); require('../methods/collection/getSectors')(Self);
require('../methods/collection/setSaleQuantity')(Self); require('../methods/collection/setSaleQuantity')(Self);
require('../methods/collection/previousLabel')(Self);
}; };

View File

@ -16,7 +16,7 @@
"url": { "url": {
"type": "string" "type": "string"
}, },
"token": { "cookie": {
"type": "string" "type": "string"
} }
}, },

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,3 @@
UPDATE `vn`.`collection`
SET sectorFk=1
WHERE id=1;

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,5 @@
UPDATE `vn`.`osTicketConfig`
SET oldStatus='1,6'
WHERE id=0;

View File

@ -0,0 +1,3 @@
ALTER TABLE `vn`.`itemPackingType` ADD isActive BOOLEAN NOT NULL;
UPDATE `vn`.`itemPackingType` SET isActive = 0 WHERE code IN ('P', 'F');
UPDATE `vn`.`itemPackingType` SET isActive = 1 WHERE code IN ('V', 'H');

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,23 @@
DROP FUNCTION IF EXISTS `vn`.`priceFixed_getRate2`;
DELIMITER $$
$$
CREATE FUNCTION `vn`.`priceFixed_getRate2`(vFixedPriceFk INT, vRate3 DOUBLE)
RETURNS DOUBLE
BEGIN
DECLARE vWarehouse INT;
DECLARE vRate2 DOUBLE;
SELECT round(vRate3 * (1 + ((r.rate2 - r.rate3)/100)), 2) INTO vRate2
FROM vn.rate r
JOIN vn.priceFixed p ON p.id = vFixedPriceFk
WHERE r.dated <= p.started
AND r.warehouseFk = p.warehouseFk
ORDER BY r.dated DESC
LIMIT 1;
RETURN vRate2;
END$$
DELIMITER ;

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

@ -0,0 +1,9 @@
ALTER TABLE `vn`.`itemConfig` ADD defaultTag INT DEFAULT 56 NOT NULL;
ALTER TABLE `vn`.`itemConfig` ADD CONSTRAINT itemConfig_FK FOREIGN KEY (defaultTag) REFERENCES vn.tag(id);
ALTER TABLE `vn`.`itemConfig` ADD validPriorities varchar(50) DEFAULT '[1,2,3]' NOT NULL;
ALTER TABLE `vn`.`itemConfig` ADD defaultPriority INT DEFAULT 2 NOT NULL;
ALTER TABLE `vn`.`item` MODIFY COLUMN relevancy tinyint(1) DEFAULT 0 NOT NULL COMMENT 'La web ordena de forma descendiente por este campo para mostrar los artículos';
INSERT INTO `salix`.`ACL`
(model, property, accessType, permission, principalType, principalId)
VALUES('ItemConfig', '*', 'READ', 'ALLOW', 'ROLE', 'buyer');

View File

@ -0,0 +1,6 @@
ALTER TABLE `vn`.`workerTimeControlConfig` ADD teleworkingStart INT NULL COMMENT 'Hora comienzo jornada de los teletrabajdores expresada en segundos';
ALTER TABLE `vn`.`workerTimeControlConfig` ADD teleworkingStartBreakTime INT NULL COMMENT 'Hora comienzo descanso de los teletrabjadores expresada en segundos';
UPDATE `vn`.`workerTimeControlConfig`
SET `teleworkingStart`=28800, `teleworkingStartBreakTime`=32400
WHERE `id`=1;

View File

@ -0,0 +1,24 @@
UPDATE `salix`.`ACL`
SET accessType='READ'
WHERE model='Worker'
AND property='*'
AND accessType='*'
AND permission='ALLOW'
AND principalType='ROLE'
AND principalId='employee';
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Worker', 'updateAttributes', 'WRITE', 'ALLOW', 'ROLE', 'hr'),
('Worker', 'createAbsence', '*', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'updateAbsence', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'deleteAbsence', '*', 'ALLOW', 'ROLE', 'employee'),
('Worker', 'new', 'WRITE', 'ALLOW', 'ROLE', 'hr'),
('Role', '*', 'READ', 'ALLOW', 'ROLE', 'hr');
ALTER TABLE `vn`.`workerConfig` ADD roleFk int(10) unsigned NOT NULL COMMENT 'Rol por defecto al dar de alta un trabajador nuevo';
UPDATE `vn`.`workerConfig`
SET roleFk = 1
WHERE id = 1;

View File

@ -1215,7 +1215,7 @@ INSERT INTO `vn`.`tag`(`id`, `code`, `name`, `isFree`, `isQuantitatif`, `sourceT
(7, NULL, 'Ancho de la base', 1, 1, NULL, 'mm',NULL, NULL), (7, NULL, 'Ancho de la base', 1, 1, NULL, 'mm',NULL, NULL),
(23, 'stems', 'Tallos', 1, 1, NULL, NULL, NULL, 'stems'), (23, 'stems', 'Tallos', 1, 1, NULL, NULL, NULL, 'stems'),
(27, NULL, 'Longitud(cm)', 1, 1, NULL, 'cm', NULL, NULL), (27, NULL, 'Longitud(cm)', 1, 1, NULL, 'cm', NULL, NULL),
(36, NULL, 'Proveedor', 1, 0, NULL, NULL, NULL, NULL), (36, 'producer', 'Proveedor', 1, 0, NULL, NULL, NULL, 'producer'),
(56, NULL, 'Genero', 1, 0, NULL, NULL, NULL, NULL), (56, NULL, 'Genero', 1, 0, NULL, NULL, NULL, NULL),
(58, NULL, 'Variedad', 1, 0, NULL, NULL, NULL, NULL), (58, NULL, 'Variedad', 1, 0, NULL, NULL, NULL, NULL),
(67, 'category', 'Categoria', 1, 0, NULL, NULL, NULL, NULL), (67, 'category', 'Categoria', 1, 0, NULL, NULL, NULL, NULL),
@ -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
@ -2633,7 +2629,7 @@ INSERT INTO `vn`.`machineWorker` (`workerFk`, `machineFk`, `inTimed`, `outTimed`
INSERT INTO `vn`.`zoneExclusion` (`id`, `zoneFk`, `dated`, `created`, `userFk`) INSERT INTO `vn`.`zoneExclusion` (`id`, `zoneFk`, `dated`, `created`, `userFk`)
VALUES VALUES
(1, 1, DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE())<=7, 7, 14) - DAYOFWEEK(util.VN_CURDATE())) DAY), util.VN_CURDATE(), 100), (1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL (IF(DAYOFWEEK(util.VN_CURDATE())<=7, 7, 14) - DAYOFWEEK(util.VN_CURDATE())) DAY), util.VN_CURDATE(), 100),
(2, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL (IF(DAYOFWEEK(util.VN_CURDATE())<=8, 8, 15) - DAYOFWEEK(util.VN_CURDATE())) DAY), util.VN_CURDATE(), 100); (2, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL (IF(DAYOFWEEK(util.VN_CURDATE())<=8, 8, 15) - DAYOFWEEK(util.VN_CURDATE())) DAY), util.VN_CURDATE(), 100);
INSERT INTO `vn`.`zoneExclusionGeo` (`zoneExclusionFk`, `geoFk`) INSERT INTO `vn`.`zoneExclusionGeo` (`zoneExclusionFk`, `geoFk`)
@ -2671,9 +2667,9 @@ INSERT INTO `vn`.`sectorCollectionSaleGroup` (`sectorCollectionFk`, `saleGroupFk
VALUES VALUES
(1, 1); (1, 1);
INSERT INTO `vn`.`workerTimeControlConfig` (`id`, `dayBreak`, `dayBreakDriver`, `shortWeekBreak`, `longWeekBreak`, `weekScope`, `mailPass`, `mailHost`, `mailSuccessFolder`, `mailErrorFolder`, `mailUser`, `minHoursToBreak`, `breakHours`, `hoursCompleteWeek`, `startNightlyHours`, `endNightlyHours`, `maxTimePerDay`, `breakTime`, `timeToBreakTime`, `dayMaxTime`, `shortWeekDays`, `longWeekDays`) INSERT INTO `vn`.`workerTimeControlConfig` (`id`, `dayBreak`, `dayBreakDriver`, `shortWeekBreak`, `longWeekBreak`, `weekScope`, `mailPass`, `mailHost`, `mailSuccessFolder`, `mailErrorFolder`, `mailUser`, `minHoursToBreak`, `breakHours`, `hoursCompleteWeek`, `startNightlyHours`, `endNightlyHours`, `maxTimePerDay`, `breakTime`, `timeToBreakTime`, `dayMaxTime`, `shortWeekDays`, `longWeekDays`, `teleworkingStart`, `teleworkingStartBreakTime`)
VALUES VALUES
(1, 43200, 32400, 129600, 259200, 604800, '', '', 'Leidos.exito', 'Leidos.error', 'timeControl', 5.33, 0.33, 40, '22:00:00', '06:00:00', 57600, 1200, 18000, 57600, 6, 13); (1, 43200, 32400, 129600, 259200, 604800, '', '', 'Leidos.exito', 'Leidos.error', 'timeControl', 5.33, 0.33, 40, '22:00:00', '06:00:00', 57600, 1200, 18000, 57600, 6, 13, 28800, 32400);
INSERT INTO `vn`.`host` (`id`, `code`, `description`, `warehouseFk`, `bankFk`) INSERT INTO `vn`.`host` (`id`, `code`, `description`, `warehouseFk`, `bankFk`)
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`)
@ -2722,6 +2719,10 @@ INSERT INTO `vn`.`collection` (`id`, `created`, `workerFk`, `stateFk`, `itemPack
VALUES VALUES
(3, util.VN_NOW(), 1107, 5, NULL, 0, 0, 1, NULL, NULL); (3, util.VN_NOW(), 1107, 5, NULL, 0, 0, 1, NULL, NULL);
INSERT INTO `vn`.`itemConfig` (`id`, `isItemTagTriggerDisabled`, `monthToDeactivate`, `wasteRecipients`, `validPriorities`, `defaultPriority`, `defaultTag`)
VALUES
(0, 0, 24, '', '[1,2,3]', 2, 56);
INSERT INTO `vn`.`ticketCollection` (`ticketFk`, `collectionFk`, `created`, `level`, `wagon`, `smartTagFk`, `usedShelves`, `itemCount`, `liters`) INSERT INTO `vn`.`ticketCollection` (`ticketFk`, `collectionFk`, `created`, `level`, `wagon`, `smartTagFk`, `usedShelves`, `itemCount`, `liters`)
VALUES VALUES
(9, 3, util.VN_NOW(), NULL, 0, NULL, NULL, NULL, NULL); (9, 3, util.VN_NOW(), NULL, 0, NULL, NULL, NULL, NULL);
@ -2744,16 +2745,20 @@ INSERT INTO `vn`.`ticketLog` (`originFk`, userFk, `action`, changedModel, oldIns
INSERT INTO `vn`.`osTicketConfig` (`id`, `host`, `user`, `password`, `oldStatus`, `newStatusId`, `day`, `comment`, `hostDb`, `userDb`, `passwordDb`, `portDb`, `responseType`, `fromEmailId`, `replyTo`) INSERT INTO `vn`.`osTicketConfig` (`id`, `host`, `user`, `password`, `oldStatus`, `newStatusId`, `day`, `comment`, `hostDb`, `userDb`, `passwordDb`, `portDb`, `responseType`, `fromEmailId`, `replyTo`)
VALUES VALUES
(0, 'http://localhost:56596/scp', 'ostadmin', 'Admin1', 'open', 3, 60, 'Este CAU se ha cerrado automáticamente. Si el problema persiste responda a este mensaje.', 'localhost', 'osticket', 'osticket', 40003, 'reply', 1, 'all'); (0, 'http://localhost:56596/scp', 'ostadmin', 'Admin1', '1,6', 3, 60, 'Este CAU se ha cerrado automáticamente. Si el problema persiste responda a este mensaje.', 'localhost', 'osticket', 'osticket', 40003, 'reply', 1, 'all');
INSERT INTO `vn`.`mdbApp` (`app`, `baselineBranchFk`, `userFk`, `locked`) INSERT INTO `vn`.`mdbApp` (`app`, `baselineBranchFk`, `userFk`, `locked`)
VALUES VALUES
('foo', 'master', NULL, NULL), ('foo', 'master', NULL, NULL),
('bar', 'test', 9, util.VN_NOW()); ('bar', 'test', 9, util.VN_NOW());
INSERT INTO `vn`.`ticketLog` (`id`, `originFk`, `userFk`, `action`, `changedModel`, `oldInstance`, `newInstance`, `changedModelId`)
VALUES
(1, 1, 9, 'insert', 'Ticket', '{}', '{"clientFk":1, "nickname": "Bat cave"}', 1);
INSERT INTO `vn`.`profileType` (`id`, `name`)
VALUES
(1, 'working');
INSERT INTO `vn`.`newWorkerConfig` (`id`, `street`, `provinceFk`, `companyFk`, `profileTypeFk`, `roleFk`)
VALUES
(1, 'S/ ', 1, 442, 1, 1);
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`) INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
VALUES VALUES
@ -2764,3 +2769,7 @@ INSERT INTO `vn`.`payDemDetail` (`id`, `detail`)
VALUES VALUES
(1, 1); (1, 1);
INSERT INTO `vn`.`workerConfig` (`id`, `businessUpdated`, `roleFk`)
VALUES
(1, NULL, 1);

View File

@ -80202,3 +80202,5 @@ 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

@ -58,6 +58,7 @@ export default {
deleteAccount: '.vn-menu [name="deleteUser"]', deleteAccount: '.vn-menu [name="deleteUser"]',
setPassword: '.vn-menu [name="setPassword"]', setPassword: '.vn-menu [name="setPassword"]',
activateAccount: '.vn-menu [name="enableAccount"]', activateAccount: '.vn-menu [name="enableAccount"]',
disableAccount: '.vn-menu [name="disableAccount"]',
activateUser: '.vn-menu [name="activateUser"]', activateUser: '.vn-menu [name="activateUser"]',
deactivateUser: '.vn-menu [name="deactivateUser"]', deactivateUser: '.vn-menu [name="deactivateUser"]',
newPassword: 'vn-textfield[ng-model="$ctrl.newPassword"]', newPassword: 'vn-textfield[ng-model="$ctrl.newPassword"]',
@ -417,8 +418,8 @@ export default {
fourthFixedPrice: 'vn-fixed-price tr:nth-child(5)', fourthFixedPrice: 'vn-fixed-price tr:nth-child(5)',
fourthItemID: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.itemFk"]', fourthItemID: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.itemFk"]',
fourthWarehouse: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.warehouseFk"]', fourthWarehouse: 'vn-fixed-price tr:nth-child(5) vn-autocomplete[ng-model="price.warehouseFk"]',
fourthPPU: 'vn-fixed-price tr:nth-child(5) > td:nth-child(4)', fourthGroupingPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(4)',
fourthPPP: 'vn-fixed-price tr:nth-child(5) > td:nth-child(5)', fourthPackingPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(5)',
fourthHasMinPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(6) > vn-check[ng-model="price.hasMinPrice"]', fourthHasMinPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(6) > vn-check[ng-model="price.hasMinPrice"]',
fourthMinPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(6) > vn-input-number[ng-model="price.minPrice"]', fourthMinPrice: 'vn-fixed-price tr:nth-child(5) > td:nth-child(6) > vn-input-number[ng-model="price.minPrice"]',
fourthStarted: 'vn-fixed-price tr:nth-child(5) vn-date-picker[ng-model="price.started"]', fourthStarted: 'vn-fixed-price tr:nth-child(5) vn-date-picker[ng-model="price.started"]',
@ -428,6 +429,7 @@ export default {
}, },
itemCreateView: { itemCreateView: {
temporalName: 'vn-item-create vn-textfield[ng-model="$ctrl.item.provisionalName"]', temporalName: 'vn-item-create vn-textfield[ng-model="$ctrl.item.provisionalName"]',
priority: 'vn-autocomplete[ng-model="$ctrl.item.priority"]',
type: 'vn-autocomplete[ng-model="$ctrl.item.typeFk"]', type: 'vn-autocomplete[ng-model="$ctrl.item.typeFk"]',
intrastat: 'vn-autocomplete[ng-model="$ctrl.item.intrastatFk"]', intrastat: 'vn-autocomplete[ng-model="$ctrl.item.intrastatFk"]',
origin: 'vn-autocomplete[ng-model="$ctrl.item.originFk"]', origin: 'vn-autocomplete[ng-model="$ctrl.item.originFk"]',
@ -521,7 +523,7 @@ export default {
}, },
itemLog: { itemLog: {
anyLineCreated: 'vn-item-log > vn-log vn-tbody > vn-tr', anyLineCreated: 'vn-item-log > vn-log vn-tbody > vn-tr',
fifthLineCreatedProperty: 'vn-item-log > vn-log vn-tbody > vn-tr:nth-child(5) table tr:nth-child(3) td.after', fifthLineCreatedProperty: 'vn-item-log > vn-log vn-tbody > vn-tr:nth-child(5) table tr:nth-child(2) td.after',
}, },
ticketSummary: { ticketSummary: {
header: 'vn-ticket-summary > vn-card > h5', header: 'vn-ticket-summary > vn-card > h5',
@ -679,7 +681,10 @@ export default {
moveToTicketButton: '.vn-popover.shown vn-icon[icon="arrow_forward_ios"]', moveToTicketButton: '.vn-popover.shown vn-icon[icon="arrow_forward_ios"]',
moveToNewTicketButton: '.vn-popover.shown vn-button[label="New ticket"]', moveToNewTicketButton: '.vn-popover.shown vn-button[label="New ticket"]',
stateMenuButton: 'vn-ticket-sale vn-tool-bar > vn-button-menu[label="State"]', stateMenuButton: 'vn-ticket-sale vn-tool-bar > vn-button-menu[label="State"]',
moreMenuState: 'body > div > div > div.content > div.filter.ng-scope > vn-textfield' moreMenuState: 'body > div > div > div.content > div.filter.ng-scope > vn-textfield',
firstSaleHistoryButton: 'vn-ticket-sale vn-tr:nth-child(1) vn-icon-button[icon="history"]',
firstSaleHistory: 'form vn-table div > vn-tbody > vn-tr',
closeHistory: 'div.window vn-button[icon="clear"]'
}, },
ticketTracking: { ticketTracking: {
createStateButton: 'vn-float-button' createStateButton: 'vn-float-button'
@ -965,6 +970,7 @@ export default {
confirmButton: '.vn-confirm.shown button[response="accept"]' confirmButton: '.vn-confirm.shown button[response="accept"]'
}, },
workerSummary: { workerSummary: {
summaryIcon: 'vn-worker-descriptor a[title="Go to module summary"]',
header: 'vn-worker-summary h5', header: 'vn-worker-summary h5',
id: 'vn-worker-summary vn-one:nth-child(1) > vn-label-value:nth-child(3) > section > span', id: 'vn-worker-summary vn-one:nth-child(1) > vn-label-value:nth-child(3) > section > span',
email: 'vn-worker-summary vn-one:nth-child(1) > vn-label-value:nth-child(4) > section > span', email: 'vn-worker-summary vn-one:nth-child(1) > vn-label-value:nth-child(4) > section > span',
@ -1017,6 +1023,25 @@ export default {
furlough: 'vn-worker-calendar > vn-side-menu [name="absenceTypes"] > vn-chip:nth-child(4)', furlough: 'vn-worker-calendar > vn-side-menu [name="absenceTypes"] > vn-chip:nth-child(4)',
halfFurlough: 'vn-worker-calendar > vn-side-menu [name="absenceTypes"] > vn-chip:nth-child(5)', halfFurlough: 'vn-worker-calendar > vn-side-menu [name="absenceTypes"] > vn-chip:nth-child(5)',
}, },
workerCreate: {
newWorkerButton: 'vn-worker-index a[ui-sref="worker.create"]',
firstname: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.firstName"]',
lastname: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.lastNames"]',
birth: 'vn-worker-create vn-date-picker[ng-model="$ctrl.worker.birth"]',
fi: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.fi"]',
code: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.code"]',
phone: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.phone"]',
city: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.city"]',
postcode: 'vn-worker-create vn-datalist[ng-model="$ctrl.worker.postcode"]',
street: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.street"]',
user: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.name"]',
email: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.email"]',
boss: 'vn-worker-create vn-autocomplete[ng-model="$ctrl.worker.bossFk"]',
role: 'vn-worker-create vn-autocomplete[ng-model="$ctrl.worker.roleFk"]',
iban: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.iban"]',
switft: 'vn-worker-create vn-autocomplete[ng-model="$ctrl.worker.bankEntityFk"]',
createButton: 'vn-worker-create vn-submit[label="Create"]',
},
invoiceOutIndex: { invoiceOutIndex: {
topbarSearch: 'vn-searchbar', topbarSearch: 'vn-searchbar',
searchResult: 'vn-invoice-out-index vn-card > vn-table > div > vn-tbody > a.vn-tr', searchResult: 'vn-invoice-out-index vn-card > vn-table > div > vn-tbody > a.vn-tr',

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

@ -0,0 +1,74 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Worker create path', () => {
let browser;
let page;
let newWorker;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('hr', 'worker');
await page.waitToClick(selectors.workerCreate.newWorkerButton);
await page.waitForState('worker.create');
});
afterAll(async() => {
await browser.close();
});
it('should insert default data', async() => {
await page.write(selectors.workerCreate.firstname, 'Victor');
await page.write(selectors.workerCreate.lastname, 'Von Doom');
await page.write(selectors.workerCreate.fi, '78457139E');
await page.write(selectors.workerCreate.phone, '12356789');
await page.write(selectors.workerCreate.postcode, '46680');
await page.write(selectors.workerCreate.street, 'S/ Doomstadt');
await page.write(selectors.workerCreate.email, 'doctorDoom@marvel.com');
await page.write(selectors.workerCreate.iban, 'ES9121000418450200051332');
await page.autocompleteSearch(selectors.workerCreate.switft, 'BBKKESMMMMM');
// should check for autocompleted worker code and worker user name
const workerCode = await page
.waitToGetProperty(selectors.workerCreate.code, 'value');
newWorker = await page
.waitToGetProperty(selectors.workerCreate.user, 'value');
expect(workerCode).toEqual('VVD');
expect(newWorker).toContain('victorvd');
// should fail if necessary data is void
await page.waitToClick(selectors.workerCreate.createButton);
let message = await page.waitForSnackbar();
expect(message.text).toContain('is a required argument');
// should create a new worker and go to worker basic data'
await page.pickDate(selectors.workerCreate.birth, new Date(1962, 8, 5));
await page.autocompleteSearch(selectors.workerCreate.boss, 'deliveryBoss');
await page.waitToClick(selectors.workerCreate.createButton);
message = await page.waitForSnackbar();
await page.waitForState('worker.card.basicData');
expect(message.text).toContain('Data saved!');
// 'rollback'
await page.loginAndModule('sysadmin', 'account');
await page.accessToSearchResult(newWorker);
await page.waitToClick(selectors.accountDescriptor.menuButton);
await page.waitToClick(selectors.accountDescriptor.deactivateUser);
await page.waitToClick(selectors.accountDescriptor.acceptButton);
message = await page.waitForSnackbar();
expect(message.text).toContain('User deactivated!');
await page.waitToClick(selectors.accountDescriptor.menuButton);
await page.waitToClick(selectors.accountDescriptor.disableAccount);
await page.waitToClick(selectors.accountDescriptor.acceptButton);
message = await page.waitForSnackbar();
expect(message.text).toContain('Account disabled!');
});
});

View File

@ -36,11 +36,20 @@ describe('Item Create', () => {
await page.waitForState('item.create'); await page.waitForState('item.create');
}); });
it('should create the Infinity Gauntlet item', async() => { it('should throw an error when insert an invalid priority', async() => {
await page.write(selectors.itemCreateView.temporalName, 'Infinity Gauntlet'); await page.write(selectors.itemCreateView.temporalName, 'Infinity Gauntlet');
await page.autocompleteSearch(selectors.itemCreateView.type, 'Crisantemo'); await page.autocompleteSearch(selectors.itemCreateView.type, 'Crisantemo');
await page.autocompleteSearch(selectors.itemCreateView.intrastat, 'Coral y materiales similares'); await page.autocompleteSearch(selectors.itemCreateView.intrastat, 'Coral y materiales similares');
await page.autocompleteSearch(selectors.itemCreateView.origin, 'Holand'); await page.autocompleteSearch(selectors.itemCreateView.origin, 'Holand');
await page.clearInput(selectors.itemCreateView.priority);
await page.waitToClick(selectors.itemCreateView.createButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Valid priorities');
});
it('should create the Infinity Gauntlet item', async() => {
await page.autocompleteSearch(selectors.itemCreateView.priority, '2');
await page.waitToClick(selectors.itemCreateView.createButton); await page.waitToClick(selectors.itemCreateView.createButton);
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();

View File

@ -24,8 +24,8 @@ describe('Item fixed prices path', () => {
it('should fill the fixed price data', async() => { it('should fill the fixed price data', async() => {
const now = new Date(); const now = new Date();
await page.autocompleteSearch(selectors.itemFixedPrice.fourthWarehouse, 'Warehouse one'); await page.autocompleteSearch(selectors.itemFixedPrice.fourthWarehouse, 'Warehouse one');
await page.write(selectors.itemFixedPrice.fourthPPU, '1'); await page.writeOnEditableTD(selectors.itemFixedPrice.fourthGroupingPrice, '1');
await page.write(selectors.itemFixedPrice.fourthPPP, '1'); await page.writeOnEditableTD(selectors.itemFixedPrice.fourthPackingPrice, '1');
await page.write(selectors.itemFixedPrice.fourthMinPrice, '1'); await page.write(selectors.itemFixedPrice.fourthMinPrice, '1');
await page.pickDate(selectors.itemFixedPrice.fourthStarted, now); await page.pickDate(selectors.itemFixedPrice.fourthStarted, now);
await page.pickDate(selectors.itemFixedPrice.fourthEnded, now); await page.pickDate(selectors.itemFixedPrice.fourthEnded, now);

View File

@ -196,6 +196,15 @@ describe('Ticket Edit sale path', () => {
expect(result).toContain('22.50'); expect(result).toContain('22.50');
}); });
it('should check in the history that logs has been added', async() => {
await page.waitToClick(selectors.ticketSales.firstSaleHistoryButton);
await page.waitForSelector(selectors.ticketSales.firstSaleHistory);
const result = await page.countElement(selectors.ticketSales.firstSaleHistory);
expect(result).toBeGreaterThan(0);
await page.waitToClick(selectors.ticketSales.closeHistory);
});
it('should recalculate price of sales', async() => { it('should recalculate price of sales', async() => {
await page.waitToClick(selectors.ticketSales.firstSaleCheckbox); await page.waitToClick(selectors.ticketSales.firstSaleCheckbox);
await page.waitToClick(selectors.ticketSales.secondSaleCheckbox); await page.waitToClick(selectors.ticketSales.secondSaleCheckbox);

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

@ -19,4 +19,5 @@ import './user-popover';
import './upload-photo'; import './upload-photo';
import './bank-entity'; import './bank-entity';
import './log'; import './log';
import './instance-log';
import './sendSms'; import './sendSms';

View File

@ -0,0 +1,12 @@
<vn-dialog
vn-id="instanceLog">
<tpl-body>
<vn-log
url="{{$ctrl.url}}"
origin-id="$ctrl.originId"
changed-model="$ctrl.changedModel"
changed-model-id="$ctrl.changedModelId">
</vn-log>
</tpl-body>
</vn-dialog>

View File

@ -0,0 +1,21 @@
import ngModule from '../../module';
import Section from '../section';
import './style.scss';
export default class Controller extends Section {
open() {
this.$.instanceLog.show();
}
}
ngModule.vnComponent('vnInstanceLog', {
controller: Controller,
template: require('./index.html'),
bindings: {
model: '<',
originId: '<',
changedModelId: '<',
changedModel: '@',
url: '@'
}
});

View File

@ -0,0 +1,13 @@
.vn-dialog {
& > .window:not(:has(.empty-rows)) {
width:60%;
vn-log {
vn-card {
visibility: hidden;
& > * {
visibility: visible;
}
}
}
}
}

View File

@ -1,6 +1,6 @@
Reset password: Restrablecer contraseña Reset password: Restrablecer contraseña
New password: Nueva contraseña New password: Nueva contraseña
Repeat password: Repetir contraseñaç Repeat password: Repetir contraseña
Password changed!: ¡Contraseña cambiada! Password changed!: ¡Contraseña cambiada!
Password requirements: > Password requirements: >
La contraseña debe tener al menos {{ length }} caracteres de longitud, La contraseña debe tener al menos {{ length }} caracteres de longitud,

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

@ -252,5 +252,12 @@
"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" "This worker code already exists": "Este codigo de trabajador ya existe",
"This personal mail already exists": "Este correo personal ya existe",
"This worker already exists": "Este trabajador ya existe",
"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

@ -0,0 +1,9 @@
module.exports = class NotFoundError extends Error {
constructor(message = 'Not found', code = 'NOT_FOUND', ...translateArgs) {
super(message);
this.name = 'NotFoundError';
this.statusCode = 404;
this.code = code;
this.translateArgs = translateArgs;
}
};

View File

@ -19,10 +19,26 @@ class Controller extends ModuleCard {
}, { }, {
relation: 'ticket', relation: 'ticket',
scope: { scope: {
fields: ['agencyModeFk'], fields: ['zoneFk', 'addressFk'],
include: { include: [
relation: 'agencyMode' {
relation: 'zone',
scope: {
fields: ['name']
} }
},
{
relation: 'address',
scope: {
fields: ['provinceFk'],
include: {
relation: 'province',
scope: {
fields: ['name']
}
}
}
}]
} }
}, { }, {
relation: 'claimState', relation: 'claimState',

View File

@ -51,8 +51,16 @@
</span> </span>
</vn-label-value> </vn-label-value>
<vn-label-value <vn-label-value
label="Agency" label="Zone">
value="{{$ctrl.claim.ticket.agencyMode.name}}"> <span
ng-click="zoneDescriptor.show($event, $ctrl.claim.ticket.zoneFk)"
class="link">
{{$ctrl.claim.ticket.zone.name}}
</span>
</vn-label-value>
<vn-label-value
label="Province"
value="{{$ctrl.claim.ticket.address.province.name}}">
</vn-label-value> </vn-label-value>
<vn-label-value <vn-label-value
label="Ticket"> label="Ticket">
@ -103,3 +111,6 @@
<vn-popup vn-id="summary"> <vn-popup vn-id="summary">
<vn-claim-summary claim="$ctrl.claim"></vn-claim-summary> <vn-claim-summary claim="$ctrl.claim"></vn-claim-summary>
</vn-popup> </vn-popup>
<vn-zone-descriptor-popover
vn-id="zoneDescriptor">
</vn-zone-descriptor-popover>

View File

@ -36,13 +36,13 @@
ng-if="$ctrl.isAgricultural()" ng-if="$ctrl.isAgricultural()"
ng-click="$ctrl.showPdfInvoice()" ng-click="$ctrl.showPdfInvoice()"
translate> translate>
Show agricultural invoice as PDF Show agricultural receipt as PDF
</vn-item> </vn-item>
<vn-item <vn-item
ng-if="$ctrl.isAgricultural()" ng-if="$ctrl.isAgricultural()"
ng-click="sendPdfConfirmation.show({email: $ctrl.entity.supplierContact[0].email})" ng-click="sendPdfConfirmation.show({email: $ctrl.entity.supplierContact[0].email})"
translate> translate>
Send agricultural invoice as PDF Send agricultural receipt as PDF
</vn-item> </vn-item>
</slot-menu> </slot-menu>
<slot-body> <slot-body>

View File

@ -19,6 +19,6 @@ To book: Contabilizar
Total amount: Total importe Total amount: Total importe
Total net: Total neto Total net: Total neto
Total stems: Total tallos Total stems: Total tallos
Show agricultural invoice as PDF: Ver factura agrícola como PDF Show agricultural receipt as PDF: Ver recibo agrícola como PDF
Send agricultural invoice as PDF: Enviar factura agrícola como PDF Send agricultural receipt as PDF: Enviar recibo agrícola como PDF
New InvoiceIn: Nueva Factura New InvoiceIn: Nueva Factura

View File

@ -11,6 +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/5035');
const invoiceId = 1; const invoiceId = 1;
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx active: activeCtx

View File

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

@ -0,0 +1,39 @@
module.exports = Self => {
Self.remoteMethod('getRate2', {
description: 'Return the rate2',
accessType: 'READ',
accepts: [
{
arg: 'fixedPriceId',
type: 'integer',
description: 'The fixedPrice Id',
required: true
},
{
arg: 'rate3',
type: 'number',
description: `The price rate 3`,
required: true
}
],
returns: {
type: 'object',
root: true
},
http: {
path: `/getRate2`,
verb: 'GET'
}
});
Self.getRate2 = async(fixedPriceId, rate3, options) => {
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const [result] = await Self.rawSql(`SELECT vn.priceFixed_getRate2(?, ?) as rate2`,
[fixedPriceId, rate3], myOptions);
return result;
};
};

View File

@ -0,0 +1,39 @@
const models = require('vn-loopback/server/server').models;
describe('getRate2()', () => {
it(`should return new rate2 if exists rate`, async() => {
const tx = await models.FixedPrice.beginTransaction({});
try {
const options = {transaction: tx};
const fixedPriceId = 1;
const rate3 = 2;
const result = await models.FixedPrice.getRate2(fixedPriceId, rate3, options);
expect(result.rate2).toEqual(1.9);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it(`should return null if not exists rate`, async() => {
const tx = await models.FixedPrice.beginTransaction({});
try {
const options = {transaction: tx};
const fixedPriceId = 13;
const rate3 = 2;
const result = await models.FixedPrice.getRate2(fixedPriceId, rate3, options);
expect(result.rate2).toEqual(null);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -37,7 +37,8 @@ module.exports = Self => {
'typeFk', 'typeFk',
'intrastatFk', 'intrastatFk',
'originFk', 'originFk',
'relevancy' 'priority',
'tag'
]; ];
for (const key in params) { for (const key in params) {
@ -46,10 +47,14 @@ module.exports = Self => {
} }
try { try {
const itemConfig = await models.ItemConfig.findOne({fields: ['validPriorities']}, myOptions);
if (!itemConfig.validPriorities.includes(params.priority))
throw new UserError(`Valid priorities: ${[...itemConfig.validPriorities]}`);
const provisionalName = params.provisionalName; const provisionalName = params.provisionalName;
delete params.provisionalName; delete params.provisionalName;
const itemType = await models.ItemType.findById(params.typeFk, myOptions); const itemType = await models.ItemType.findById(params.typeFk, {fields: ['isLaid']}, myOptions);
params.isLaid = itemType.isLaid; params.isLaid = itemType.isLaid;
@ -63,12 +68,13 @@ module.exports = Self => {
await Self.rawSql(query, null, myOptions); await Self.rawSql(query, null, myOptions);
let nameTag = await models.Tag.findOne({where: {name: 'Nombre temporal'}}); const nameTag = await models.Tag.findById(params.tag, {fields: ['id']}, myOptions);
let newTags = []; let newTags = [];
newTags.push({itemFk: item.id, tagFk: nameTag.id, value: provisionalName, priority: '2'}); newTags.push({itemFk: item.id, tagFk: nameTag.id, value: provisionalName, priority: item.priority});
typeTags.forEach(typeTag => { typeTags.forEach(typeTag => {
if (nameTag.id != typeTag.tagFk)
newTags.push({itemFk: item.id, tagFk: typeTag.tagFk, value: '', priority: typeTag.priority}); newTags.push({itemFk: item.id, tagFk: typeTag.tagFk, value: '', priority: typeTag.priority});
}); });

View File

@ -11,7 +11,8 @@ describe('item new()', () => {
originFk: 1, originFk: 1,
provisionalName: 'planta', provisionalName: 'planta',
typeFk: 2, typeFk: 2,
relevancy: 0 priority: 2,
tag: 1
}; };
let item = await models.Item.new(itemParams, options); let item = await models.Item.new(itemParams, options);
@ -20,7 +21,7 @@ describe('item new()', () => {
item.isLaid = itemType.isLaid; item.isLaid = itemType.isLaid;
const temporalNameTag = await models.Tag.findOne({where: {name: 'Nombre temporal'}}, options); const temporalNameTag = await models.Tag.findById(itemParams.tag, {fields: ['id']}, options);
const temporalName = await models.ItemTag.findOne({ const temporalName = await models.ItemTag.findOne({
where: { where: {
@ -31,7 +32,7 @@ describe('item new()', () => {
item = await models.Item.findById(item.id, null, options); item = await models.Item.findById(item.id, null, options);
itemType = await models.ItemType.findById(item.typeFk, options); itemType = await models.ItemType.findById(item.typeFk, {fields: ['isLaid']}, options);
item.isLaid = itemType.isLaid; item.isLaid = itemType.isLaid;

View File

@ -1,4 +1,5 @@
module.exports = Self => { module.exports = Self => {
require('../methods/fixed-price/filter')(Self); require('../methods/fixed-price/filter')(Self);
require('../methods/fixed-price/upsertFixedPrice')(Self); require('../methods/fixed-price/upsertFixedPrice')(Self);
require('../methods/fixed-price/getRate2')(Self);
}; };

View File

@ -16,6 +16,22 @@
"wasteRecipients": { "wasteRecipients": {
"type": "string", "type": "string",
"description": "Buyers waste report recipients" "description": "Buyers waste report recipients"
},
"validPriorities": {
"type": "array"
},
"defaultPriority": {
"type": "int"
},
"defaultTag": {
"type": "int"
}
},
"relations": {
"tag": {
"type": "belongsTo",
"model": "Tag",
"foreignKey": "defaultTag"
} }
} }
} }

View File

@ -13,6 +13,9 @@
}, },
"description": { "description": {
"type": "string" "type": "string"
},
"isActive":{
"type": "boolean"
} }
}, },
"acls": [ "acls": [

View File

@ -16,10 +16,24 @@
<vn-card class="vn-pa-lg"> <vn-card class="vn-pa-lg">
<vn-horizontal> <vn-horizontal>
<vn-textfield <vn-textfield
label="Temporal name" label="Name"
ng-model="$ctrl.item.provisionalName" ng-model="$ctrl.item.provisionalName"
vn-focus> vn-focus>
</vn-textfield> </vn-textfield>
<vn-autocomplete
ng-model="$ctrl.item.tag"
url="Tags"
show-field="name"
value-field="id"
label="Tag">
</vn-autocomplete>
<vn-autocomplete
data="$ctrl.validPriorities"
label="Priority"
ng-model="$ctrl.item.priority"
show-field="priority"
value-field="priority">
</vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-autocomplete <vn-autocomplete

View File

@ -4,10 +4,26 @@ import Section from 'salix/components/section';
class Controller extends Section { class Controller extends Section {
constructor($element, $) { constructor($element, $) {
super($element, $); super($element, $);
this.fetchDefaultPriorityTag();
}
fetchDefaultPriorityTag() {
this.validPriorities = [];
const filter = {fields: ['defaultPriority', 'defaultTag', 'validPriorities'], limit: 1};
this.$http.get(`ItemConfigs`, {filter})
.then(res => {
if (res.data) {
const dataRow = res.data[0];
dataRow.validPriorities.forEach(priority => {
this.validPriorities.push({priority});
});
this.item = { this.item = {
relevancy: 0 priority: dataRow.defaultPriority,
tag: dataRow.defaultTag
}; };
} }
});
}
onSubmit() { onSubmit() {
this.$.watcher.submit().then( this.$.watcher.submit().then(

View File

@ -41,14 +41,12 @@
<span translate>Warehouse</span> <span translate>Warehouse</span>
</th> </th>
<th <th
field="rate2" field="rate2">
vn-tooltip="Price By Unit"> <span translate>Grouping price</span>
<span translate>P.P.U.</span>
</th> </th>
<th <th
field="rate3" field="rate3">
vn-tooltip="Price By Package"> <span translate>Packing price</span>
<span translate>P.P.P.</span>
</th> </th>
<th field="minPrice"> <th field="minPrice">
<span translate>Min price</span> <span translate>Min price</span>
@ -72,7 +70,7 @@
show-field="name" show-field="name"
value-field="id" value-field="id"
search-function="$ctrl.itemSearchFunc($search)" search-function="$ctrl.itemSearchFunc($search)"
on-change="$ctrl.upsertPrice(price)" on-change="$ctrl.upsertPrice(price, true)"
order="id DESC" order="id DESC"
tabindex="1"> tabindex="1">
<tpl-item> <tpl-item>
@ -112,18 +110,32 @@
</vn-autocomplete> </vn-autocomplete>
</td> </td>
<td shrink-field> <td shrink-field>
<vn-td-editable number>
<text>{{price.rate2 | currency: 'EUR':2}}</text>
<field>
<vn-input-number <vn-input-number
class="dense"
vn-focus
ng-model="price.rate2" ng-model="price.rate2"
on-change="$ctrl.upsertPrice(price)" on-change="$ctrl.upsertPrice(price)"
step="0.01"> step="0.01">
</vn-input-number> </vn-input-number>
</field>
</vn-td-editable>
</td> </td>
<td shrink-field> <td shrink-field>
<vn-td-editable number>
<text>{{price.rate3 | currency: 'EUR':2}}</text>
<field>
<vn-input-number <vn-input-number
class="dense"
vn-focus
ng-model="price.rate3" ng-model="price.rate3"
on-change="$ctrl.upsertPrice(price)" on-change="$ctrl.upsertPrice(price); $ctrl.recalculateRate2(price)"
step="0.01"> step="0.01"s>
</vn-input-number> </vn-input-number>
</field>
</vn-td-editable>
</td> </td>
<td shrink-field-expand class="minPrice"> <td shrink-field-expand class="minPrice">
<vn-check <vn-check

View File

@ -62,7 +62,10 @@ export default class Controller extends Section {
}); });
} }
upsertPrice(price) { upsertPrice(price, resetMinPrice) {
if (resetMinPrice)
delete price['minPrice'];
price.hasMinPrice = price.minPrice ? true : false; price.hasMinPrice = price.minPrice ? true : false;
let requiredFields = ['itemFk', 'started', 'ended', 'rate2', 'rate3']; let requiredFields = ['itemFk', 'started', 'ended', 'rate2', 'rate3'];
@ -110,6 +113,24 @@ export default class Controller extends Section {
return {[param]: value}; return {[param]: value};
} }
} }
recalculateRate2(price) {
if (!price.id || !price.rate3) return;
const query = 'FixedPrices/getRate2';
const params = {
fixedPriceId: price.id,
rate3: price.rate3
};
this.$http.get(query, {params})
.then(res => {
const rate2 = res.data.rate2;
if (rate2) {
price.rate2 = rate2;
this.upsertPrice(price);
}
});
}
} }
ngModule.vnComponent('vnFixedPrice', { ngModule.vnComponent('vnFixedPrice', {

View File

@ -85,5 +85,25 @@ describe('fixed price', () => {
expect(controller.$.model.remove).toHaveBeenCalled(); expect(controller.$.model.remove).toHaveBeenCalled();
}); });
}); });
describe('recalculateRate2()', () => {
it(`should rate2 recalculate`, () => {
jest.spyOn(controller.vnApp, 'showSuccess');
const price = {
id: 1,
itemFk: 1,
rate2: 2,
rate3: 2
};
const response = {rate2: 1};
controller.recalculateRate2(price);
const query = `FixedPrices/getRate2?fixedPriceId=${price.id}&rate3=${price.rate3}`;
$httpBackend.expectGET(query).respond(response);
$httpBackend.flush();
expect(price.rate2).toEqual(response.rate2);
});
});
}); });
}); });

View File

@ -3,5 +3,3 @@ Search prices by item ID or code: Buscar por ID de artículo o código
Search fixed prices: Buscar precios fijados Search fixed prices: Buscar precios fijados
Add fixed price: Añadir precio fijado Add fixed price: Añadir precio fijado
This row will be removed: Esta linea se eliminará This row will be removed: Esta linea se eliminará
Price By Unit: Precio Por Unidad
Price By Package: Precio Por Paquete

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

@ -157,7 +157,7 @@ class Controller extends Section {
* Apply order to model * Apply order to model
*/ */
applyOrder() { applyOrder() {
if (this.typeId || this.tagGroups.length > 0) if (this.typeId || this.tagGroups.length > 0 || this.itemName)
this.$.model.addFilter(null, {orderBy: this.getOrderBy()}); this.$.model.addFilter(null, {orderBy: this.getOrderBy()});
} }

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

@ -36,7 +36,7 @@ module.exports = Self => {
st.originalQuantity, st.originalQuantity,
st.created, st.created,
st.workerFk, st.workerFk,
u.nickname userNickname, u.name,
ste.name AS state ste.name AS state
FROM saleTracking st FROM saleTracking st
JOIN sale s ON s.id = st.saleFk JOIN sale s ON s.id = st.saleFk
@ -48,24 +48,6 @@ module.exports = Self => {
const trackings = await Self.rawStmt(stmt, myOptions); const trackings = await Self.rawStmt(stmt, myOptions);
const salesFilter = {
include: [
{
relation: 'item'
}
],
where: {ticketFk: filter.where.ticketFk}
};
const sales = await Self.app.models.Sale.find(salesFilter, myOptions);
for (const tracking of trackings) {
for (const sale of sales) {
if (tracking.itemFk == sale.itemFk)
tracking.item = sale.item();
}
}
return trackings; return trackings;
}; };
}; };

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

@ -26,6 +26,9 @@
"PackingSiteConfig": { "PackingSiteConfig": {
"dataSource": "vn" "dataSource": "vn"
}, },
"ExpeditionMistake": {
"dataSource": "vn"
},
"PrintServerQueue": { "PrintServerQueue": {
"dataSource": "vn" "dataSource": "vn"
}, },

View File

@ -0,0 +1,33 @@
{
"name": "ExpeditionMistake",
"base": "VnModel",
"options": {
"mysql": {
"table": "expeditionMistake"
}
},
"properties": {
"created": {
"type": "date"
}
},
"relations": {
"expedition": {
"type": "belongsTo",
"model": "Expedition",
"foreignKey": "expeditionFk"
},
"worker": {
"type": "belongsTo",
"model": "Worker",
"foreignKey": "workerFk"
},
"type": {
"type": "belongsTo",
"model": "MistakeType",
"foreignKey": "typeFk"
}
}
}

View File

@ -25,7 +25,10 @@ class Controller extends SearchPanel {
getItemPackingTypes() { getItemPackingTypes() {
let itemPackingTypes = []; let itemPackingTypes = [];
this.$http.get('ItemPackingTypes').then(res => { const filter = {
where: {isActive: true}
};
this.$http.get('ItemPackingTypes', {filter}).then(res => {
for (let ipt of res.data) { for (let ipt of res.data) {
itemPackingTypes.push({ itemPackingTypes.push({
code: ipt.code, code: ipt.code,

View File

@ -39,6 +39,7 @@ export default class Controller extends Section {
field: 'ipt', field: 'ipt',
autocomplete: { autocomplete: {
url: 'ItemPackingTypes', url: 'ItemPackingTypes',
where: `{isActive: true}`,
showField: 'description', showField: 'description',
valueField: 'code' valueField: 'code'
} }
@ -47,6 +48,7 @@ export default class Controller extends Section {
field: 'futureIpt', field: 'futureIpt',
autocomplete: { autocomplete: {
url: 'ItemPackingTypes', url: 'ItemPackingTypes',
where: `{isActive: true}`,
showField: 'description', showField: 'description',
valueField: 'code' valueField: 'code'
} }

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

@ -25,7 +25,10 @@ class Controller extends SearchPanel {
getItemPackingTypes() { getItemPackingTypes() {
let itemPackingTypes = []; let itemPackingTypes = [];
this.$http.get('ItemPackingTypes').then(res => { const filter = {
where: {isActive: true}
};
this.$http.get('ItemPackingTypes', {filter}).then(res => {
for (let ipt of res.data) { for (let ipt of res.data) {
itemPackingTypes.push({ itemPackingTypes.push({
description: this.$t(ipt.description), description: this.$t(ipt.description),

View File

@ -129,12 +129,12 @@
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 | dashIfEmpty}}</td>
<td> <td>
<span <span
class="chip {{$ctrl.stateColor(ticket.state)}}"> class="chip {{$ctrl.stateColor(ticket.state)}}">
@ -150,12 +150,12 @@
{{::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 | dashIfEmpty}}</td>
<td> <td>
<span <span
class="chip {{$ctrl.stateColor(ticket.futureState)}}"> class="chip {{$ctrl.stateColor(ticket.futureState)}}">

View File

@ -34,6 +34,7 @@ export default class Controller extends Section {
field: 'ipt', field: 'ipt',
autocomplete: { autocomplete: {
url: 'ItemPackingTypes', url: 'ItemPackingTypes',
where: `{isActive: true}`,
showField: 'description', showField: 'description',
valueField: 'code' valueField: 'code'
} }
@ -42,6 +43,7 @@ export default class Controller extends Section {
field: 'futureIpt', field: 'futureIpt',
autocomplete: { autocomplete: {
url: 'ItemPackingTypes', url: 'ItemPackingTypes',
where: `{isActive: true}`,
showField: 'description', showField: 'description',
valueField: 'code' valueField: 'code'
} }

View File

@ -31,9 +31,9 @@
</vn-td> </vn-td>
<vn-td number> <vn-td number>
<span <span
ng-click="$ctrl.showItemDescriptor($event, sale)" ng-click="itemDescriptor.show($event, sale.item.id)"
class="link"> class="link">
{{::sale.itemFk | zeroFill:6}} {{::sale.item.id}}
</span> </span>
</vn-td> </vn-td>
<vn-td vn-fetched-tags> <vn-td vn-fetched-tags>
@ -96,27 +96,23 @@
</vn-tr> </vn-tr>
</vn-thead> </vn-thead>
<vn-tbody> <vn-tbody>
<vn-tr ng-repeat="sale in saleTrackings"> <vn-tr ng-repeat="saleTracking in saleTrackings">
<vn-td number>{{::sale.quantity}}</vn-td> <vn-td number>{{::saleTracking.quantity}}</vn-td>
<vn-td number>{{::sale.originalQuantity}}</vn-td> <vn-td number>{{::saleTracking.originalQuantity}}</vn-td>
<vn-td expand> <vn-td expand>
<span <span
class="link" class="link"
ng-click="workerDescriptor.show($event, sale.workerFk)"> ng-click="workerDescriptor.show($event, saleTracking.workerFk)">
{{::sale.userNickname | dashIfEmpty}} {{::saleTracking.name | dashIfEmpty}}
</span> </span>
</vn-td> </vn-td>
<vn-td shrink>{{::sale.state}}</vn-td> <vn-td shrink>{{::saleTracking.state}}</vn-td>
<vn-td expand>{{::sale.created | date: 'dd/MM/yyyy HH:mm'}}</vn-td> <vn-td expand>{{::saleTracking.created | date: 'dd/MM/yyyy HH:mm'}}</vn-td>
</vn-tr> </vn-tr>
</vn-tbody> </vn-tbody>
</vn-table> </vn-table>
</vn-card> </vn-card>
</vn-data-viewer> </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-worker-descriptor-popover
vn-id="worker-descriptor"> vn-id="worker-descriptor">
</vn-worker-descriptor-popover> </vn-worker-descriptor-popover>

View File

@ -45,14 +45,14 @@ class Controller extends Section {
btnThree: { btnThree: {
icon: 'icon-transaction', icon: 'icon-transaction',
state: `item.card.diary({ state: `item.card.diary({
id: ${sale.itemFk}, id: ${sale.item.id},
warehouseFk: ${this.ticket.warehouseFk}, warehouseFk: ${this.ticket.warehouseFk},
lineFk: ${sale.id} lineFk: ${sale.id}
})`, })`,
tooltip: 'Item diary' tooltip: 'Item diary'
} }
}; };
this.$.itemDescriptor.show(event.target, sale.itemFk); this.$.itemDescriptor.show(event.target, sale.item.id);
} }
chipHasSaleGroupDetail(hasSaleGroupDetail) { chipHasSaleGroupDetail(hasSaleGroupDetail) {

View File

@ -68,6 +68,7 @@
<vn-th number>Disc</vn-th> <vn-th number>Disc</vn-th>
<vn-th number>Amount</vn-th> <vn-th number>Amount</vn-th>
<vn-th shrink>Packaging</vn-th> <vn-th shrink>Packaging</vn-th>
<vn-th shrink></vn-th>
</vn-tr> </vn-tr>
</vn-thead> </vn-thead>
<vn-tbody> <vn-tbody>
@ -204,6 +205,22 @@
<vn-td shrink> <vn-td shrink>
{{::sale.item.itemPackingTypeFk | dashIfEmpty}} {{::sale.item.itemPackingTypeFk | dashIfEmpty}}
</vn-td> </vn-td>
<vn-td shrink>
<vn-icon-button
vn-none
vn-tooltip="History"
icon="history"
ng-click="log.open()">
</vn-icon-button>
<vn-instance-log
vn-id="log"
url="TicketLogs"
origin-id="$ctrl.$params.id"
changed-model="Sale"
changed-model-id="sale.id">
</vn-instance-log>
</vn-td>
</vn-tr> </vn-tr>
</vn-tbody> </vn-tbody>
</vn-table> </vn-table>
@ -441,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"

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