Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 5525-ibanSEPA-CORE

This commit is contained in:
Carlos Satorres 2023-07-25 14:41:11 +02:00
commit b2702d8567
30 changed files with 684 additions and 250 deletions

View File

@ -1,7 +1,5 @@
const axios = require('axios');
module.exports = Self => {
Self.remoteMethodCtx('checkFile', {
Self.remoteMethod('checkFile', {
description: 'Check if exist docuware file',
accessType: 'READ',
accepts: [
@ -17,12 +15,16 @@ module.exports = Self => {
required: true,
description: 'The fileCabinet name'
},
{
arg: 'filter',
type: 'object',
description: 'The filter'
},
{
arg: 'signed',
type: 'boolean',
required: true,
description: 'If pdf is necessary to be signed'
}
},
],
returns: {
type: 'object',
@ -34,7 +36,7 @@ module.exports = Self => {
}
});
Self.checkFile = async function(ctx, id, fileCabinet, signed) {
Self.checkFile = async function(id, fileCabinet, filter, signed) {
const models = Self.app.models;
const action = 'find';
@ -45,39 +47,34 @@ module.exports = Self => {
}
});
const searchFilter = {
condition: [
{
DBName: docuwareInfo.findById,
Value: [id]
}
],
sortOrder: [
{
Field: 'FILENAME',
Direction: 'Desc'
}
]
};
if (!filter) {
filter = {
condition: [
{
DBName: docuwareInfo.findById,
Value: [id]
}
],
sortOrder: [
{
Field: 'FILENAME',
Direction: 'Desc'
}
]
};
}
if (signed) {
filter.condition.push({
DBName: 'ESTADO',
Value: ['Firmado']
});
}
try {
const options = await Self.getOptions();
const fileCabinetId = await Self.getFileCabinet(fileCabinet);
const dialogId = await Self.getDialog(fileCabinet, action, fileCabinetId);
const response = await axios.post(
`${options.url}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`,
searchFilter,
options.headers
);
const [documents] = response.data.Items;
const response = await Self.get(fileCabinet, filter);
const [documents] = response.Items;
if (!documents) return false;
const state = documents.Fields.find(field => field.FieldName == 'ESTADO');
if (signed && state.Item != 'Firmado') return false;
return {id: documents.Id};
} catch (error) {
return false;

View File

@ -1,59 +1,6 @@
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
*
@ -75,4 +22,139 @@ module.exports = Self => {
headers
};
};
/**
* 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) => {
if (!process.env.NODE_ENV)
return Math.floor(Math.random() + 100);
const docuwareInfo = await Self.app.models.Docuware.findOne({
where: {
code,
action
}
});
if (!fileCabinetId) fileCabinetId = await Self.getFileCabinet(code);
const options = await Self.getOptions();
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 => {
if (!process.env.NODE_ENV)
return Math.floor(Math.random() + 100);
const options = await Self.getOptions();
const docuwareInfo = await Self.app.models.Docuware.findOne({
where: {
code
}
});
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 docuware data
*
* @param {string} code - The fileCabinet code
* @param {object} filter - The filter for docuware
* @param {object} parse - The fields parsed
* @return {object} - The data
*/
Self.get = async(code, filter, parse) => {
if (!process.env.NODE_ENV) return;
const options = await Self.getOptions();
const fileCabinetId = await Self.getFileCabinet(code);
const dialogId = await Self.getDialog(code, 'find', fileCabinetId);
const data = await axios.post(
`${options.url}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`,
filter,
options.headers
);
return parser(data.data, parse);
};
/**
* Returns docuware data
*
* @param {string} code - The fileCabinet code
* @param {any} id - The id of docuware
* @param {object} parse - The fields parsed
* @return {object} - The data
*/
Self.getById = async(code, id, parse) => {
if (!process.env.NODE_ENV) return;
const docuwareInfo = await Self.app.models.Docuware.findOne({
fields: ['findById'],
where: {
code,
action: 'find'
}
});
const filter = {
condition: [
{
DBName: docuwareInfo.findById,
Value: [id]
}
]
};
return Self.get(code, filter, parse);
};
/**
* Returns docuware data filtered
*
* @param {array} data - The data
* @param {object} parse - The fields parsed
* @return {object} - The data parsed
*/
function parser(data, parse) {
if (!(data && data.Items)) return data;
const parsed = [];
for (item of data.Items) {
const itemParsed = {};
item.Fields.map(field => {
if (field.ItemElementName.includes('Date')) field.Item = toDate(field.Item);
if (!parse) return itemParsed[field.FieldLabel] = field.Item;
if (parse[field.FieldLabel])
itemParsed[parse[field.FieldLabel]] = field.Item;
});
parsed.push(itemParsed);
}
return parsed;
}
function toDate(value) {
if (!value) return;
return new Date(Number(value.substring(6, 19)));
}
};

View File

@ -3,7 +3,7 @@ const axios = require('axios');
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('download', {
Self.remoteMethod('download', {
description: 'Download an docuware PDF',
accessType: 'READ',
accepts: [
@ -16,8 +16,12 @@ module.exports = Self => {
{
arg: 'fileCabinet',
type: 'string',
description: 'The file cabinet',
http: {source: 'path'}
description: 'The file cabinet'
},
{
arg: 'filter',
type: 'object',
description: 'The filter'
}
],
returns: [
@ -36,14 +40,15 @@ module.exports = Self => {
}
],
http: {
path: `/:id/download/:fileCabinet`,
path: `/:id/download`,
verb: 'GET'
}
});
Self.download = async function(ctx, id, fileCabinet) {
Self.download = async function(id, fileCabinet, filter) {
const models = Self.app.models;
const docuwareFile = await models.Docuware.checkFile(ctx, id, fileCabinet, true);
const docuwareFile = await models.Docuware.checkFile(id, fileCabinet, filter);
if (!docuwareFile) throw new UserError('The DOCUWARE PDF document does not exists');
const fileCabinetId = await Self.getFileCabinet(fileCabinet);

View File

@ -1,57 +1,15 @@
const models = require('vn-loopback/server/server').models;
const axios = require('axios');
describe('docuware download()', () => {
const ticketId = 1;
const userId = 9;
const ctx = {
req: {
accessToken: {userId: userId},
headers: {origin: 'http://localhost:5000'},
}
};
const docuwareModel = models.Docuware;
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 return false if there are no documents', async() => {
const response = {
data: {
Items: []
}
};
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response)));
spyOn(docuwareModel, 'get').and.returnValue((new Promise(resolve => resolve({Items: []}))));
const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true);
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);
const result = await models.Docuware.checkFile(ticketId, fileCabinetName, null, true);
expect(result).toEqual(false);
});
@ -59,23 +17,21 @@ describe('docuware download()', () => {
it('should return the document data', async() => {
const docuwareId = 1;
const response = {
data: {
Items: [
{
Id: docuwareId,
Fields: [
{
FieldName: 'ESTADO',
Item: 'Firmado'
}
]
}
]
}
Items: [
{
Id: docuwareId,
Fields: [
{
FieldName: 'ESTADO',
Item: 'Firmado'
}
]
}
]
};
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response)));
spyOn(docuwareModel, 'get').and.returnValue((new Promise(resolve => resolve(response))));
const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true);
const result = await models.Docuware.checkFile(ticketId, fileCabinetName, null, true);
expect(result.id).toEqual(docuwareId);
});

