#5926 - Worker/PDA docuware #2482

Open
jsegarra wants to merge 40 commits from 5926_pda_worker_docuware into dev
26 changed files with 722 additions and 161 deletions

View File

@ -1,6 +1,67 @@
const axios = require('axios'); const axios = require('axios');
const env = process.env.NODE_ENV;
const {existsSync} = require('fs');
module.exports = Self => { module.exports = Self => {
/**
* Returns templateJSON
*
* @param {object} config - The path for real config file
* @return {boolean} - The template parse
*/
Self.hasDeviceReady = config => {
let isDeviceConfigured = false;
if (!config)
config = existsSync(`../docuware.${env}.json`) ?? {};
Review

Esto que es?

Esto que es?
Review

Es una funcionalidad que he hecho muy similar a print.development.json para que si existe algún archivo de configuración de tablet.
Esto es útil para hacer pruebas reales con la tablet 4 o con datos falsos

Es una funcionalidad que he hecho muy similar a print.development.json para que si existe algún archivo de configuración de tablet. Esto es útil para hacer pruebas reales con la tablet 4 o con datos falsos
isDeviceConfigured = !!config?.device;
return isDeviceConfigured;
};
/**
* Returns templateJSON
*
* @param {string} fields - The config as template upload
* @return {object} - The template parse
*/
Self.buildTemplateJSON = fields => {
const templateJson = {
'Fields': []
};
templateJson.Fields = Object.keys(fields).map(fieldName => ({
'FieldName': fieldName,
'ItemElementName': fields[fieldName].type,
'Item': fields[fieldName].value
}));
return templateJson;
};
/**
* Returns upload options
*
* @param {string} value - The document value
* @param {Object} template - The config as template upload
* @param {string} fileName - The document fileName with extension
* @param {object} options - The options
* @return {object} - The options with headers
*/
Self.uploadOptions = async(value, template, fileName = '', options) => {
const FormData = require('form-data');
const data = new FormData();
const docuwareOptions = options ?? await Self.getOptions();
const templateJson = Self.buildTemplateJSON(template);
data.append('document', JSON.stringify(templateJson), 'schema.json');
data.append('file[]', value, fileName);
const uploadOptions = {
headers: {
'Content-Type': 'multipart/form-data',
'X-File-ModifiedDate': Date.vnNew(),
'Cookie': docuwareOptions.headers.headers.Cookie,
...data.getHeaders()
},
};
return {data, uploadOptions};
};
/** /**
* Returns basic headers * Returns basic headers
* *
@ -23,6 +84,27 @@ module.exports = Self => {
}; };
}; };
/**
* Returns the docuware id
*
* @param {object} filter - The filter to use in findOne method
* @return {object} - The doware record
*/
Self.getDocuware = async filter => {
return await Self.app.models.Docuware.findOne(filter);
};
/**
* Returns the base url
*
* @param {object} options - The docuware options
* @param {string} fileCabinetId - The fileCabinetId to use in findOne method
* @param {string} model - The model to use in findOne method
* @return {string} - The doware record
*/
Self.baseURL = (options, fileCabinetId, model) => {
return `${options.url}/FileCabinets/${fileCabinetId}/${model}`;
};
/** /**
* Returns the dialog id * Returns the dialog id
* *
@ -32,10 +114,10 @@ module.exports = Self => {
* @return {number} - The fileCabinet id * @return {number} - The fileCabinet id
*/ */
Self.getDialog = async(code, action, fileCabinetId) => { Self.getDialog = async(code, action, fileCabinetId) => {
if (!process.env.NODE_ENV) if (!env && !Self.hasDeviceReady())
return Math.floor(Math.random() + 100); return Math.floor(Math.random() + 100);
const docuwareInfo = await Self.app.models.Docuware.findOne({ const docuwareInfo = await Self.getDocuware({
where: { where: {
code, code,
action action
@ -45,7 +127,7 @@ module.exports = Self => {
const options = await Self.getOptions(); const options = await Self.getOptions();
const response = await axios.get(`${options.url}/FileCabinets/${fileCabinetId}/dialogs`, options.headers); const response = await axios.get(Self.baseURL(options, fileCabinetId, 'dialog'), options.headers);
const dialogs = response.data.Dialog; const dialogs = response.data.Dialog;
const dialogId = dialogs.find(dialogs => dialogs.DisplayName === docuwareInfo.dialogName).Id; const dialogId = dialogs.find(dialogs => dialogs.DisplayName === docuwareInfo.dialogName).Id;
@ -59,7 +141,7 @@ module.exports = Self => {
* @return {number} - The fileCabinet id * @return {number} - The fileCabinet id
*/ */
Self.getFileCabinet = async code => { Self.getFileCabinet = async code => {
if (!process.env.NODE_ENV) if (!env && !Self.hasDeviceReady())
return Math.floor(Math.random() + 100); return Math.floor(Math.random() + 100);
const options = await Self.getOptions(); const options = await Self.getOptions();
@ -85,14 +167,14 @@ module.exports = Self => {
* @return {object} - The data * @return {object} - The data
*/ */
Self.get = async(code, filter, parse) => { Self.get = async(code, filter, parse) => {
if (!process.env.NODE_ENV) return; if (!env && !Self.hasDeviceReady()) return;
const options = await Self.getOptions(); const options = await Self.getOptions();
const fileCabinetId = await Self.getFileCabinet(code); const fileCabinetId = await Self.getFileCabinet(code);
const dialogId = await Self.getDialog(code, 'find', fileCabinetId); const dialogId = await Self.getDialog(code, 'find', fileCabinetId);
const data = await axios.post( const data = await axios.post(
`${options.url}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`, `${Self.baseURL(options, fileCabinetId)}/Query/DialogExpression?dialogId=${dialogId}`,
filter, filter,
options.headers options.headers
); );
@ -108,7 +190,7 @@ module.exports = Self => {
* @return {object} - The data * @return {object} - The data
*/ */
Self.getById = async(code, id, parse) => { Self.getById = async(code, id, parse) => {
if (!process.env.NODE_ENV) return; if (!env && !Self.hasDeviceReady()) return;
const docuwareInfo = await Self.app.models.Docuware.findOne({ const docuwareInfo = await Self.app.models.Docuware.findOne({
fields: ['findById'], fields: ['findById'],
@ -129,6 +211,26 @@ module.exports = Self => {
return Self.get(code, filter, parse); return Self.get(code, filter, parse);
}; };
/**
* Execute detete old docuware
*
* @param {string} id - The id
* @param {string} fileCabinet - The fieldCabinet
* @param {Object} template - The config
* @param {string} uri - The uri
* @param {Object} options - The options
*/
Self.deleteOld = async(id, fileCabinet, template, uri, options) => {
const docuwareOptions = options ?? await Self.getOptions();
template = template ?? {'ESTADO': {type: 'String', value: 'Pendiente eliminar'}};
const docuwareFile = await Self.checkFile(id, fileCabinet, false);
if (docuwareFile) {
const deleteJson = Self.buildTemplateJSON(template);
const deleteUri = `${uri}/${docuwareFile.id}/Fields`;
await axios.put(deleteUri, deleteJson, docuwareOptions.headers);
}
};
/** /**
* Returns docuware data filtered * Returns docuware data filtered
* *

View File

@ -4,7 +4,7 @@ const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethod('download', { Self.remoteMethod('download', {
description: 'Download an docuware PDF', description: 'Download a docuware PDF',
accessType: 'READ', accessType: 'READ',
accepts: [ accepts: [
{ {
@ -58,7 +58,7 @@ module.exports = Self => {
const fileName = `filename="${id}.pdf"`; const fileName = `filename="${id}.pdf"`;
const contentType = 'application/pdf'; const contentType = 'application/pdf';
const downloadUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents/${docuwareFile.id}/FileDownload?targetFileType=Auto&keepAnnotations=false`; const downloadUri = `${Self.baseURL(options, fileCabinetId)}Documents/${docuwareFile.id}/FileDownload?targetFileType=Auto&keepAnnotations=false`;
const stream = await axios.get(downloadUri, options.headers); const stream = await axios.get(downloadUri, options.headers);

View File

@ -1,9 +1,29 @@
const axios = require('axios'); const axios = require('axios');
const models = require('vn-loopback/server/server').models; const {Docuware} = require('vn-loopback/server/server').models;
describe('hasDeviceReady()', () => {
it('should return true', async() => {
const result = await Docuware.hasDeviceReady({device: 'Tablet 1'});
expect(result).toBeTrue();
});
it('should return false', async() => {
const result = await Docuware.hasDeviceReady({device: null});
expect(result).toBeFalse();
});
it('should not exists return false ', async() => {
const result = await Docuware.hasDeviceReady();
expect(result).toBeFalse();
});
});
describe('Docuware core', () => { describe('Docuware core', () => {
beforeAll(() => { beforeAll(() => {
process.env.NODE_ENV = 'testing'; process.env.NODE_ENV = 'testing';
spyOn(Docuware, 'hasDeviceReady').and.returnValue(true);
}); });
afterAll(() => { afterAll(() => {
@ -12,7 +32,7 @@ describe('Docuware core', () => {
describe('getOptions()', () => { describe('getOptions()', () => {
it('should return url and headers', async() => { it('should return url and headers', async() => {
const result = await models.Docuware.getOptions(); const result = await Docuware.getOptions();
expect(result.url).toBeDefined(); expect(result.url).toBeDefined();
expect(result.headers).toBeDefined(); expect(result.headers).toBeDefined();
@ -32,16 +52,90 @@ describe('Docuware core', () => {
} }
}; };
spyOn(axios, 'get').and.returnValue(new Promise(resolve => resolve(dialogs))); spyOn(axios, 'get').and.returnValue(new Promise(resolve => resolve(dialogs)));
const result = await models.Docuware.getDialog('deliveryNote', 'find', 'randomFileCabinetId'); const result = await Docuware.getDialog('deliveryNote', 'find', 'randomFileCabinetId');
expect(result).toEqual('getDialogTest'); expect(result).toEqual('getDialogTest');
}); });
}); });
describe('buildTemplateJSON()', () => {
it('should return buildTemplateJSON', async() => {
const config = {
'N__DOCUMENTO': {
type: 'string',
value: '12345'
},
'ESTADO': {
type: 'string',
value: 'Pendiente procesar'
},
'FIRMA_': {
type: 'string',
value: 'Si'
},
'FILTRO_TABLET': {
type: 'string',
value: 'Tablet123'
}
};
const result = await Docuware.buildTemplateJSON(config);
expect(result).toEqual({
'Fields': [
{
'FieldName': 'N__DOCUMENTO',
'ItemElementName': 'string',
'Item': '12345',
},
{
'FieldName': 'ESTADO',
'ItemElementName': 'string',
'Item': 'Pendiente procesar',
},
{
'FieldName': 'FIRMA_',
'ItemElementName': 'string',
'Item': 'Si',
},
{
'FieldName': 'FILTRO_TABLET',
'ItemElementName': 'string',
'Item': 'Tablet123',
}
]
});
});
});
describe('uploadOptions()', () => {
it('should return uploadOptions', async() => {
spyOn(Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
const {uploadOptions: result} = await Docuware.uploadOptions(1, {
'N__DOCUMENTO': {
type: 'string',
jsegarra marked this conversation as resolved
Review

Quitar?

Quitar?
value: '12345'
}}, 'test.pdf');
expect(result.headers.Cookie).toEqual(null);
expect(result.headers['Content-Type']).toEqual('multipart/form-data');
expect(result.headers['content-type']).toMatch(/^multipart\/form-data; boundary=/);
});
});
describe('deleteOld()', () => {
it('should return deleteOld', async() => {
await Docuware.deleteOld(1, 'deliveryNote', {
'N__DOCUMENTO': {
type: 'string',
value: '12345'
}});
});
});
describe('getFileCabinet()', () => { describe('getFileCabinet()', () => {
it('should return fileCabinetId', async() => { it('should return fileCabinetId', async() => {
const code = 'deliveryNote'; const code = 'deliveryNote';
const docuwareInfo = await models.Docuware.findOne({ const docuwareInfo = await Docuware.findOne({
where: { where: {
code code
} }
@ -57,7 +151,7 @@ describe('Docuware core', () => {
} }
}; };
spyOn(axios, 'get').and.returnValue(new Promise(resolve => resolve(dialogs))); spyOn(axios, 'get').and.returnValue(new Promise(resolve => resolve(dialogs)));
const result = await models.Docuware.getFileCabinet(code); const result = await Docuware.getFileCabinet(code);
expect(result).toEqual('getFileCabinetTest'); expect(result).toEqual('getFileCabinetTest');
}); });
@ -65,22 +159,22 @@ describe('Docuware core', () => {
describe('get()', () => { describe('get()', () => {
it('should return data without parse', async() => { it('should return data without parse', async() => {
spyOn(models.Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random())))); spyOn(Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
spyOn(models.Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random())))); spyOn(Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
const data = { const data = {
data: { data: {
id: 1 id: 1
} }
}; };
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data))); spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data)));
const result = await models.Docuware.get('deliveryNote'); const result = await Docuware.get('deliveryNote');
expect(result.id).toEqual(1); expect(result.id).toEqual(1);
}); });
it('should return data with parse', async() => { it('should return data with parse', async() => {
spyOn(models.Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random())))); spyOn(Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
spyOn(models.Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random())))); spyOn(Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
const data = { const data = {
data: { data: {
Items: [{ Items: [{
@ -109,7 +203,7 @@ describe('Docuware core', () => {
'secondRequiredField': 'name', 'secondRequiredField': 'name',
}; };
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data))); spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data)));
const [result] = await models.Docuware.get('deliveryNote', null, parse); const [result] = await Docuware.get('deliveryNote', null, parse);
expect(result.id).toEqual(1); expect(result.id).toEqual(1);
expect(result.name).toEqual('myName'); expect(result.name).toEqual('myName');
@ -119,15 +213,15 @@ describe('Docuware core', () => {
describe('getById()', () => { describe('getById()', () => {
it('should return data', async() => { it('should return data', async() => {
spyOn(models.Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random())))); spyOn(Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
spyOn(models.Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random())))); spyOn(Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
const data = { const data = {
data: { data: {
id: 1 id: 1
} }
}; };
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data))); spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data)));
const result = await models.Docuware.getById('deliveryNote', 1); const result = await Docuware.getById('deliveryNote', 1);
expect(result.id).toEqual(1); expect(result.id).toEqual(1);
}); });

View File

@ -1,10 +1,10 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
describe('docuware upload()', () => { describe('docuware upload()', () => {
const userId = 9; const userId = 18;
const ticketIds = [10]; const ids = [10];
const ctx = { const ctx = {
args: {ticketIds}, args: {ids},
req: { req: {
getLocale: () => { getLocale: () => {
return 'en'; return 'en';
@ -32,7 +32,7 @@ describe('docuware upload()', () => {
const options = {transaction: tx}; const options = {transaction: tx};
const user = await models.UserConfig.findById(userId, null, options); const user = await models.UserConfig.findById(userId, null, options);
await user.updateAttribute('tabletFk', 'Tablet1', options); await user.updateAttribute('tabletFk', 'Tablet1', options);
await models.Docuware.upload(ctx, ticketIds, fileCabinetName, options); await models.Docuware.upload(ctx, ids, fileCabinetName, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -50,7 +50,7 @@ describe('docuware upload()', () => {
let error; let error;
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
await models.Docuware.upload(ctx, ticketIds, fileCabinetName, options); await models.Docuware.upload(ctx, ids, fileCabinetName, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -1,5 +1,4 @@
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
const axios = require('axios');
const isProduction = require('vn-loopback/server/boot/isProduction'); const isProduction = require('vn-loopback/server/boot/isProduction');
module.exports = Self => { module.exports = Self => {
@ -8,9 +7,9 @@ module.exports = Self => {
accessType: 'WRITE', accessType: 'WRITE',
accepts: [ accepts: [
{ {
arg: 'ticketIds', arg: 'ids',
type: ['number'], type: ['number'],
description: 'The ticket ids', description: 'The ids',
required: true required: true
}, },
{ {
@ -30,8 +29,7 @@ module.exports = Self => {
} }
}); });
Self.upload = async function(ctx, ticketIds, fileCabinet, options) { Self.upload = async function(ctx, ids, fileCabinet, options) {
delete ctx.args.ticketIds;
const models = Self.app.models; const models = Self.app.models;
const action = 'store'; const action = 'store';
@ -40,125 +38,33 @@ module.exports = Self => {
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const userConfig = await models.UserConfig.findById(ctx.req.accessToken.userId, { const {tabletFk} = await models.UserConfig.findById(ctx.req.accessToken.userId, {
fields: ['tabletFk'] fields: ['tabletFk']
}, myOptions); }, myOptions);
if (!userConfig?.tabletFk) if (!tabletFk)
throw new UserError('This user does not have an assigned tablet'); throw new UserError('This user does not have an assigned tablet');
const docuwareOptions = await Self.getOptions(); const docuwareOptions = await Self.getOptions();
const docuware = await Self.getDocuware({where: {code: fileCabinet, action}});
const {modelFk} = docuware;
const modelIsValid = await Self.app.models.Module.findOne({where: {code: modelFk}});
if (!modelIsValid)
throw new UserError('This fileCabinet does not have an assigned module');
const model = modelFk.replace(/^.{1}/g, modelFk[0].toUpperCase());
const fileCabinetId = await Self.getFileCabinet(fileCabinet); const fileCabinetId = await Self.getFileCabinet(fileCabinet);
const dialogId = await Self.getDialog(fileCabinet, action, fileCabinetId); const dialogId = await Self.getDialog(fileCabinet, action, fileCabinetId);
const uploaded = []; const uri = Self.baseURL(docuwareOptions, fileCabinetId, 'Documents');
for (id of ticketIds) {
// get delivery note
ctx.args.id = id;
const deliveryNote = await models.Ticket.deliveryNotePdf(ctx, {
id,
type: 'deliveryNote'
}, myOptions);
// get ticket data
const ticket = await models.Ticket.findById(id, {
include: [{
relation: 'client',
scope: {
fields: ['id', 'name', 'fi']
}
}]
}, myOptions);
// upload file if (!isProduction(false) && !Self.hasDeviceReady())
const templateJson = { throw new UserError('Action not allowed on the test environment');
'Fields': [ const upload = {ctx, tabletFk, ids, myOptions, uri, fileCabinet, fileCabinetId, dialogId};
{ await Self.app.models[model].docuwareUpload(upload);
'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().name + ' - ' + id,
},
{
'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': userConfig.tabletFk,
}
]
};
if (!isProduction(false)) // return models.TicketTracking.setDelivered(ctx, ids, myOptions);
throw new UserError('Action not allowed on the test environment');
// delete old
const docuwareFile = await models.Docuware.checkFile(id, fileCabinet, false);
if (docuwareFile) {
const deleteJson = {
'Field': [{'FieldName': 'ESTADO', 'Item': 'Pendiente eliminar', 'ItemElementName': 'String'}]
};
const deleteUri = `${docuwareOptions.url}/FileCabinets/${fileCabinetId}/Documents/${docuwareFile.id}/Fields`;
await axios.put(deleteUri, deleteJson, docuwareOptions.headers);
}
const uploadUri = `${docuwareOptions.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': Date.vnNew(),
'Cookie': docuwareOptions.headers.headers.Cookie,
...data.getHeaders()
},
};
try {
await axios.post(uploadUri, data, uploadOptions);
} catch (err) {
const $t = ctx.req.__;
const message = $t('Failed to upload delivery note', {id});
if (uploaded.length)
await models.TicketTracking.setDelivered(ctx, uploaded, myOptions);
throw new UserError(message);
}
uploaded.push(id);
}
return models.TicketTracking.setDelivered(ctx, ticketIds, myOptions);
}; };
}; };

View File

@ -27,6 +27,9 @@
}, },
"findById": { "findById": {
"type": "string" "type": "string"
},
"modelFk": {
"type": "string"
} }
}, },
"relations": { "relations": {

View File

@ -2786,7 +2786,7 @@ INSERT INTO `bs`.`sale` (`saleFk`, `amount`, `dated`, `typeFk`, `clientFk`)
INSERT INTO `vn`.`docuwareConfig` (`id`, `url`) INSERT INTO `vn`.`docuwareConfig` (`id`, `url`)
VALUES VALUES
(1, 'http://docuware.url/'); (1, 'http://docuware.url');
INSERT INTO `vn`.`calendarHolidaysName` (`id`, `name`) INSERT INTO `vn`.`calendarHolidaysName` (`id`, `name`)
VALUES VALUES
@ -3141,6 +3141,11 @@ INSERT INTO `vn`.`docuwareTablet` (`tablet`,`description`)
('Tablet1','Jarvis tablet'), ('Tablet1','Jarvis tablet'),
('Tablet2','Avengers tablet'); ('Tablet2','Avengers tablet');
-- Auto-generated SQL script #202406060955
INSERT INTO `vn`.`docuwareTablet` (`tablet`,`description`,`fileCabinet`)
VALUES ('Tablet4','Docuware Tablet','RRHH');
INSERT INTO `vn`.`sms` (`id`, `senderFk`, `sender`, `destination`, `message`, `statusCode`, `status`, `created`) INSERT INTO `vn`.`sms` (`id`, `senderFk`, `sender`, `destination`, `message`, `statusCode`, `status`, `created`)
VALUES (1, 66, '111111111', '0001111111111', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 0, 'OK', util.VN_CURDATE()), VALUES (1, 66, '111111111', '0001111111111', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 0, 'OK', util.VN_CURDATE()),
(2, 66, '222222222', '0002222222222', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 0, 'PENDING', util.VN_CURDATE()), (2, 66, '222222222', '0002222222222', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 0, 'PENDING', util.VN_CURDATE()),
@ -3884,6 +3889,41 @@ INSERT INTO vn.trainingCourse (workerFk,trainingCourseTypeFk,centerFk,started,en
(9,1,2,'2018-06-20 00:00:00.000','2020-06-24 00:00:00.000',1,0), (9,1,2,'2018-06-20 00:00:00.000','2020-06-24 00:00:00.000',1,0),
(9,2,2,'2018-06-20 00:00:00.000','2020-06-24 00:00:00.000',1,1); (9,2,2,'2018-06-20 00:00:00.000','2020-06-24 00:00:00.000',1,1);
-- Auto-generated SQL script #202405201318
UPDATE vn.userConfig
SET tabletFk='Tablet4'
WHERE userFk=5;
-- Auto-generated SQL script #202405201207
UPDATE vn.docuware
SET modelFk='ticket'
WHERE id=1;
UPDATE vn.docuware
SET modelFk='ticket'
WHERE id=2;
UPDATE vn.docuware
SET modelFk='worker'
WHERE id=3;
-- Auto-generated SQL script #202405201318
UPDATE vn.userConfig
SET tabletFk='Tablet1'
WHERE userFk=9;
-- Auto-generated SQL script #202405201207
UPDATE vn.docuware
SET modelFk='ticket'
WHERE id=1;
UPDATE vn.docuware
SET modelFk='ticket'
WHERE id=2;
UPDATE vn.docuware
SET modelFk='worker'
WHERE id=3;
INSERT INTO vn.sectorCollection INSERT INTO vn.sectorCollection
SET id = 2, SET id = 2,
userFk = 18, userFk = 18,
@ -3953,21 +3993,22 @@ INSERT INTO dipole.expedition_PrintOut (expeditionFk, ticketFk, addressFk, stree
truckName, clientFk, phone, province, agency, m3, workerCode, itemFk, quantity, longName, shelvingFk, comments) truckName, clientFk, phone, province, agency, m3, workerCode, itemFk, quantity, longName, shelvingFk, comments)
VALUES(1, 1, 0, ' ', ' ', ' ', ' ', 0, '2001-01-01 00:00:00', 1, 0, ' ', ' ', 0, NULL, '', NULL, 0.000, NULL, 10, NULL, NULL, 'NCC', NULL); VALUES(1, 1, 0, ' ', ' ', ' ', ' ', 0, '2001-01-01 00:00:00', 1, 0, ' ', ' ', 0, NULL, '', NULL, 0.000, NULL, 10, NULL, NULL, 'NCC', NULL);
INSERT INTO vn.accountDetail INSERT INTO vn.accountDetail (id, value, accountDetailTypeFk, supplierAccountFk)
(id, value, accountDetailTypeFk, supplierAccountFk) VALUES
VALUES
(21, 'ES12345B12345678', 3, 241), (21, 'ES12345B12345678', 3, 241),
(35, 'ES12346B12345679', 3, 241); (35, 'ES12346B12345679', 3, 241);
INSERT INTO vn.accountDetailType INSERT INTO vn.accountDetailType (id, description, code)
(id, description, code) VALUES
VALUES (1, 'IBAN'),
(1, 'IBAN', 'iban'), (2, 'SWIFT'),
(2, 'SWIFT', 'swift'), (3, 'Referencia Remesas'),
(3, 'Referencia Remesas', 'remRef'), (4, 'Referencia Transferencias'),
(4, 'Referencia Transferencias', 'trnRef'), (5, 'Referencia Nominas'),
(5, 'Referencia Nominas', 'payRef'), (6, 'ABA');
(6, 'ABA', 'aba');
INSERT INTO account.userConfig (userFk,warehouseFk,companyFk,darkMode,tabletFk)
VALUES (37,1,442,1,'Tablet4');
INSERT IGNORE INTO ormConfig INSERT IGNORE INTO ormConfig
SET id =1, SET id =1,

View File

@ -0,0 +1,13 @@
ALTER TABLE vn.docuware ADD modelFk VARCHAR(45) DEFAULT NULL NULL;
ALTER TABLE vn.docuware ADD CONSTRAINT docuware_module_FK FOREIGN KEY (modelFk) REFERENCES salix.module(code);
ALTER TABLE vn.docuwareTablet ADD fileCabinet varchar(100) NULL;
INSERT IGNORE INTO vn.docuware (code,fileCabinetName,`action`,dialogName,findById,dmsTypeFk,modelFk)
VALUES ('hr','RRHH','store','Archivar','N__DOCUMENTO',3,'worker');
INSERT INTO salix.ACL (model,property,accessType,permission,principalType,principalId)
VALUES ('Worker','signPdaPdf','READ','ALLOW','ROLE','hr');
INSERT INTO salix.ACL (model,property,accessType,permission,principalType,principalId)
jsegarra marked this conversation as resolved
Review

quitar

quitar
VALUES ('Docuware','upload','WRITE','ALLOW','ROLE','hr');

View File

@ -0,0 +1,89 @@
const axios = require('axios');
const {models} = require('vn-loopback/server/server');
const UserError = require('vn-loopback/util/user-error');
const isProduction = require('vn-loopback/server/boot/isProduction');
module.exports = Self => {
Self.docuwareUpload = async({ctx, tabletFk, ids: ticketIds, myOptions, uri, dialogId}) => {
const type = 'deliveryNote';
for (id of ticketIds) {
// get delivery note
ctx.args.id = id;
const deliveryNote = await models.Ticket.deliveryNotePdf(ctx, {
id,
type
}, myOptions);
// get ticket data
const ticket = await models.Ticket.findById(id, {
include: [{
relation: 'client',
scope: {
fields: ['id', 'name', 'fi']
}
}]
}, myOptions);
// upload file
const configTemplate = {
'N__ALBAR_N': {
type: 'string',
value: id,
},
'CIF_PROVEEDOR': {
type: 'string',
value: ticket.client().fi,
},
'CODIGO_PROVEEDOR': {
type: 'string',
value: ticket.client().id,
},
'NOMBRE_PROVEEDOR': {
type: 'string',
value: ticket.client().name + ' - ' + id,
},
'FECHA_FACTURA': {
type: 'date',
value: ticket.shipped,
},
'TOTAL_FACTURA': {
type: 'Decimal',
value: ticket.totalWithVat,
},
'ESTADO': {
type: 'string',
value: 'Pendiente procesar',
},
'FIRMA_': {
type: 'string',
value: 'Si',
},
'FILTRO_TABLET': {
type: 'string',
value: tabletFk,
}
};
if (!isProduction(false) && !Self.hasDeviceReady())
throw new UserError('Action not allowed on the test environment');
// delete old
await models.Docuware.deleteOld(id, fileCabinet, uri);
const uploadUri = `${uri}?StoreDialogId=${dialogId}`;
const {data, uploadOptions} = models.Docuware.uploadOptions(deliveryNote[0], configTemplate, `${type}.pdf`);
try {
await axios.post(uploadUri, data, uploadOptions);
} catch (err) {
const $t = ctx.req.__;
const message = $t('Failed to upload delivery note', {id});
if (uploaded.length)
await models.TicketTracking.setDelivered(ctx, uploaded, myOptions);
throw new UserError(message);
}
uploaded.push(id);
}
};
};

View File

@ -45,5 +45,6 @@ module.exports = function(Self) {
require('../methods/ticket/invoiceTickets')(Self); require('../methods/ticket/invoiceTickets')(Self);
require('../methods/ticket/invoiceTicketsAndPdf')(Self); require('../methods/ticket/invoiceTicketsAndPdf')(Self);
require('../methods/ticket/docuwareDownload')(Self); require('../methods/ticket/docuwareDownload')(Self);
require('../methods/ticket/docuwareUpload')(Self);
require('../methods/ticket/myLastModified')(Self); require('../methods/ticket/myLastModified')(Self);
}; };

View File

@ -324,7 +324,7 @@ class Controller extends Section {
if (!force) if (!force)
return this.$.pdfToTablet.show(); return this.$.pdfToTablet.show();
return this.$http.post(`Docuwares/upload`, {fileCabinet: 'deliveryNote', ticketIds: [this.id]}) return this.$http.post(`Docuwares/upload`, {fileCabinet: 'deliveryNote', ids: [this.id]})
.then(() => { .then(() => {
this.vnApp.showSuccess(this.$t('PDF sent!')); this.vnApp.showSuccess(this.$t('PDF sent!'));
}); });

View File

@ -11,12 +11,12 @@ export default class Controller extends Section {
sendDocuware() { sendDocuware() {
const checkedTickets = this.checked; const checkedTickets = this.checked;
let ticketIds = []; let ids = [];
for (let ticket of checkedTickets) for (let ticket of checkedTickets)
ticketIds.push(ticket.id); ids.push(ticket.id);
return this.$http.post(`Docuwares/upload`, {fileCabinet: 'deliveryNote', ticketIds}) return this.$http.post(`Docuwares/upload`, {fileCabinet: 'deliveryNote', ids})
.then(res => { .then(res => {
let state = res.data; let state = res.data;
for (let ticket of checkedTickets) { for (let ticket of checkedTickets) {

View File

@ -0,0 +1,63 @@
const axios = require('axios');
const {models} = require('vn-loopback/server/server');
const UserError = require('vn-loopback/util/user-error');
const isProduction = require('vn-loopback/server/boot/isProduction');
module.exports = Self => {
Self.docuwareUpload = async({ctx, tabletFk, ids: id, myOptions, uri, fileCabinet, dialogId}) => {
const pdaId = id[0];
ctx.args.id = pdaId;
delete ctx.args.ids;
// upload file
const workerFk = await models.DeviceProductionUser.findOne(
{
fields: ['userFk'],
where: {deviceProductionFk: pdaId}
}
, myOptions);
const signPda = await models.Worker.signPdaPdf(ctx,
{pdaId, workerFk}
, myOptions);
const configTemplate = {
'N__DOCUMENTO': {
type: 'String',
value: pdaId
},
'ESTADO': {
type: 'String',
value: 'Pendiente procesar'
},
'FIRMA_': {
type: 'String',
value: 'Si'
},
'FILTRO_TABLET': {
type: 'String',
value: tabletFk
}
};
if (!isProduction(false) && !Self.hasDeviceReady())
throw new UserError('Action not allowed on the test environment');
// delete old
await models.Docuware.deleteOld(id, fileCabinet, uri);
const uploadUri = `${uri}?StoreDialogId=${dialogId}`;
const fileName = `assign${pdaId}Pda${workerFk}.pdf`;
const {data, uploadOptions} = await models.Docuware.uploadOptions(signPda[0], configTemplate, fileName);
try {
await axios.post(uploadUri, data, uploadOptions);
} catch (err) {
const $t = ctx.req.__;
const message = $t('Failed to upload delivery note', {id});
throw new UserError(message);
}
uploaded.push(id);
};
};

View File

@ -0,0 +1,44 @@
module.exports = Self => {
Self.remoteMethodCtx('signPdaPdf', {
description: 'Print pdf to sign PDA',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The pda id',
http: {source: 'path'}
},
{
arg: 'workerFk',
type: 'number',
required: true,
description: 'The worker 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/sign-pda-pdf',
verb: 'GET'
},
accessScopes: ['DEFAULT', 'read:multimedia']
});
Self.signPdaPdf = (ctx, id) => Self.printReport(ctx, id, 'sign-pda');
};

View File

@ -8,6 +8,7 @@ module.exports = Self => {
require('../methods/worker/createAbsence')(Self); require('../methods/worker/createAbsence')(Self);
require('../methods/worker/deleteAbsence')(Self); require('../methods/worker/deleteAbsence')(Self);
require('../methods/worker/updateAbsence')(Self); require('../methods/worker/updateAbsence')(Self);
require('../methods/worker/docuwareUpload')(Self);
require('../methods/worker/active')(Self); require('../methods/worker/active')(Self);
require('../methods/worker/activeWithRole')(Self); require('../methods/worker/activeWithRole')(Self);
require('../methods/worker/activeWithInheritedRole')(Self); require('../methods/worker/activeWithInheritedRole')(Self);
@ -17,6 +18,7 @@ module.exports = Self => {
require('../methods/worker/new')(Self); require('../methods/worker/new')(Self);
require('../methods/worker/deallocatePDA')(Self); require('../methods/worker/deallocatePDA')(Self);
require('../methods/worker/allocatePDA')(Self); require('../methods/worker/allocatePDA')(Self);
require('../methods/worker/signPdaPdf')(Self);
require('../methods/worker/search')(Self); require('../methods/worker/search')(Self);
require('../methods/worker/isAuthorized')(Self); require('../methods/worker/isAuthorized')(Self);
require('../methods/worker/setPassword')(Self); require('../methods/worker/setPassword')(Self);

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,25 @@
.description strong {
text-transform: uppercase;
}
h2 {
font-weight: 100;
color: #555
}
.column-oriented {
margin-bottom: 5px;
}
.report-info {
font-size: 20px
}
.row-oriented > tbody > tr > th {
padding-left: 30px;
width: 20%
}
.grid-block {
font-size: 1.2em
}

View File

@ -0,0 +1,18 @@
reportName: pda
signNote: Recepción PDA
date: Fecha
deviceRecieved: He recibido de Verdnatura Levante SL, el terminal *{modelFk}* con nº de serie {serialNumber} junto con su funda y protector de pantalla para el desarrollo de mi actividad profesional.
pdaModel: PDA Modelo
pdaSerie: Serie PDA
sim: sim vodafone
pin: pin
puk: puk
label1: El terminal es propiedad de la empresa y debe ser utilizado exclusivamente para las funciones y aplicaciones determinadas por la empresa.
label2: Me comprometo a conservar el terminal con su batería, tarjeta SIM, funda y protector de pantalla, y en caso de finalizar mi relación de servicios con la empresa, realizar la devolución en el plazo máximo de 24 horas.
label3: Si el terminal fuese robado, perdido o dañado, me comprometo a abonar el importe íntegro del terminal o su reparación en el centro oficial , así como la funda y el protector de pantalla.
label4: La instalación de aplicaciones no autorizadas por la empresa, puedan provocar o no un daño al terminal o a la empresa por vulneración de datos, virus, etc, no está autorizado, bajo ningún concepto, el uso de accesorios no originales.
label5: La empresa prohibe el uso de accesorios no originales.
label6: Mediante este documento me comprometo a usar la funda, y protector de pantalla en todo momento para minimizar daños en el terminal.
sign1: Recibe, D/Dña
sign2: DNI
sign: FIRMA

View File

@ -0,0 +1,18 @@
reportName: pda
signNote: Recepción PDA
date: Fecha
deviceRecieved: He recibido de Verdnatura Levante SL, el terminal *{modelFk}* con nº de serie {serialNumber} junto con su funda y protector de pantalla para el desarrollo de mi actividad profesional.
pdaModel: PDA Modelo
pdaSerie: Serie PDA
sim: sim vodafone
pin: pin
puk: puk
label1: El terminal es propiedad de la empresa y debe ser utilizado exclusivamente para las funciones y aplicaciones determinadas por la empresa.
label2: Me comprometo a conservar el terminal con su batería, tarjeta SIM, funda y protector de pantalla, y en caso de finalizar mi relación de servicios con la empresa, realizar la devolución en el plazo máximo de 24 horas.
label3: Si el terminal fuese robado, perdido o dañado, me comprometo a abonar el importe íntegro del terminal o su reparación en el centro oficial , así como la funda y el protector de pantalla.
label4: La instalación de aplicaciones no autorizadas por la empresa, puedan provocar o no un daño al terminal o a la empresa por vulneración de datos, virus, etc, no está autorizado, bajo ningún concepto, el uso de accesorios no originales.
label5: La empresa prohibe el uso de accesorios no originales.
label6: Mediante este documento me comprometo a usar la funda, y protector de pantalla en todo momento para minimizar daños en el terminal.
sign1: Recibe, D/Dña
sign2: DNI
sign: FIRMA

View File

@ -0,0 +1,18 @@
reportName: pda
signNote: Recepción PDA
date: Fecha
deviceRecieved: He recibido de Verdnatura Levante SL, el terminal *{modelFk}* con nº de serie {serialNumber} junto con su funda y protector de pantalla para el desarrollo de mi actividad profesional.
pdaModel: PDA Modelo
pdaSerie: Serie PDA
sim: sim vodafone
pin: pin
puk: puk
label1: El terminal es propiedad de la empresa y debe ser utilizado exclusivamente para las funciones y aplicaciones determinadas por la empresa.
label2: Me comprometo a conservar el terminal con su batería, tarjeta SIM, funda y protector de pantalla, y en caso de finalizar mi relación de servicios con la empresa, realizar la devolución en el plazo máximo de 24 horas.
label3: Si el terminal fuese robado, perdido o dañado, me comprometo a abonar el importe íntegro del terminal o su reparación en el centro oficial , así como la funda y el protector de pantalla.
label4: La instalación de aplicaciones no autorizadas por la empresa, puedan provocar o no un daño al terminal o a la empresa por vulneración de datos, virus, etc, no está autorizado, bajo ningún concepto, el uso de accesorios no originales.
label5: La empresa prohibe el uso de accesorios no originales.
label6: Mediante este documento me comprometo a usar la funda, y protector de pantalla en todo momento para minimizar daños en el terminal.
sign1: Recibe, D/Dña
sign2: DNI
sign: FIRMA

View File

@ -0,0 +1,18 @@
reportName: pda
signNote: Recepción PDA
date: Fecha
deviceRecieved: He recibido de Verdnatura Levante SL, el terminal *{modelFk}* con nº de serie {serialNumber} junto con su funda y protector de pantalla para el desarrollo de mi actividad profesional.
pdaModel: PDA Modelo
pdaSerie: Serie PDA
sim: sim vodafone
pin: pin
puk: puk
label1: El terminal es propiedad de la empresa y debe ser utilizado exclusivamente para las funciones y aplicaciones determinadas por la empresa.
label2: Me comprometo a conservar el terminal con su batería, tarjeta SIM, funda y protector de pantalla, y en caso de finalizar mi relación de servicios con la empresa, realizar la devolución en el plazo máximo de 24 horas.
label3: Si el terminal fuese robado, perdido o dañado, me comprometo a abonar el importe íntegro del terminal o su reparación en el centro oficial , así como la funda y el protector de pantalla.
label4: La instalación de aplicaciones no autorizadas por la empresa, puedan provocar o no un daño al terminal o a la empresa por vulneración de datos, virus, etc, no está autorizado, bajo ningún concepto, el uso de accesorios no originales.
label5: La empresa prohibe el uso de accesorios no originales.
label6: Mediante este documento me comprometo a usar la funda, y protector de pantalla en todo momento para minimizar daños en el terminal.
sign1: Recibe, D/Dña
sign2: DNI
sign: FIRMA

View File

@ -0,0 +1,18 @@
reportName: pda
signNote: Recepción PDA
date: Fecha
deviceRecieved: He recibido de Verdnatura Levante SL, el terminal *{modelFk}* con nº de serie {serialNumber} junto con su funda y protector de pantalla para el desarrollo de mi actividad profesional.
pdaModel: PDA Modelo
pdaSerie: Serie PDA
sim: sim vodafone
pin: pin
puk: puk
label1: El terminal es propiedad de la empresa y debe ser utilizado exclusivamente para las funciones y aplicaciones determinadas por la empresa.
label2: Me comprometo a conservar el terminal con su batería, tarjeta SIM, funda y protector de pantalla, y en caso de finalizar mi relación de servicios con la empresa, realizar la devolución en el plazo máximo de 24 horas.
label3: Si el terminal fuese robado, perdido o dañado, me comprometo a abonar el importe íntegro del terminal o su reparación en el centro oficial , así como la funda y el protector de pantalla.
label4: La instalación de aplicaciones no autorizadas por la empresa, puedan provocar o no un daño al terminal o a la empresa por vulneración de datos, virus, etc, no está autorizado, bajo ningún concepto, el uso de accesorios no originales.
label5: La empresa prohibe el uso de accesorios no originales.
label6: Mediante este documento me comprometo a usar la funda, y protector de pantalla en todo momento para minimizar daños en el terminal.
sign1: Recibe, D/Dña
sign2: DNI
sign: FIRMA

View File

@ -0,0 +1,48 @@
<report-body v-bind="$props">
<template v-slot:header>
<report-header v-bind="$props"> </report-header>
</template>
<div class="grid-row">
<div class="grid-block">
<div class="columns">
<div class="body">
<span>{{$t('date')}}:{{formatDate(new Date(), '%d-%m-%Y')}}</span>
<p>
{{$t('deviceRecieved',{modelFk: device.modelFk, serialNumber: device.serialNumber})}}:
</p>
<p>{{$t('label1')}}
</p>
<p>
{{$t('label2')}}
</p>
<p>
{{$t('label3')}}
</p>
<p>
{{$t('label4')}}
</p>
<p>
{{$t('label5')}}
</p>
<p>
{{$t('label6')}}
</p>
<p>
{{$t('sign1')}} {{worker.firstName}} {{ worker.lastName}}
</p>
<p>
{{$t('sign2')}} {{worker.fi}}
</p>
<p>
{{$t('sign')}}
</p>
</div>
</div>
</div>
</div>
<template v-slot:footer>
<report-footer id="pageFooter" v-bind="$props"> </report-footer>
</template>
</report-body>

View File

@ -0,0 +1,26 @@
const vnReport = require('../../../core/mixins/vn-report.js');
module.exports = {
name: 'sign-pda',
mixins: [vnReport],
async serverPrefetch() {
this.device = await this.findOneFromDef('device', [this.id]);
this.worker = await this.findOneFromDef('worker', [this.workerFk]);
},
props: {
id: {
type: Number,
required: true,
description: 'The device id'
},
workerFk: {
type: Number,
required: true,
description: 'The worker id'
},
type: {
type: String,
required: false
}
}
};

View File

@ -0,0 +1 @@
SELECT * FROM vn.deviceProduction WHERE id = ?

View File

@ -0,0 +1 @@
SELECT * FROM vn.worker WHERE id = ?