4658-createWorker #1236

Merged
alexm merged 35 commits from 4658-createWorker into dev 2023-01-23 07:30:52 +00:00
89 changed files with 1604 additions and 4100 deletions
Showing only changes of commit a674169ddb - Show all commits

View File

@ -5,18 +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
- [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

@ -36,19 +36,26 @@ 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 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 WHERE ot.isanswered
AND ots.state = ? AND ots.id IN (?)
AND IF(sub.updated > sub.created, sub.updated, sub.created) < DATE_SUB(CURDATE(), INTERVAL ? DAY)`; AND sub.type = 'R'
AND IF(sub.updated > sub.created, sub.updated, sub.created) < DATE_SUB(CURDATE(), INTERVAL ? DAY);`;
const ticketsId = []; const ticketsId = [];
const statusIdToClose = config.oldStatus.split(',');
con.connect(err => { con.connect(err => {
if (err) throw err; if (err) throw err;
con.query(sql, [config.oldStatus, config.day], con.query(sql, [statusIdToClose, config.day],
(err, results) => { (err, results) => {
if (err) throw err; if (err) throw err;
for (const result of results) for (const result of results)

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

@ -0,0 +1,3 @@
UPDATE `vn`.`collection`
SET sectorFk=1
WHERE id=1;

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

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
@ -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
@ -2745,7 +2741,7 @@ 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

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

@ -418,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"]',
@ -680,7 +680,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'

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

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

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

@ -19,9 +19,9 @@ class Controller extends ModuleCard {
}, { }, {
relation: 'ticket', relation: 'ticket',
scope: { scope: {
fields: ['agencyModeFk'], fields: ['zoneFk'],
include: { include: {
relation: 'agencyMode' relation: 'zone'
} }
} }
}, { }, {

View File

@ -51,8 +51,12 @@
</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.zoneFk}}
</span>
</vn-label-value> </vn-label-value>
<vn-label-value <vn-label-value
label="Ticket"> label="Ticket">
@ -103,3 +107,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

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

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

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

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,6 +62,7 @@ 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 && mdbApp.locked && mdbApp.userFk != userId) { if (mdbApp && mdbApp.locked && mdbApp.userFk != userId) {
@ -71,6 +71,19 @@ module.exports = Self => {
})); }));
} }
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

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

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

View File

@ -39,3 +39,4 @@ Packaging: Encajado
Refund: Abono Refund: Abono
Promotion mana: Maná promoción Promotion mana: Maná promoción
Claim mana: Maná reclamación Claim mana: Maná reclamación
History: Historial

View File

@ -32,22 +32,15 @@ module.exports = Self => {
const models = Self.app.models; const models = Self.app.models;
const conn = Self.dataSource.connector; const conn = Self.dataSource.connector;
const args = ctx.args; const args = ctx.args;
const $t = ctx.req.__; // $translate
let tx; let tx;
const myOptions = {}; const myOptions = {};
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
const stmts = []; const stmts = [];
let stmt; let stmt;
try {
if (!args.week || !args.year) { if (!args.week || !args.year) {
const from = new Date(); const from = new Date();
const to = new Date(); const to = new Date();
@ -156,6 +149,9 @@ module.exports = Self => {
`, [args.workerId]); `, [args.workerId]);
const index = stmts.push(stmt) - 1; const index = stmts.push(stmt) - 1;
stmts.push('DROP TEMPORARY TABLE tmp.timeControlCalculate');
stmts.push('DROP TEMPORARY TABLE tmp.timeBusinessCalculate');
const sql = ParameterizedSQL.join(stmts, ';'); const sql = ParameterizedSQL.join(stmts, ';');
const days = await conn.executeStmt(sql, myOptions); const days = await conn.executeStmt(sql, myOptions);
@ -165,14 +161,19 @@ module.exports = Self => {
const workerTimeControlConfig = await models.WorkerTimeControlConfig.findOne(null, myOptions); const workerTimeControlConfig = await models.WorkerTimeControlConfig.findOne(null, myOptions);
for (let day of days[index]) { for (let day of days[index]) {
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
workerFk = day.workerFk; workerFk = day.workerFk;
if (day.timeWorkDecimal > 0 && day.timeWorkedDecimal == null if (day.timeWorkDecimal > 0 && day.timeWorkedDecimal == null
&& (day.permissionRate ? day.permissionRate : true)) { && (day.permissionRate == null ? true : day.permissionRate)) {
if (day.timeTable == null) { if (day.timeTable == null) {
const timed = new Date(day.dated); const timed = new Date(day.dated);
await models.WorkerTimeControl.create({ await models.WorkerTimeControl.create({
userFk: day.workerFk, userFk: day.workerFk,
timed: timed.setHours(8), timed: timed.setHours(workerTimeControlConfig.teleworkingStart / 3600),
manual: true, manual: true,
direction: 'in', direction: 'in',
isSendMail: true isSendMail: true
@ -181,7 +182,7 @@ module.exports = Self => {
if (day.timeWorkDecimal >= workerTimeControlConfig.timeToBreakTime / 3600) { if (day.timeWorkDecimal >= workerTimeControlConfig.timeToBreakTime / 3600) {
await models.WorkerTimeControl.create({ await models.WorkerTimeControl.create({
userFk: day.workerFk, userFk: day.workerFk,
timed: timed.setHours(9), timed: timed.setHours(workerTimeControlConfig.teleworkingStartBreakTime / 3600),
manual: true, manual: true,
direction: 'middle', direction: 'middle',
isSendMail: true isSendMail: true
@ -189,7 +190,10 @@ module.exports = Self => {
await models.WorkerTimeControl.create({ await models.WorkerTimeControl.create({
userFk: day.workerFk, userFk: day.workerFk,
timed: timed.setHours(9, 20), timed: timed.setHours(
workerTimeControlConfig.teleworkingStartBreakTime / 3600,
workerTimeControlConfig.breakTime / 60
),
manual: true, manual: true,
direction: 'middle', direction: 'middle',
isSendMail: true isSendMail: true
@ -199,7 +203,11 @@ module.exports = Self => {
const [hoursWork, minutesWork, secondsWork] = getTime(day.timeWorkSexagesimal); const [hoursWork, minutesWork, secondsWork] = getTime(day.timeWorkSexagesimal);
await models.WorkerTimeControl.create({ await models.WorkerTimeControl.create({
userFk: day.workerFk, userFk: day.workerFk,
timed: timed.setHours(8 + hoursWork, minutesWork, secondsWork), timed: timed.setHours(
workerTimeControlConfig.teleworkingStart / 3600 + hoursWork,
minutesWork,
secondsWork
),
manual: true, manual: true,
direction: 'out', direction: 'out',
isSendMail: true isSendMail: true
@ -307,7 +315,7 @@ module.exports = Self => {
}, myOptions); }, myOptions);
if (firstWorkerTimeControl) if (firstWorkerTimeControl)
firstWorkerTimeControl.updateAttribute('direction', 'in', myOptions); await firstWorkerTimeControl.updateAttribute('direction', 'in', myOptions);
const lastWorkerTimeControl = await models.WorkerTimeControl.findOne({ const lastWorkerTimeControl = await models.WorkerTimeControl.findOne({
where: { where: {
@ -318,7 +326,7 @@ module.exports = Self => {
}, myOptions); }, myOptions);
if (lastWorkerTimeControl) if (lastWorkerTimeControl)
lastWorkerTimeControl.updateAttribute('direction', 'out', myOptions); await lastWorkerTimeControl.updateAttribute('direction', 'out', myOptions);
} }
} }
@ -339,15 +347,18 @@ module.exports = Self => {
previousWorkerFk = day.workerFk; previousWorkerFk = day.workerFk;
previousReceiver = day.receiver; previousReceiver = day.receiver;
} }
if (tx) {
await tx.commit();
delete myOptions.transaction;
} }
if (tx) await tx.commit();
return true;
} catch (e) { } catch (e) {
if (tx) await tx.rollback(); if (tx) await tx.rollback();
throw e; throw e;
} }
}
return true;
}; };
function getStartDateOfWeekNumber(week, year) { function getStartDateOfWeekNumber(week, year) {

View File

@ -10,7 +10,6 @@ describe('workerTimeControl sendMail()', () => {
const ctx = {req: activeCtx, args: {}}; const ctx = {req: activeCtx, args: {}};
it('should fill time control of a worker without records in Journey and with rest', async() => { it('should fill time control of a worker without records in Journey and with rest', async() => {
pending('https://redmine.verdnatura.es/issues/4903');
const tx = await models.WorkerTimeControl.beginTransaction({}); const tx = await models.WorkerTimeControl.beginTransaction({});
try { try {
@ -35,7 +34,6 @@ describe('workerTimeControl sendMail()', () => {
}); });
it('should fill time control of a worker without records in Journey and without rest', async() => { it('should fill time control of a worker without records in Journey and without rest', async() => {
pending('https://redmine.verdnatura.es/issues/4903');
const workdayOf20Hours = 3; const workdayOf20Hours = 3;
const tx = await models.WorkerTimeControl.beginTransaction({}); const tx = await models.WorkerTimeControl.beginTransaction({});
@ -63,7 +61,6 @@ describe('workerTimeControl sendMail()', () => {
}); });
it('should fill time control of a worker with records in Journey and with rest', async() => { it('should fill time control of a worker with records in Journey and with rest', async() => {
pending('https://redmine.verdnatura.es/issues/4903');
const tx = await models.WorkerTimeControl.beginTransaction({}); const tx = await models.WorkerTimeControl.beginTransaction({});
try { try {
@ -95,7 +92,6 @@ describe('workerTimeControl sendMail()', () => {
}); });
it('should fill time control of a worker with records in Journey and without rest', async() => { it('should fill time control of a worker with records in Journey and without rest', async() => {
pending('https://redmine.verdnatura.es/issues/4903');
const tx = await models.WorkerTimeControl.beginTransaction({}); const tx = await models.WorkerTimeControl.beginTransaction({});
try { try {

View File

@ -2,7 +2,7 @@ const {Email} = require('vn-print');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('weeklyHourRecordEmail', { Self.remoteMethodCtx('weeklyHourRecordEmail', {
description: 'Sends the buyer waste email', description: 'Sends the weekly hour record',
accessType: 'WRITE', accessType: 'WRITE',
accepts: [ accepts: [
{ {

View File

@ -11,8 +11,17 @@
"id": true, "id": true,
"type": "number" "type": "number"
}, },
"breakTime": {
"type": "number"
},
"timeToBreakTime": { "timeToBreakTime": {
"type": "number" "type": "number"
},
"teleworkingStart": {
"type": "number"
},
"teleworkingStartBreakTime": {
"type": "number"
} }
} }
} }

28
package-lock.json generated
View File

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

View File

@ -1,6 +1,6 @@
{ {
"name": "salix-back", "name": "salix-back",
"version": "9.0.0", "version": "230401",
"author": "Verdnatura Levante SL", "author": "Verdnatura Levante SL",
"description": "Salix backend", "description": "Salix backend",
"license": "GPL-3.0", "license": "GPL-3.0",
@ -12,7 +12,7 @@
"node": ">=14" "node": ">=14"
}, },
"dependencies": { "dependencies": {
"axios": "^0.25.0", "axios": "^1.2.2",
"bcrypt": "^5.0.1", "bcrypt": "^5.0.1",
"bmp-js": "^0.1.0", "bmp-js": "^0.1.0",
"compression": "^1.7.3", "compression": "^1.7.3",

3588
print/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
subject: Your agricultural invoice subject: Your agricultural receipt
title: Your agricultural invoice title: Your agricultural receipt
dear: Dear supplier dear: Dear supplier
description: Attached you can find agricultural receipt generated from your last deliveries. Please return a signed and stamped copy to our administration department. description: Attached you can find agricultural receipt generated from your last deliveries. Please return a signed and stamped copy to our administration department.
conclusion: Thanks for your attention! conclusion: Thanks for your attention!

View File

@ -1,5 +1,5 @@
subject: Tu factura agrícola subject: Tu recibo agrícola
title: Tu factura agrícola title: Tu recibo agrícola
dear: Estimado proveedor dear: Estimado proveedor
description: Adjunto puede encontrar recibo agrícola generado de sus últimas entregas. Por favor, devuelva una copia firmada y sellada a nuestro de departamento de administración. description: Adjunto puede encontrar recibo agrícola generado de sus últimas entregas. Por favor, devuelva una copia firmada y sellada a nuestro de departamento de administración.
conclusion: ¡Gracias por tu atención! conclusion: ¡Gracias por tu atención!

View File

@ -1,5 +1,5 @@
subject: Votre facture agricole subject: Votre reçu agricole
title: Votre facture agricole title: Votre reçu agricole
dear: Cher Fournisseur dear: Cher Fournisseur
description: Vous trouverez en pièce jointe le reçu agricole généré à partir de vos dernières livraisons. Veuillez retourner une copie signée et tamponnée à notre service administratif. description: Vous trouverez en pièce jointe le reçu agricole généré à partir de vos dernières livraisons. Veuillez retourner une copie signée et tamponnée à notre service administratif.
conclusion: Merci pour votre attention! conclusion: Merci pour votre attention!

View File

@ -1,5 +1,5 @@
subject: A sua fatura agrícola subject: A sua recibo agrícola
title: A sua fatura agrícola title: A sua recibo agrícola
dear: Caro Fornecedor dear: Caro Fornecedor
description: Em anexo encontra-se o recibo agrícola gerado a partir das suas últimas entregas. Por favor, devolva uma cópia assinada e carimbada ao nosso departamento de administração. description: Em anexo encontra-se o recibo agrícola gerado a partir das suas últimas entregas. Por favor, devolva uma cópia assinada e carimbada ao nosso departamento de administração.
conclusion: Obrigado pela atenção. conclusion: Obrigado pela atenção.

View File

@ -3,9 +3,12 @@ SELECT
i.name, i.name,
i.stems, i.stems,
i.size, i.size,
b.packing b.packing,
p.name as 'producer'
FROM vn.item i FROM vn.item i
JOIN cache.last_buy clb ON clb.item_id = i.id JOIN cache.last_buy clb ON clb.item_id = i.id
JOIN vn.buy b ON b.id = clb.buy_id JOIN vn.buy b ON b.id = clb.buy_id
JOIN vn.entry e ON e.id = b.entryFk JOIN vn.entry e ON e.id = b.entryFk
JOIN vn.producer p ON p.id = i.producerFk
WHERE i.id = ? AND clb.warehouse_id = ? WHERE i.id = ? AND clb.warehouse_id = ?

View File

@ -0,0 +1,12 @@
const Stylesheet = require(`vn-print/core/stylesheet`);
const path = require('path');
const vnPrintPath = path.resolve('print');
module.exports = new Stylesheet([
`${vnPrintPath}/common/css/spacing.css`,
`${vnPrintPath}/common/css/misc.css`,
`${vnPrintPath}/common/css/layout.css`,
`${vnPrintPath}/common/css/report.css`,
`${__dirname}/style.css`])
.mergeStyles();

View File

@ -0,0 +1,85 @@
* {
box-sizing: border-box;
padding-right: 1%;
}
.label {
font-size: 1.2em;
font-family: Arial, Helvetica, sans-serif;
}
.barcode {
float: left;
width: 40%;
}
.barcode h1 {
text-align: center;
font-size: 1.8em;
margin: 0 0 10px 0
}
.barcode .image {
text-align: center
}
.barcode .image img {
width: 170px
}
.data {
float: left;
width: 60%;
}
.data .header {
background-color: #000;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
margin-bottom: 25px;
text-align: right;
font-size: 1.2em;
padding: 0.2em;
color: #FFF
}
.data .sector,
.data .producer {
text-transform: uppercase;
text-align: right;
font-size: 1.5em;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.data .sector-sm {
text-transform: uppercase;
text-align: right;
font-size: 1.2em;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.data .producer {
text-justify: inter-character;
}
.data .details {
border-top: 4px solid #000;
padding-top: 2px;
}
.data .details .package {
padding-right: 5px;
float: left;
width: 50%;
}
.package .packing,
.package .dated,
.package .labelNumber {
text-align: right
}
.package .packing {
font-size: 1.8em;
font-weight: 400
}
.data .details .size {
background-color: #000;
text-align: center;
font-size: 3em;
padding: 0.2em 0;
float: left;
width: 50%;
color: #FFF
}

View File

@ -0,0 +1,2 @@
previous: PREVIOUS
report: Report

View File

@ -0,0 +1,2 @@
previous: PREVIA
report: Ticket

View File

@ -0,0 +1,11 @@
{
"width": "10.4cm",
"height": "4.8cm",
"margin": {
"top": "0cm",
"right": "0cm",
"bottom": "0cm",
"left": "0cm"
},
"printBackground": true
}

View File

@ -0,0 +1,26 @@
<DOCTYPE html>
<body>
<div class="label">
<div class="barcode">
<h1>{{previa.saleGroupFk}}</h1>
<div class="image">
<img v-bind:src="barcode" />
</div>
</div>
<div class="data">
<div class="header">{{ $t('previous') }}</div>
<div v-if="sector.description.length > 16" class="sector-sm">
{{sector.description}}
</div>
<div v-else class="sector">{{sector.description}}</div>
<div class="producer">{{ $t('report') }}#{{previa.ticketFk}}</div>
<div class="details">
<div class="package">
<div class="packing">{{previa.itemPackingTypeFk}}</div>
<div class="dated">{{previa.shippingHour}}:{{previa.shippingMinute}}</div>
</div>
<div class="size">{{previa.items}}</div>
</div>
</div>
</div>
</body>

View File

@ -0,0 +1,42 @@
const Component = require(`vn-print/core/component`);
const reportBody = new Component('report-body');
const qrcode = require('qrcode');
const UserError = require('vn-loopback/util/user-error');
module.exports = {
name: 'previa-label',
async serverPrefetch() {
this.previa = await this.fetchPrevia(this.id);
this.sector = await this.fetchSector(this.id);
this.barcode = await this.getBarcodeBase64(this.id);
if (this.previa)
this.previa = this.previa[0];
if (!this.sector)
throw new UserError('Something went wrong - no sector found');
},
methods: {
fetchPrevia(id) {
return this.findOneFromDef('previa', [id]);
},
getBarcodeBase64(id) {
const data = String(id);
return qrcode.toDataURL(data, {margin: 0});
},
fetchSector(id) {
return this.findOneFromDef('sector', [id]);
}
},
components: {
'report-body': reportBody.build()
},
props: {
id: {
type: Number,
required: true,
description: 'The saleGroupFk id'
},
}
};

View File

@ -0,0 +1 @@
CALL vn.previousSticker_get(?)

View File

@ -0,0 +1,4 @@
SELECT s.description
FROM vn.saleGroup sg
JOIN vn.sector s ON sg.sectorFk = s.id
WHERE sg.id = ?