View File

@ -0,0 +1,135 @@
const axios = require('axios');
const models = require('vn-loopback/server/server').models;
describe('Docuware core', () => {
beforeAll(() => {
process.env.NODE_ENV = 'testing';
});
afterAll(() => {
delete process.env.NODE_ENV;
});
describe('getOptions()', () => {
it('should return url and headers', async() => {
const result = await models.Docuware.getOptions();
expect(result.url).toBeDefined();
expect(result.headers).toBeDefined();
});
});
describe('getDialog()', () => {
it('should return dialogId', async() => {
const dialogs = {
data: {
Dialog: [
{
DisplayName: 'find',
Id: 'getDialogTest'
}
]
}
};
spyOn(axios, 'get').and.returnValue(new Promise(resolve => resolve(dialogs)));
const result = await models.Docuware.getDialog('deliveryNote', 'find', 'randomFileCabinetId');
expect(result).toEqual('getDialogTest');
});
});
describe('getFileCabinet()', () => {
it('should return fileCabinetId', async() => {
const code = 'deliveryNote';
const docuwareInfo = await models.Docuware.findOne({
where: {
code
}
});
const dialogs = {
data: {
FileCabinet: [
{
Name: docuwareInfo.fileCabinetName,
Id: 'getFileCabinetTest'
}
]
}
};
spyOn(axios, 'get').and.returnValue(new Promise(resolve => resolve(dialogs)));
const result = await models.Docuware.getFileCabinet(code);
expect(result).toEqual('getFileCabinetTest');
});
});
describe('get()', () => {
it('should return data without parse', async() => {
spyOn(models.Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
spyOn(models.Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
const data = {
data: {
id: 1
}
};
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data)));
const result = await models.Docuware.get('deliveryNote');
expect(result.id).toEqual(1);
});
it('should return data with parse', async() => {
spyOn(models.Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
spyOn(models.Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
const data = {
data: {
Items: [{
Fields: [
{
ItemElementName: 'integer',
FieldLabel: 'firstRequiredField',
Item: 1
},
{
ItemElementName: 'string',
FieldLabel: 'secondRequiredField',
Item: 'myName'
},
{
ItemElementName: 'integer',
FieldLabel: 'notRequiredField',
Item: 2
}
]
}]
}
};
const parse = {
'firstRequiredField': 'id',
'secondRequiredField': 'name',
};
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data)));
const [result] = await models.Docuware.get('deliveryNote', null, parse);
expect(result.id).toEqual(1);
expect(result.name).toEqual('myName');
expect(result.notRequiredField).not.toBeDefined();
});
});
describe('getById()', () => {
it('should return data', async() => {
spyOn(models.Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
spyOn(models.Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
const data = {
data: {
id: 1
}
};
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data)));
const result = await models.Docuware.getById('deliveryNote', 1);
expect(result.id).toEqual(1);
});
});
});

View File

@ -39,7 +39,7 @@ describe('docuware download()', () => {
spyOn(docuwareModel, 'checkFile').and.returnValue({});
spyOn(axios, 'get').and.returnValue(new stream.PassThrough({objectMode: true}));
const result = await models.Docuware.download(ctx, ticketId, fileCabinetName);
const result = await models.Docuware.download(ticketId, fileCabinetName);
expect(result[1]).toEqual('application/pdf');
expect(result[2]).toEqual(`filename="${ticketId}.pdf"`);

View File

@ -18,6 +18,9 @@
},
"expired": {
"type": "date"
},
"supplierAccountFk": {
"type": "number"
}
},
"scope": {

View File

@ -28,5 +28,12 @@
"findById": {
"type": "string"
}
},
"relations": {
"dmsType": {
"type": "belongsTo",
"model": "DmsType",
"foreignKey": "dmsTypeFk"
}
}
}

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`company` MODIFY COLUMN sage200Company int(2) DEFAULT 10 NOT NULL;

View File

@ -0,0 +1,2 @@
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalId`)
VALUES ('Ticket','transferClient','WRITE','ALLOW','ROLE','administrative');

View File

@ -0,0 +1,10 @@
ALTER TABLE `vn`.`docuware` ADD dmsTypeFk INT(11) DEFAULT NULL NULL;
ALTER TABLE `vn`.`docuware` ADD CONSTRAINT docuware_FK FOREIGN KEY (dmsTypeFk) REFERENCES `vn`.`dmsType`(id) ON DELETE RESTRICT ON UPDATE CASCADE;
INSERT INTO `vn`.`docuware` (code, fileCabinetName, `action`, dialogName, findById, dmsTypeFk)
VALUES
('hr', 'RRHH', 'find', 'Búsqueda', 'N__DOCUMENTO', NULL); -- set dmsTypeFk 3 when deploy in production
INSERT INTO `salix`.`url` (appName, environment, url)
VALUES
('docuware', 'production', 'https://verdnatura.docuware.cloud/DocuWare/Platform/');

View File

@ -66,7 +66,11 @@ export default class App {
return this.logger.$http.get('Urls/findOne', {filter})
.then(res => {
return res.data.url + route;
if (res && res.data)
return res.data.url + route;
})
.catch(() => {
this.showError('Direction not found');
});
}
}

View File

@ -3,4 +3,5 @@ Could not contact the server: No se ha podido contactar con el servidor, asegura
Please enter your username: Por favor introduce tu nombre de usuario
It seems that the server has fall down: Parece que el servidor se ha caído, espera unos minutos e inténtalo de nuevo
Session has expired: Tu sesión ha expirado, por favor vuelve a iniciar sesión
Access denied: Acción no permitida
Access denied: Acción no permitida
Direction not found: Dirección no encontrada

View File

@ -305,6 +305,7 @@
"The renew period has not been exceeded": "El periodo de renovación no ha sido superado",
"Valid priorities": "Prioridades válidas: %d",
"Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}",
"The company has not informed the supplier account for bank transfers": "La empresa no tiene informado la cuenta de proveedor para transferencias bancarias",
"You cannot assign/remove an alias that you are not assigned to": "No puede asignar/eliminar un alias que no tenga asignado",
"This invoice has a linked vehicle.": "Esta factura tiene un vehiculo vinculado"
}

View File

@ -1,3 +1,5 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = function(Self) {
Self.remoteMethodCtx('canBeInvoiced', {
description: 'Change property isEqualizated in all client addresses',
@ -9,6 +11,12 @@ module.exports = function(Self) {
required: true,
description: 'Client id',
http: {source: 'path'}
},
{
arg: 'companyFk',
description: 'The company id',
type: 'number',
required: true
}
],
returns: {
@ -22,18 +30,29 @@ module.exports = function(Self) {
}
});
Self.canBeInvoiced = async(id, options) => {
Self.canBeInvoiced = async(id, companyFk, options) => {
const models = Self.app.models;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const client = await models.Client.findById(id, {
fields: ['id', 'isTaxDataChecked', 'hasToInvoice']
fields: ['id', 'isTaxDataChecked', 'hasToInvoice', 'payMethodFk'],
include:
{
relation: 'payMethod',
scope: {
fields: ['code']
}
}
}, myOptions);
const company = await models.Company.findById(companyFk, {fields: ['supplierAccountFk']}, myOptions);
if (client.payMethod().code === 'wireTransfer' && !company.supplierAccountFk)
throw new UserError('The company has not informed the supplier account for bank transfers');
if (client.isTaxDataChecked && client.hasToInvoice)
return true;

View File

@ -4,6 +4,7 @@ const LoopBackContext = require('loopback-context');
describe('client canBeInvoiced()', () => {
const userId = 19;
const clientId = 1101;
const companyId = 442;
const activeCtx = {
accessToken: {userId: userId}
};
@ -23,7 +24,7 @@ describe('client canBeInvoiced()', () => {
const client = await models.Client.findById(clientId, null, options);
await client.updateAttribute('isTaxDataChecked', false, options);
const canBeInvoiced = await models.Client.canBeInvoiced(clientId, options);
const canBeInvoiced = await models.Client.canBeInvoiced(clientId, companyId, options);
expect(canBeInvoiced).toEqual(false);
@ -43,7 +44,7 @@ describe('client canBeInvoiced()', () => {
const client = await models.Client.findById(clientId, null, options);
await client.updateAttribute('hasToInvoice', false, options);
const canBeInvoiced = await models.Client.canBeInvoiced(clientId, options);
const canBeInvoiced = await models.Client.canBeInvoiced(clientId, companyId, options);
expect(canBeInvoiced).toEqual(false);
@ -60,7 +61,7 @@ describe('client canBeInvoiced()', () => {
try {
const options = {transaction: tx};
const canBeInvoiced = await models.Client.canBeInvoiced(clientId, options);
const canBeInvoiced = await models.Client.canBeInvoiced(clientId, companyId, options);
expect(canBeInvoiced).toEqual(true);

View File

@ -0,0 +1,55 @@
module.exports = Self => {
Self.remoteMethod('docuwareDownload', {
description: 'Download a ticket delivery note document',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'Number',
description: 'The document 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/docuwareDownload`,
verb: 'GET'
}
});
Self.docuwareDownload = async id => {
const filter = {
condition: [
{
DBName: docuwareInfo.findById,
Value: [id]
},
{
DBName: 'ESTADO',
Value: ['Firmado']
}
],
sortOrder: [
{
Field: 'FILENAME',
Direction: 'Desc'
}
]
};
return Self.app.models.Docuware.download(id, 'deliveryNote', filter);
};
};

View File

@ -14,7 +14,7 @@ module.exports = function(Self) {
{
arg: 'companyFk',
description: 'The company id',
type: 'string',
type: 'number',
required: true
},
{
@ -67,7 +67,7 @@ module.exports = function(Self) {
const [firstTicket] = tickets;
const clientId = firstTicket.clientFk;
const clientCanBeInvoiced = await models.Client.canBeInvoiced(clientId, myOptions);
const clientCanBeInvoiced = await models.Client.canBeInvoiced(clientId, companyFk, myOptions);
if (!clientCanBeInvoiced)
throw new UserError(`This client can't be invoiced`);

View File

@ -0,0 +1,49 @@
const models = require('vn-loopback/server/server').models;
describe('Ticket transferClient()', () => {
const userId = 9;
const activeCtx = {
accessToken: {userId: userId},
};
const ctx = {req: activeCtx};
it('should throw an error as the ticket is not editable', async() => {
const tx = await models.Ticket.beginTransaction({});
let error;
try {
const options = {transaction: tx};
const ticketId = 4;
const clientId = 1;
await models.Ticket.transferClient(ctx, ticketId, clientId, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error.message).toEqual(`The current ticket can't be modified`);
});
it('should be assigned a different clientFk', async() => {
const tx = await models.Ticket.beginTransaction({});
let updatedTicket;
const ticketId = 10;
const clientId = 1;
try {
const options = {transaction: tx};
await models.Ticket.transferClient(ctx, ticketId, clientId, options);
updatedTicket = await models.Ticket.findById(ticketId, {fields: ['clientFk']}, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
expect(updatedTicket.clientFk).toEqual(clientId);
});
});

View File

@ -0,0 +1,49 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('transferClient', {
description: 'Transfering ticket to another client',
accessType: 'WRITE',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'the ticket id',
http: {source: 'path'}
},
{
arg: 'clientFk',
type: 'number',
required: true,
},
],
http: {
path: `/:id/transferClient`,
verb: 'PATCH'
}
});
Self.transferClient = async(ctx, id, clientFk, options) => {
const models = Self.app.models;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const isEditable = await Self.isEditable(ctx, id, myOptions);
if (!isEditable)
throw new UserError(`The current ticket can't be modified`);
const ticket = await models.Ticket.findById(
id,
{fields: ['id', 'shipped', 'clientFk', 'addressFk']},
myOptions
);
const client = await models.Client.findById(clientFk, {fields: ['id', 'defaultAddressFk']}, myOptions);
await ticket.updateAttributes({
clientFk,
addressFk: client.defaultAddressFk,
});
};
};

View File

@ -19,6 +19,7 @@ module.exports = function(Self) {
require('../methods/ticket/uploadFile')(Self);
require('../methods/ticket/addSale')(Self);
require('../methods/ticket/transferSales')(Self);
require('../methods/ticket/transferClient')(Self);
require('../methods/ticket/recalculateComponents')(Self);
require('../methods/ticket/sendSms')(Self);
require('../methods/ticket/isLocked')(Self);

View File

@ -36,13 +36,12 @@
translate>
as PDF without prices
</vn-item>
<a class="vn-item"
<vn-item
ng-if="$ctrl.hasDocuwareFile"
href='api/Docuwares/{{$ctrl.ticket.id}}/download/deliveryNote?access_token={{$ctrl.vnToken.token}}'
target="_blank"
ng-click="$ctrl.docuwareDownload()"
translate>
as PDF signed
</a>
</vn-item>
<vn-item
ng-click="$ctrl.showCsvDeliveryNote()"
translate>

View File

@ -96,20 +96,14 @@ class Controller extends Section {
}
transferClient() {
this.$http.get(`Clients/${this.ticket.client.id}`).then(client => {
const ticket = this.ticket;
const ticket = this.ticket;
const clientFk = ticket.client.id;
const params =
{
clientFk: client.data.id,
addressFk: client.data.defaultAddressFk,
};
this.$http.patch(`Tickets/${ticket.id}`, params).then(() => {
this.$http.patch(`Tickets/${ticket.id}/transferClient`, {clientFk})
.then(() => {
this.vnApp.showSuccess(this.$t('Data saved!'));
this.reload();
});
});
}
isTicketEditable() {
@ -327,6 +321,10 @@ class Controller extends Section {
});
}
docuwareDownload() {
this.vnFile.download(`api/Ticket/${this.ticket.id}/docuwareDownload`);
}
setTicketWeight(weight) {
return this.$http.patch(`Tickets/${this.ticket.id}`, {weight})
.then(() => {

View File

@ -326,17 +326,4 @@ describe('Ticket Component vnTicketDescriptorMenu', () => {
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
describe('transferClient()', () => {
it(`should perform two queries, a get to obtain the clientData and a patch to update the ticket`, () => {
const client =
{
clientFk: 1101,
addressFk: 1,
};
$httpBackend.expect('GET', `Clients/${ticket.client.id}`).respond(client);
$httpBackend.expect('PATCH', `Tickets/${ticket.id}`).respond();
controller.transferClient();
});
});
});

View File

@ -0,0 +1,45 @@
module.exports = Self => {
Self.remoteMethod('docuwareDownload', {
description: 'Download a worker document',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'Number',
description: 'The document 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/docuwareDownload`,
verb: 'GET'
}
});
Self.docuwareDownload = async id => {
const filter = {
condition: [
{
DBName: 'FILENAME',
Value: [id]
}
]
};
return Self.app.models.Docuware.download(id, 'hr', filter);
};
};

View File

@ -5,6 +5,12 @@ module.exports = Self => {
description: 'Find all instances of the model matched by filter from the data source.',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'Number',
description: 'The worker id',
http: {source: 'path'}
},
{
arg: 'filter',
type: 'Object',
@ -17,16 +23,17 @@ module.exports = Self => {
root: true
},
http: {
path: `/filter`,
path: `/:id/filter`,
verb: 'GET'
}
});
Self.filter = async(ctx, filter) => {
Self.filter = async(ctx, id, filter) => {
const conn = Self.dataSource.connector;
const userId = ctx.req.accessToken.userId;
const models = Self.app.models;
const account = await Self.app.models.VnUser.findById(userId);
const account = await models.VnUser.findById(userId);
const stmt = new ParameterizedSQL(
`SELECT d.id dmsFk, d.reference, d.description, d.file, d.created, d.hardCopyNumber, d.hasFile
FROM workerDocument wd
@ -47,7 +54,38 @@ module.exports = Self => {
}]
}, oldWhere]};
stmt.merge(conn.makeSuffix(filter));
const workerDms = await conn.executeStmt(stmt);
return await conn.executeStmt(stmt);
// Get docuware info
const docuware = await models.Docuware.findOne({
fields: ['dmsTypeFk'],
where: {code: 'hr', action: 'find'}
});
const docuwareDmsType = docuware.dmsTypeFk;
let workerDocuware = [];
if (!docuwareDmsType || (docuwareDmsType && await models.DmsType.hasReadRole(ctx, docuwareDmsType))) {
const worker = await models.Worker.findById(id, {fields: ['fi', 'firstName', 'lastName']});
const docuwareParse = {
'Filename': 'dmsFk',
'Tipo Documento': 'description',
'Stored on': 'created',
'Document ID': 'id'
};
workerDocuware =
await models.Docuware.getById('hr', worker.lastName + worker.firstName, docuwareParse) ?? [];
for (document of workerDocuware) {
const defaultData = {
file: 'dw' + document.id + '.png',
isDocuware: true,
hardCopyNumber: null,
hasFile: false,
reference: worker.fi,
dmsFk: 'DW' + document.id
};
document = Object.assign(document, defaultData);
}
}
return workerDms.concat(workerDocuware);
};
};

View File

@ -2,6 +2,7 @@ module.exports = Self => {
require('../methods/worker-dms/downloadFile')(Self);
require('../methods/worker-dms/removeFile')(Self);
require('../methods/worker-dms/filter')(Self);
require('../methods/worker-dms/docuwareDownload')(Self);
Self.isMine = async function(ctx, dmsId) {
const myUserId = ctx.req.accessToken.userId;

View File

@ -1,6 +1,6 @@
<vn-crud-model
vn-id="model"
url="WorkerDms/filter"
url="WorkerDms/{{$ctrl.$params.id}}/filter"
link="{worker: $ctrl.$params.id}"
limit="20"
data="$ctrl.workerDms"
@ -46,14 +46,14 @@
</span>
</vn-td>
<vn-td shrink>
<vn-check
<vn-check
ng-model="document.hasFile"
disabled="true">
</vn-check>
</vn-td>
<vn-td shrink>
<span title="{{'Download file' | translate}}" class="link"
ng-click="$ctrl.downloadFile(document.dmsFk)">
ng-click="$ctrl.downloadFile(document.dmsFk, document.isDocuware)">
{{::document.file}}
</span>
</vn-td>
@ -63,16 +63,14 @@
<vn-td shrink>
<vn-icon-button title="{{'Download file' | translate}}"
icon="cloud_download"
ng-click="$ctrl.downloadFile(document.dmsFk)">
ng-click="$ctrl.downloadFile(document.dmsFk, document.isDocuware)">
</vn-icon-button>
</vn-td>
<vn-td shrink>
<vn-td expand ng-if="::!document.isDocuware">
<vn-icon-button ui-sref="worker.card.dms.edit({dmsId: {{::document.dmsFk}}})"
icon="edit"
title="{{'Edit file' | translate}}">
</vn-icon-button>
</vn-td>
<vn-td shrink>
<vn-icon-button
icon="delete"
ng-click="confirm.show($index)"
@ -80,6 +78,14 @@
tabindex="-1">
</vn-icon-button>
</vn-td>
<vn-td expand ng-if="::document.isDocuware">
<vn-icon-button
icon="open_in_new"
ng-click="$ctrl.openDocuware()"
title="{{'Open in docuware' | translate}}"
tabindex="-1">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
@ -91,9 +97,9 @@
fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>
</a>
<vn-confirm
<vn-confirm
vn-id="confirm"
message="This file will be deleted"
question="Are you sure you want to continue?"
on-accept="$ctrl.deleteDms($data)">
</vn-confirm>
</vn-confirm>

View File

@ -17,9 +17,15 @@ class Controller extends Component {
});
}
downloadFile(dmsId) {
downloadFile(dmsId, isDocuware) {
if (isDocuware) return this.vnFile.download(`api/workerDms/${dmsId}/docuwareDownload`);
this.vnFile.download(`api/workerDms/${dmsId}/downloadFile`);
}
async openDocuware() {
const url = await this.vnApp.getUrl(`WebClient`, 'docuware');
if (url) window.open(url).focus();
}
}
Controller.$inject = ['$element', '$scope', 'vnFile'];

View File

@ -1,31 +1,19 @@
SELECT
SELECT
io.ref,
c.socialName,
sa.iban,
pm.name AS payMethod,
t.clientFk,
t.shipped,
t.nickname,
s.ticketFk,
s.itemFk,
s.concept,
s.quantity,
s.price,
ib.ediBotanic botanical,
s.quantity,
s.price,
s.discount,
i.tag5,
i.value5,
i.tag6,
i.value6,
i.tag7,
i.value7,
tc.code AS vatType,
ib.ediBotanic botanical
s.itemFk,
s.concept,
tc.code vatType
FROM vn.invoiceOut io
JOIN vn.ticket t ON t.refFk = io.ref
JOIN vn.supplier su ON su.id = io.companyFk
JOIN vn.client c ON c.id = t.clientFk
JOIN vn.client c ON c.id = t.clientFk
JOIN vn.payMethod pm ON pm.id = c.payMethodFk
JOIN vn.company co ON co.id = io.companyFk
JOIN vn.company co ON co.id = io.companyFk
JOIN vn.supplierAccount sa ON sa.id = co.supplierAccountFk
JOIN vn.sale s ON s.ticketFk = t.id
JOIN item i ON i.id = s.itemFk
@ -38,35 +26,23 @@ SELECT
AND itc.itemFk = s.itemFk
JOIN vn.taxClass tc ON tc.id = itc.taxClassFk
WHERE t.refFk = ?
UNION ALL
SELECT
UNION ALL
SELECT
io.ref,
c.socialName,
sa.iban,
pm.name AS payMethod,
t.clientFk,
t.shipped,
t.nickname,
t.id AS ticketFk,
t.id ticketFk,
NULL botanical,
ts.quantity,
ts.price,
0 discount,
'',
ts.description concept,
ts.quantity,
ts.price,
0 discount,
NULL AS tag5,
NULL AS value5,
NULL AS tag6,
NULL AS value6,
NULL AS tag7,
NULL AS value7,
tc.code AS vatType,
NULL AS botanical
ts.description concept,
tc.code vatType
FROM vn.invoiceOut io
JOIN vn.ticket t ON t.refFk = io.ref
JOIN vn.ticketService ts ON ts.ticketFk = t.id
JOIN vn.client c ON c.id = t.clientFk
JOIN vn.client c ON c.id = t.clientFk
JOIN vn.payMethod pm ON pm.id = c.payMethodFk
JOIN vn.company co ON co.id = io.companyFk
JOIN vn.company co ON co.id = io.companyFk
JOIN vn.supplierAccount sa ON sa.id = co.supplierAccountFk
JOIN vn.taxClass tc ON tc.id = ts.taxClassFk
WHERE t.refFk = ?
WHERE t.refFk = ?