dev #1731

Merged
jgallego merged 105 commits from dev into test 2023-08-31 09:13:29 +00:00
171 changed files with 3185 additions and 1051 deletions
Showing only changes of commit dd1dbcbd4a - Show all commits

View File

@ -5,7 +5,8 @@ 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).
## [2330.01] - 2023-07-27
## [2336.01] - 2023-09-07
### Added ### Added
@ -14,6 +15,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed ### Fixed
## [2334.01] - 2023-08-24
### Added
- (General -> Errores) Botón para enviar cau con los datos del error
## [2332.01] - 2023-08-10
### Added
- (Trabajadores -> Gestión documental) Soporte para Docuware
- (General -> Agencia) Soporte para Viaexpress
- (Tickets -> SMS) Nueva sección en Lilium
### Changed
- (General -> Tickets) Devuelve el motivo por el cual no es editable
- (Desplegables -> Trabajadores) Mejorados
- (General -> Clientes) Razón social y dirección en mayúsculas
### Fixed
- (Clientes -> SMS) Al pasar el ratón por encima muestra el mensaje completo
## [2330.01] - 2023-07-27 ## [2330.01] - 2023-07-27
### Added ### Added
@ -24,10 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- (General -> Iconos) Añadidos nuevos iconos - (General -> Iconos) Añadidos nuevos iconos
- (Clientes -> Razón social) Nuevas restricciones por pais - (Clientes -> Razón social) Permite crear clientes con la misma razón social según el país
### Fixed
## [2328.01] - 2023-07-13 ## [2328.01] - 2023-07-13

View File

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

View File

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

View File

@ -1,57 +1,15 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const axios = require('axios');
describe('docuware download()', () => { describe('docuware download()', () => {
const ticketId = 1; const ticketId = 1;
const userId = 9;
const ctx = {
req: {
accessToken: {userId: userId},
headers: {origin: 'http://localhost:5000'},
}
};
const docuwareModel = models.Docuware; const docuwareModel = models.Docuware;
const fileCabinetName = 'deliveryNote'; 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() => { it('should return false if there are no documents', async() => {
const response = { spyOn(docuwareModel, 'get').and.returnValue((new Promise(resolve => resolve({Items: []}))));
data: {
Items: []
}
};
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);
});
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); expect(result).toEqual(false);
}); });
@ -59,23 +17,21 @@ describe('docuware download()', () => {
it('should return the document data', async() => { it('should return the document data', async() => {
const docuwareId = 1; const docuwareId = 1;
const response = { const response = {
data: { Items: [
Items: [ {
{ Id: docuwareId,
Id: docuwareId, Fields: [
Fields: [ {
{ FieldName: 'ESTADO',
FieldName: 'ESTADO', Item: 'Firmado'
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); 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(docuwareModel, 'checkFile').and.returnValue({});
spyOn(axios, 'get').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); const result = await models.Docuware.download(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,63 @@
const smtp = require('vn-print/core/smtp');
const config = require('vn-print/core/config');
module.exports = Self => {
Self.remoteMethodCtx('sendToSupport', {
description: 'Send mail to support',
accessType: 'WRITE',
accepts: [
{
arg: 'reason',
type: 'string',
description: 'The reason'
},
{
arg: 'additionalData',
type: 'object',
required: true,
description: 'The additional data'
}
],
returns: {
type: 'object',
root: true
},
http: {
path: `/send-to-support`,
verb: 'POST'
}
});
Self.sendToSupport = async(ctx, reason, additionalData) => {
const emailUser =
await Self.app.models.EmailUser.findById(ctx.req.accessToken.userId, {fields: ['email']});
let html = `<strong>Motivo</strong>:<br/>${reason}<br/>`;
for (const data in additionalData)
html += `<strong>${data}</strong>:<br/>${tryParse(additionalData[data])}<br/>`;
const subjectReason = JSON.parse(additionalData?.httpRequest)?.data?.error;
smtp.send({
to: config.app.reportEmail,
replyTo: emailUser.email,
subject:
'[Support-Salix] ' +
additionalData?.frontPath + ' ' +
subjectReason?.name + ':' +
subjectReason?.message,
html
});
};
function tryParse(value) {
try {
try {
value = JSON.parse(value);
} catch {}
return JSON.stringify(value, null, '&nbsp;').split('\n').join('<br>');
} catch {
return value;
}
}
};

View File

@ -0,0 +1,45 @@
const axios = require('axios');
const {DOMParser} = require('xmldom');
module.exports = Self => {
Self.remoteMethod('internationalExpedition', {
description: 'Create an expedition and return a label',
accessType: 'WRITE',
accepts: [{
arg: 'expeditionFk',
type: 'number',
required: true
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/internationalExpedition`,
verb: 'POST'
}
});
Self.internationalExpedition = async expeditionFk => {
const models = Self.app.models;
const viaexpressConfig = await models.ViaexpressConfig.findOne({
fields: ['url']
});
const renderedXml = await models.ViaexpressConfig.renderer(expeditionFk);
const response = await axios.post(`${viaexpressConfig.url}ServicioVxClientes.asmx`, renderedXml, {
headers: {
'Content-Type': 'application/soap+xml; charset=utf-8'
}
});
const xmlString = response.data;
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlString, 'text/xml');
const referenciaVxElement = xmlDoc.getElementsByTagName('ReferenciaVx')[0];
const referenciaVx = referenciaVxElement.textContent;
return referenciaVx;
};
};

View File

@ -0,0 +1,126 @@
const fs = require('fs');
const ejs = require('ejs');
module.exports = Self => {
Self.remoteMethod('renderer', {
description: 'Renders the data from an XML',
accessType: 'READ',
accepts: [{
arg: 'expeditionFk',
type: 'number',
required: true
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/renderer`,
verb: 'GET'
}
});
Self.renderer = async expeditionFk => {
const models = Self.app.models;
const viaexpressConfig = await models.ViaexpressConfig.findOne({
fields: ['client', 'user', 'password', 'defaultWeight', 'deliveryType']
});
const expedition = await models.Expedition.findOne({
fields: ['id', 'ticketFk'],
where: {id: expeditionFk},
include: [
{
relation: 'ticket',
scope: {
fields: ['shipped', 'addressFk', 'clientFk', 'companyFk'],
include: [
{
relation: 'client',
scope: {
fields: ['mobile', 'phone', 'email']
}
},
{
relation: 'address',
scope: {
fields: [
'nickname',
'street',
'postalCode',
'city',
'mobile',
'phone',
'provinceFk'
],
include: {
relation: 'province',
scope: {
fields: ['name', 'countryFk'],
include: {
relation: 'country',
scope: {
fields: ['code'],
}
}
}
}
}
},
{
relation: 'company',
scope: {
fields: ['clientFk'],
include: {
relation: 'client',
scope: {
fields: ['socialName', 'mobile', 'phone', 'email', 'defaultAddressFk'],
include: {
relation: 'defaultAddress',
scope: {
fields: [
'street',
'postalCode',
'city',
'mobile',
'phone',
'provinceFk'
],
include: {
relation: 'province',
scope: {
fields: ['name']
}
}
}
}
}
}
}
}
]
}
}
]
});
const ticket = expedition.ticket();
const sender = ticket.company().client();
const shipped = ticket.shipped.toISOString();
const data = {
viaexpressConfig,
sender,
senderAddress: sender.defaultAddress(),
client: ticket.client(),
address: ticket.address(),
shipped
};
const template = fs.readFileSync(__dirname + '/template.ejs', 'utf-8');
const renderedXml = ejs.render(template, data);
return renderedXml;
};
};

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
<soap12:Body>
<PutExpedicionInternacional xmlns="http://82.223.6.71:82">
<ObjetoEnvio>
<Peso><%= viaexpressConfig.defaultWeight %></Peso>
<Bultos>1</Bultos>
<Reembolso>0</Reembolso>
<Fecha><%= shipped %></Fecha>
<ConRetorno>0</ConRetorno>
<Tipo><%= viaexpressConfig.deliveryType %></Tipo>
<Debidos>0</Debidos>
<Asegurado>0</Asegurado>
<Imprimir>0</Imprimir>
<ConDevolucionAlbaran>0</ConDevolucionAlbaran>
<Intradia>0</Intradia>
<Observaciones></Observaciones>
<AlbaranRemitente></AlbaranRemitente>
<Modo>0</Modo>
<TextoAgencia></TextoAgencia>
<Terminal></Terminal>
<ObjetoRemitente>
<RazonSocial><%= sender.socialName %></RazonSocial>
<Domicilio><%= senderAddress.street %></Domicilio>
<Cpostal><%= senderAddress.postalCode %></Cpostal>
<Poblacion><%= senderAddress.city %></Poblacion>
<Provincia><%= senderAddress.province().name %></Provincia>
<Contacto></Contacto>
<Telefono><%= senderAddress.mobile || senderAddress.phone || sender.mobile || sender.phone %></Telefono>
<Email><%= sender.email %></Email>
</ObjetoRemitente>
<ObjetoDestinatario>
<RazonSocial><%= address.nickname %></RazonSocial>
<Domicilio><%= address.street %></Domicilio>
<Cpostal><%= address.postalCode %></Cpostal>
<Poblacion><%= address.city %></Poblacion>
<Municipio></Municipio>
<Provincia><%= address.province().name %></Provincia>
<Contacto></Contacto>
<Telefono><%= address.mobile || address.phone || client.mobile || client.phone %></Telefono>
<Email><%= client.email %></Email>
<Pais><%= address.province().country().code %></Pais>
</ObjetoDestinatario>
<ObjetoLogin>
<IdCliente><%= viaexpressConfig.client %></IdCliente>
<Usuario><%= viaexpressConfig.user %></Usuario>
<Password><%= viaexpressConfig.password %></Password>
</ObjetoLogin>
</ObjetoEnvio>
</PutExpedicionInternacional>
</soap12:Body>
</soap12:Envelope>

View File

@ -47,7 +47,7 @@ module.exports = Self => {
const user = await Self.findById(userId, {fields: ['hasGrant']}, myOptions); const user = await Self.findById(userId, {fields: ['hasGrant']}, myOptions);
const userToUpdate = await Self.findById(id, { const userToUpdate = await Self.findById(id, {
fields: ['id', 'name', 'hasGrant', 'roleFk', 'password'], fields: ['id', 'name', 'hasGrant', 'roleFk', 'password', 'email'],
include: { include: {
relation: 'role', relation: 'role',
scope: { scope: {

View File

@ -7,6 +7,11 @@ module.exports = Self => {
type: 'string', type: 'string',
description: 'The user name or email', description: 'The user name or email',
required: true required: true
},
{
arg: 'app',
type: 'string',
description: 'The directory for mail'
} }
], ],
http: { http: {
@ -15,7 +20,7 @@ module.exports = Self => {
} }
}); });
Self.recoverPassword = async function(user) { Self.recoverPassword = async function(user, app) {
const models = Self.app.models; const models = Self.app.models;
const usesEmail = user.indexOf('@') !== -1; const usesEmail = user.indexOf('@') !== -1;
@ -29,7 +34,7 @@ module.exports = Self => {
} }
try { try {
await Self.resetPassword({email: user, emailTemplate: 'recover-password'}); await Self.resetPassword({email: user, emailTemplate: 'recover-password', app});
} catch (err) { } catch (err) {
if (err.code === 'EMAIL_NOT_FOUND') if (err.code === 'EMAIL_NOT_FOUND')
return; return;

View File

@ -53,19 +53,13 @@ module.exports = Self => {
return Self.validateLogin(user, password); return Self.validateLogin(user, password);
}; };
Self.passExpired = async(vnUser, myOptions) => { Self.passExpired = async vnUser => {
const today = Date.vnNew(); const today = Date.vnNew();
today.setHours(0, 0, 0, 0); today.setHours(0, 0, 0, 0);
if (vnUser.passExpired && vnUser.passExpired.getTime() <= today.getTime()) { if (vnUser.passExpired && vnUser.passExpired.getTime() <= today.getTime()) {
const $ = Self.app.models;
const changePasswordToken = await $.AccessToken.create({
scopes: ['changePassword'],
userId: vnUser.id
}, myOptions);
const err = new UserError('Pass expired', 'passExpired'); const err = new UserError('Pass expired', 'passExpired');
changePasswordToken.twoFactor = vnUser.twoFactor ? true : false; err.details = {userId: vnUser.id, twoFactor: vnUser.twoFactor ? true : false};
err.details = {token: changePasswordToken};
throw err; throw err;
} }
}; };

View File

@ -150,6 +150,9 @@
}, },
"PrintConfig": { "PrintConfig": {
"dataSource": "vn" "dataSource": "vn"
},
"ViaexpressConfig": {
"dataSource": "vn"
} }
} }

View File

@ -18,11 +18,21 @@
}, },
"expired": { "expired": {
"type": "date" "type": "date"
},
"supplierAccountFk": {
"type": "number"
} }
}, },
"scope": { "scope": {
"where" :{ "where" :{
"expired": null "expired": null
} }
},
"relations": {
"client": {
"type": "belongsTo",
"model": "Client",
"foreignKey": "clientFk"
}
} }
} }

View File

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

View File

@ -1,4 +1,5 @@
module.exports = Self => { module.exports = Self => {
require('../methods/osticket/osTicketReportEmail')(Self); require('../methods/osticket/osTicketReportEmail')(Self);
require('../methods/osticket/closeTicket')(Self); require('../methods/osticket/closeTicket')(Self);
require('../methods/osticket/sendToSupport')(Self);
}; };

View File

@ -0,0 +1,4 @@
module.exports = Self => {
require('../methods/viaexpress-config/internationalExpedition')(Self);
require('../methods/viaexpress-config/renderer')(Self);
};

View File

@ -0,0 +1,34 @@
{
"name": "ViaexpressConfig",
"base": "VnModel",
"options": {
"mysql": {
"table": "viaexpressConfig"
}
},
"properties": {
"id": {
"type": "number",
"required": true
},
"url": {
"type": "string",
"required": true
},
"client": {
"type": "string"
},
"user": {
"type": "string"
},
"password": {
"type": "string"
},
"defaultWeight": {
"type": "number"
},
"deliveryType": {
"type": "string"
}
}
}

View File

@ -20,7 +20,7 @@ module.exports = function(Self) {
Self.validatesFormatOf('email', { Self.validatesFormatOf('email', {
message: 'Invalid email', message: 'Invalid email',
allowNull: true, allowNull: true,
allowBlank: true, allowBlank: false,
with: /^[\w|.|-]+@[\w|-]+(\.[\w|-]+)*(,[\w|.|-]+@[\w|-]+(\.[\w|-]+)*)*$/ with: /^[\w|.|-]+@[\w|-]+(\.[\w|-]+)*(,[\w|.|-]+@[\w|-]+(\.[\w|-]+)*)*$/
}); });
@ -96,11 +96,21 @@ module.exports = function(Self) {
const headers = httpRequest.headers; const headers = httpRequest.headers;
const origin = headers.origin; const origin = headers.origin;
const defaultHash = '/reset-password?access_token=$token$';
const recoverHashes = {
hedera: 'verificationToken=$token$'
};
const app = info.options?.app;
let recoverHash = app ? recoverHashes[app] : defaultHash;
recoverHash = recoverHash.replace('$token$', info.accessToken.id);
const user = await Self.app.models.VnUser.findById(info.user.id); const user = await Self.app.models.VnUser.findById(info.user.id);
const params = { const params = {
recipient: info.email, recipient: info.email,
lang: user.lang, lang: user.lang,
url: `${origin}/#!/reset-password?access_token=${info.accessToken.id}` url: origin + '/#!' + recoverHash
}; };
const options = Object.assign({}, info.options); const options = Object.assign({}, info.options);
@ -115,6 +125,14 @@ module.exports = function(Self) {
Self.validateLogin = async function(user, password) { Self.validateLogin = async function(user, password) {
let loginInfo = Object.assign({password}, Self.userUses(user)); let loginInfo = Object.assign({password}, Self.userUses(user));
token = await Self.login(loginInfo, 'user'); token = await Self.login(loginInfo, 'user');
const userToken = await token.user.get();
try {
await Self.app.models.Account.sync(userToken.name, password);
} catch (err) {
console.warn(err);
}
return {token: token.id, ttl: token.ttl}; return {token: token.id, ttl: token.ttl};
}; };

View File

@ -31,7 +31,6 @@ RUN sed -i -e 's/@mockDate/'"$MOCKDATE"'/g' mockDate.sql \
&& gosu mysql docker-structure.sh && gosu mysql docker-structure.sh
COPY changes ./changes COPY changes ./changes
COPY dump/fixtures.sql ./ COPY dump/fixtures.sql ./
ARG STAMP=unknown
RUN gosu mysql docker-fixtures.sh RUN gosu mysql docker-fixtures.sh
RUN echo "[INFO] -> Import finished" \ RUN echo "[INFO] -> Import finished" \

View File

@ -1,4 +0,0 @@
/**
* Hay una versión en salix que machacará toda esta función/procedimiento avisa
* a ___ de los cambios que quieres hacer.
*/

View File

@ -0,0 +1,4 @@
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('SaleTracking', 'deleteSaleGroupDetail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('SaleTracking', 'replaceOrCreate', 'WRITE', 'ALLOW', 'ROLE', 'employee');

View File

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

View File

@ -0,0 +1,4 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('ViaexpressConfig', 'internationalExpedition', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('ViaexpressConfig', 'renderer', 'READ', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1,7 @@
DELETE FROM `vn`.`saleGroupDetail` WHERE id IN (468106,468104,468107,468105,495210,495208,495207,495209,462879,462880,447186,450623,450622,455606,455605,455827,455829,455828,459067,460689,460691,460690,460692,462408,463403,463405,463404,463129,463127,463126,463128,468098,468096,468099,468097,468310,468314,468313,475654,468325,473248,474803,474739,475042,475052,475047,475041,475051,475046,475040,475050,475045,475039,475049,475044,475038,475048,475043,474888,474892,474890,474887,474891,474889,481109,481107,481105,481108,481106,481110,479008,490787,490792,490791,485295,485294,485293,485528,490796,487853,487959,491303,490789,490914,490913,492305,492310,492307,492304,492309,492306,492303,492308,494111,494110,494480,494482,494481,494483,495202,495200,495199,495201,497209,499765,499763,499767,499764,499768,499766,502014,502013,508820,508819,508818,463133,463131,463130,463132,468102,468100,468103,468101,468311,468316,468315,468327,474894,474898,474896,474893,474897,474895,495206,495204,495203,495205,499771,499769,499773,499770,499774,499772);
ALTER TABLE `vn`.`saleGroupDetail` ADD CONSTRAINT saleGroupDetail_UN UNIQUE KEY (saleFk);
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`)
VALUES
('SaleGroupDetail','deleteById','WRITE','ALLOW','employee');

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,2 @@
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalId`)
VALUES ('Ticket','canEditWeekly','WRITE','ALLOW','ROLE','buyer');

View File

@ -0,0 +1,10 @@
CREATE TABLE `vn`.`viaexpressConfig` (
id int auto_increment NOT NULL,
url varchar(100) NOT NULL,
client varchar(100) NOT NULL,
user varchar(100) NOT NULL,
password varchar(100) NOT NULL,
defaultWeight decimal(10,2) NOT NULL,
deliveryType varchar(5) NOT NULL,
CONSTRAINT viaexpressConfig_PK PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;

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

@ -0,0 +1,87 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`client_create`(
vFirstname VARCHAR(50),
vSurnames VARCHAR(50),
vFi VARCHAR(9),
vAddress TEXT,
vPostcode CHAR(5),
vCity VARCHAR(25),
vProvinceFk SMALLINT(5),
vCompanyFk SMALLINT(5),
vPhone VARCHAR(11),
vEmail VARCHAR(255),
vUserFk INT
)
BEGIN
/**
* Create new client
*
* @params vFirstname firstName
* @params vSurnames surnames
* @params vFi company code from accounting transactions
* @params vAddress address
* @params vPostcode postCode
* @params vCity city
* @params vProvinceFk province
* @params vCompanyFk company in which he has become a client
* @params vPhone telephone number
* @params vEmail email address
* @params vUserFk user id
*/
DECLARE vPayMethodFk INT;
DECLARE vDueDay INT;
DECLARE vDefaultCredit DECIMAL(10, 2);
DECLARE vIsTaxDataChecked TINYINT(1);
DECLARE vHasCoreVnl BOOLEAN;
DECLARE vMandateTypeFk INT;
SELECT defaultPayMethodFk,
defaultDueDay,
defaultCredit,
defaultIsTaxDataChecked,
defaultHasCoreVnl,
defaultMandateTypeFk
INTO vPayMethodFk,
vDueDay,
vDefaultCredit,
vIsTaxDataChecked,
vHasCoreVnl,
vMandateTypeFk
FROM clientConfig;
INSERT INTO `client`
SET id = vUserFk,
name = CONCAT(vFirstname, ' ', vSurnames),
street = vAddress,
fi = TRIM(vFi),
phone = vPhone,
email = vEmail,
provinceFk = vProvinceFk,
city = vCity,
postcode = vPostcode,
socialName = UPPER(CONCAT(vSurnames, ' ', vFirstname)),
payMethodFk = vPayMethodFk,
dueDay = vDueDay,
credit = vDefaultCredit,
isTaxDataChecked = vIsTaxDataChecked,
hasCoreVnl = vHasCoreVnl,
isEqualizated = FALSE
ON duplicate KEY UPDATE
payMethodFk = vPayMethodFk,
dueDay = vDueDay,
credit = vDefaultCredit,
isTaxDataChecked = vIsTaxDataChecked,
hasCoreVnl = vHasCoreVnl,
isActive = TRUE;
INSERT INTO mandate (clientFk, companyFk, mandateTypeFk)
SELECT vUserFk, vCompanyFk, vMandateTypeFk
WHERE NOT EXISTS (
SELECT id
FROM mandate
WHERE clientFk = vUserFk
AND companyFk = vCompanyFk
AND mandateTypeFk = vMandateTypeFk
);
END$$
DELIMITER ;

View File

@ -0,0 +1,3 @@
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('TicketSms', 'find', 'READ', 'ALLOW', 'ROLE', 'salesPerson');

View File

View File

@ -0,0 +1,6 @@
UPDATE `salix`.`ACL`
SET principalId='salesPerson'
WHERE
model='Ticket'
AND property='setDeleted'
AND accessType='WRITE';

View File

View File

@ -22,12 +22,8 @@ module.exports = class Docker {
* @param {String} networkName Name of the container network * @param {String} networkName Name of the container network
*/ */
async run(ci, networkName = 'jenkins') { async run(ci, networkName = 'jenkins') {
let d = new Date();
let pad = v => v < 10 ? '0' + v : v;
let stamp = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
log('Building container image...'); log('Building container image...');
await this.execP(`docker build --build-arg STAMP=${stamp} -t salix-db ./db`); await this.execP(`docker build -t salix-db ./db`);
log('Image built.'); log('Image built.');
let dockerArgs; let dockerArgs;

File diff suppressed because one or more lines are too long

View File

@ -360,18 +360,18 @@ INSERT INTO `vn`.`contactChannel`(`id`, `name`)
INSERT INTO `vn`.`client`(`id`,`name`,`fi`,`socialName`,`contact`,`street`,`city`,`postcode`,`phone`,`mobile`,`isRelevant`,`email`,`iban`,`dueDay`,`accountingAccount`,`isEqualizated`,`provinceFk`,`hasToInvoice`,`credit`,`countryFk`,`isActive`,`gestdocFk`,`quality`,`payMethodFk`,`created`,`isToBeMailed`,`contactChannelFk`,`hasSepaVnl`,`hasCoreVnl`,`hasCoreVnh`,`riskCalculated`,`clientTypeFk`, `hasToInvoiceByAddress`,`isTaxDataChecked`,`isFreezed`,`creditInsurance`,`isCreatedAsServed`,`hasInvoiceSimplified`,`salesPersonFk`,`isVies`,`eypbc`, `businessTypeFk`) INSERT INTO `vn`.`client`(`id`,`name`,`fi`,`socialName`,`contact`,`street`,`city`,`postcode`,`phone`,`mobile`,`isRelevant`,`email`,`iban`,`dueDay`,`accountingAccount`,`isEqualizated`,`provinceFk`,`hasToInvoice`,`credit`,`countryFk`,`isActive`,`gestdocFk`,`quality`,`payMethodFk`,`created`,`isToBeMailed`,`contactChannelFk`,`hasSepaVnl`,`hasCoreVnl`,`hasCoreVnh`,`riskCalculated`,`clientTypeFk`, `hasToInvoiceByAddress`,`isTaxDataChecked`,`isFreezed`,`creditInsurance`,`isCreatedAsServed`,`hasInvoiceSimplified`,`salesPersonFk`,`isVies`,`eypbc`, `businessTypeFk`)
VALUES VALUES
(1101, 'Bruce Wayne', '84612325V', 'Batman', 'Alfred', '1007 Mountain Drive, Gotham', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceWayne@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'), (1101, 'Bruce Wayne', '84612325V', 'BATMAN', 'Alfred', '1007 MOUNTAIN DRIVE, GOTHAM', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceWayne@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'),
(1102, 'Petter Parker', '87945234L', 'Spider man', 'Aunt May', '20 Ingram Street, Queens, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'PetterParker@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'), (1102, 'Petter Parker', '87945234L', 'SPIDER MAN', 'Aunt May', '20 INGRAM STREET, QUEENS, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'PetterParker@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'),
(1103, 'Clark Kent', '06815934E', 'Super man', 'lois lane', '344 Clinton Street, Apartament 3-D', 'Gotham', 46460, 1111111111, 222222222, 1, 'ClarkKent@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 0, 19, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'), (1103, 'Clark Kent', '06815934E', 'SUPER MAN', 'lois lane', '344 CLINTON STREET, APARTAMENT 3-D', 'Gotham', 46460, 1111111111, 222222222, 1, 'ClarkKent@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 0, 19, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'),
(1104, 'Tony Stark', '06089160W', 'Iron man', 'Pepper Potts', '10880 Malibu Point, 90265', 'Gotham', 46460, 1111111111, 222222222, 1, 'TonyStark@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'), (1104, 'Tony Stark', '06089160W', 'IRON MAN', 'Pepper Potts', '10880 MALIBU POINT, 90265', 'Gotham', 46460, 1111111111, 222222222, 1, 'TonyStark@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist'),
(1105, 'Max Eisenhardt', '251628698', 'Magneto', 'Rogue', 'Unknown Whereabouts', 'Gotham', 46460, 1111111111, 222222222, 1, 'MaxEisenhardt@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 300, 8, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 18, 0, 1, 'florist'), (1105, 'Max Eisenhardt', '251628698', 'MAGNETO', 'Rogue', 'UNKNOWN WHEREABOUTS', 'Gotham', 46460, 1111111111, 222222222, 1, 'MaxEisenhardt@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 300, 8, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 18, 0, 1, 'florist'),
(1106, 'DavidCharlesHaller', '53136686Q', 'Legion', 'Charles Xavier', 'City of New York, New York, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'DavidCharlesHaller@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 0, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 19, 0, 1, 'florist'), (1106, 'DavidCharlesHaller', '53136686Q', 'LEGION', 'Charles Xavier', 'CITY OF NEW YORK, NEW YORK, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'DavidCharlesHaller@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 0, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 19, 0, 1, 'florist'),
(1107, 'Hank Pym', '09854837G', 'Ant man', 'Hawk', 'Anthill, San Francisco, California', 'Gotham', 46460, 1111111111, 222222222, 1, 'HankPym@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, 19, 0, 1, 'florist'), (1107, 'Hank Pym', '09854837G', 'ANT MAN', 'Hawk', 'ANTHILL, SAN FRANCISCO, CALIFORNIA', 'Gotham', 46460, 1111111111, 222222222, 1, 'HankPym@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, 19, 0, 1, 'florist'),
(1108, 'Charles Xavier', '22641921P', 'Professor X', 'Beast', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'CharlesXavier@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 19, 0, 1, 'florist'), (1108, 'Charles Xavier', '22641921P', 'PROFESSOR X', 'Beast', '3800 VICTORY PKWY, CINCINNATI, OH 45207, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'CharlesXavier@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 19, 0, 1, 'florist'),
(1109, 'Bruce Banner', '16104829E', 'Hulk', 'Black widow', 'Somewhere in New York', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceBanner@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, 9, 0, 1, 'florist'), (1109, 'Bruce Banner', '16104829E', 'HULK', 'Black widow', 'SOMEWHERE IN NEW YORK', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceBanner@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, 9, 0, 1, 'florist'),
(1110, 'Jessica Jones', '58282869H', 'Jessica Jones', 'Luke Cage', 'NYCC 2015 Poster', 'Gotham', 46460, 1111111111, 222222222, 1, 'JessicaJones@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, NULL, 0, 1, 'florist'), (1110, 'Jessica Jones', '58282869H', 'JESSICA JONES', 'Luke Cage', 'NYCC 2015 POSTER', 'Gotham', 46460, 1111111111, 222222222, 1, 'JessicaJones@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, NULL, 0, 1, 'florist'),
(1111, 'Missing', NULL, 'Missing man', 'Anton', 'The space, Universe far away', 'Gotham', 46460, 1111111111, 222222222, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, 0, 1, 0, NULL, 1, 0, NULL, 0, 1, 'others'), (1111, 'Missing', NULL, 'MISSING MAN', 'Anton', 'THE SPACE, UNIVERSE FAR AWAY', 'Gotham', 46460, 1111111111, 222222222, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, 0, 1, 0, NULL, 1, 0, NULL, 0, 1, 'others'),
(1112, 'Trash', NULL, 'Garbage man', 'Unknown name', 'New York city, Underground', 'Gotham', 46460, 1111111111, 222222222, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, 0, 1, 0, NULL, 1, 0, NULL, 0, 1, 'others'); (1112, 'Trash', NULL, 'GARBAGE MAN', 'Unknown name', 'NEW YORK CITY, UNDERGROUND', 'Gotham', 46460, 1111111111, 222222222, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, 0, 1, 0, NULL, 1, 0, NULL, 0, 1, 'others');
INSERT INTO `vn`.`client`(`id`, `name`, `fi`, `socialName`, `contact`, `street`, `city`, `postcode`, `isRelevant`, `email`, `iban`,`dueDay`,`accountingAccount`, `isEqualizated`, `provinceFk`, `hasToInvoice`, `credit`, `countryFk`, `isActive`, `gestdocFk`, `quality`, `payMethodFk`,`created`, `isTaxDataChecked`) INSERT INTO `vn`.`client`(`id`, `name`, `fi`, `socialName`, `contact`, `street`, `city`, `postcode`, `isRelevant`, `email`, `iban`,`dueDay`,`accountingAccount`, `isEqualizated`, `provinceFk`, `hasToInvoice`, `credit`, `countryFk`, `isActive`, `gestdocFk`, `quality`, `payMethodFk`,`created`, `isTaxDataChecked`)
SELECT id, name, CONCAT(RPAD(CONCAT(id,9),8,id),'A'), CONCAT(name, 'Social'), CONCAT(name, 'Contact'), CONCAT(name, 'Street'), 'GOTHAM', 46460, 1, CONCAT(name,'@mydomain.com'), NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1,NULL, 10, 5, util.VN_CURDATE(), 1 SELECT id, name, CONCAT(RPAD(CONCAT(id,9),8,id),'A'), CONCAT(name, 'Social'), CONCAT(name, 'Contact'), CONCAT(name, 'Street'), 'GOTHAM', 46460, 1, CONCAT(name,'@mydomain.com'), NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1,NULL, 10, 5, util.VN_CURDATE(), 1
@ -579,13 +579,13 @@ INSERT INTO `vn`.`supplierAccount`(`id`, `supplierFk`, `iban`, `bankEntityFk`)
VALUES VALUES
(241, 442, 'ES111122333344111122221111', 128); (241, 442, 'ES111122333344111122221111', 128);
INSERT INTO `vn`.`company`(`id`, `code`, `supplierAccountFk`, `workerManagerFk`, `companyCode`, `sage200Company`, `expired`, `companyGroupFk`, `phytosanitary`) INSERT INTO `vn`.`company`(`id`, `code`, `supplierAccountFk`, `workerManagerFk`, `companyCode`, `sage200Company`, `expired`, `companyGroupFk`, `phytosanitary` , `clientFk`)
VALUES VALUES
(69 , 'CCs', NULL, 30, NULL, 0, NULL, 1, NULL), (69 , 'CCs', NULL, 30, NULL, 0, NULL, 1, NULL , NULL),
(442 , 'VNL', 241, 30, 2 , 1, NULL, 2, 'VNL Company - Plant passport'), (442 , 'VNL', 241, 30, 2 , 1, NULL, 2, 'VNL Company - Plant passport' , 1101),
(567 , 'VNH', NULL, 30, NULL, 4, NULL, 1, 'VNH Company - Plant passport'), (567 , 'VNH', NULL, 30, NULL, 4, NULL, 1, 'VNH Company - Plant passport' , NULL),
(791 , 'FTH', NULL, 30, NULL, 3, '2015-11-30', 1, NULL), (791 , 'FTH', NULL, 30, NULL, 3, '2015-11-30', 1, NULL , NULL),
(1381, 'ORN', NULL, 30, NULL, 7, NULL, 1, 'ORN Company - Plant passport'); (1381, 'ORN', NULL, 30, NULL, 7, NULL, 1, 'ORN Company - Plant passport' , NULL);
INSERT INTO `vn`.`taxArea` (`code`, `claveOperacionFactura`, `CodigoTransaccion`) INSERT INTO `vn`.`taxArea` (`code`, `claveOperacionFactura`, `CodigoTransaccion`)
VALUES VALUES

View File

@ -65,7 +65,6 @@ TABLES=(
sample sample
state state
ticketUpdateAction ticketUpdateAction
time
volumeConfig volumeConfig
workCenter workCenter
companyI18n companyI18n

View File

@ -875,7 +875,7 @@ export default {
}, },
routeTickets: { routeTickets: {
firstTicketPriority: 'vn-route-tickets vn-tr:nth-child(1) vn-input-number[ng-model="ticket.priority"]', firstTicketPriority: 'vn-route-tickets vn-tr:nth-child(1) vn-td-editable',
firstTicketCheckbox: 'vn-route-tickets vn-tr:nth-child(1) vn-check', firstTicketCheckbox: 'vn-route-tickets vn-tr:nth-child(1) vn-check',
buscamanButton: 'vn-route-tickets vn-button[icon="icon-buscaman"]', buscamanButton: 'vn-route-tickets vn-button[icon="icon-buscaman"]',
firstTicketDeleteButton: 'vn-route-tickets vn-tr:nth-child(1) vn-icon[icon="delete"]', firstTicketDeleteButton: 'vn-route-tickets vn-tr:nth-child(1) vn-icon[icon="delete"]',

View File

@ -73,8 +73,8 @@ describe('Client create path', () => {
it(`should attempt to create a new user with all it's data but wrong email`, async() => { it(`should attempt to create a new user with all it's data but wrong email`, async() => {
await page.write(selectors.createClientView.name, 'Carol Danvers'); await page.write(selectors.createClientView.name, 'Carol Danvers');
await page.write(selectors.createClientView.socialName, 'AVG tax'); await page.write(selectors.createClientView.socialName, 'AVG TAX');
await page.write(selectors.createClientView.street, 'Many places'); await page.write(selectors.createClientView.street, 'MANY PLACES');
await page.clearInput(selectors.createClientView.email); await page.clearInput(selectors.createClientView.email);
await page.write(selectors.createClientView.email, 'incorrect email format'); await page.write(selectors.createClientView.email, 'incorrect email format');
await page.waitToClick(selectors.createClientView.createButton); await page.waitToClick(selectors.createClientView.createButton);

View File

@ -61,7 +61,7 @@ describe('Client Edit fiscalData path', () => {
await page.clearInput(selectors.clientFiscalData.fiscalId); await page.clearInput(selectors.clientFiscalData.fiscalId);
await page.write(selectors.clientFiscalData.fiscalId, 'INVALID!'); await page.write(selectors.clientFiscalData.fiscalId, 'INVALID!');
await page.clearInput(selectors.clientFiscalData.address); await page.clearInput(selectors.clientFiscalData.address);
await page.write(selectors.clientFiscalData.address, 'Somewhere edited'); await page.write(selectors.clientFiscalData.address, 'SOMEWHERE EDITED');
await page.autocompleteSearch(selectors.clientFiscalData.country, 'España'); await page.autocompleteSearch(selectors.clientFiscalData.country, 'España');
await page.autocompleteSearch(selectors.clientFiscalData.province, 'Province one'); await page.autocompleteSearch(selectors.clientFiscalData.province, 'Province one');
await page.clearInput(selectors.clientFiscalData.city); await page.clearInput(selectors.clientFiscalData.city);
@ -190,7 +190,7 @@ describe('Client Edit fiscalData path', () => {
const verifiedData = await page.checkboxState(selectors.clientFiscalData.verifiedDataCheckbox); const verifiedData = await page.checkboxState(selectors.clientFiscalData.verifiedDataCheckbox);
expect(fiscalId).toEqual('94980061C'); expect(fiscalId).toEqual('94980061C');
expect(address).toEqual('Somewhere edited'); expect(address).toEqual('SOMEWHERE EDITED');
expect(postcode).toContain('46000'); expect(postcode).toContain('46000');
expect(sageTax).toEqual('Operaciones no sujetas'); expect(sageTax).toEqual('Operaciones no sujetas');
expect(sageTransaction).toEqual('Regularización de inversiones'); expect(sageTransaction).toEqual('Regularización de inversiones');

View File

@ -28,7 +28,7 @@ describe('Client lock verified data path', () => {
it('should edit the social name', async() => { it('should edit the social name', async() => {
await page.waitForSelector(selectors.clientFiscalData.socialName); await page.waitForSelector(selectors.clientFiscalData.socialName);
await page.clearInput(selectors.clientFiscalData.socialName); await page.clearInput(selectors.clientFiscalData.socialName);
await page.write(selectors.clientFiscalData.socialName, 'Captain America Civil War'); await page.write(selectors.clientFiscalData.socialName, 'CAPTAIN AMERICA CIVIL WAR');
await page.waitToClick(selectors.clientFiscalData.saveButton); await page.waitToClick(selectors.clientFiscalData.saveButton);
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();
@ -39,7 +39,7 @@ describe('Client lock verified data path', () => {
await page.reloadSection('client.card.fiscalData'); await page.reloadSection('client.card.fiscalData');
const result = await page.waitToGetProperty(selectors.clientFiscalData.socialName, 'value'); const result = await page.waitToGetProperty(selectors.clientFiscalData.socialName, 'value');
expect(result).toEqual('Captain America Civil War'); expect(result).toEqual('CAPTAIN AMERICA CIVIL WAR');
}); });
}); });
@ -88,7 +88,7 @@ describe('Client lock verified data path', () => {
await page.reloadSection('client.card.fiscalData'); await page.reloadSection('client.card.fiscalData');
const result = await page.waitToGetProperty(selectors.clientFiscalData.socialName, 'value'); const result = await page.waitToGetProperty(selectors.clientFiscalData.socialName, 'value');
expect(result).toEqual('Ant man and the Wasp'); expect(result).toEqual('ANT MAN AND THE WASP');
}); });
}); });
@ -142,7 +142,7 @@ describe('Client lock verified data path', () => {
await page.reloadSection('client.card.fiscalData'); await page.reloadSection('client.card.fiscalData');
const result = await page.waitToGetProperty(selectors.clientFiscalData.socialName, 'value'); const result = await page.waitToGetProperty(selectors.clientFiscalData.socialName, 'value');
expect(result).toEqual('new social name edition'); expect(result).toEqual('NEW SOCIAL NAME EDITION');
}); });
}); });

View File

@ -36,7 +36,7 @@ describe('Client summary path', () => {
it('should display fiscal address details', async() => { it('should display fiscal address details', async() => {
const result = await page.waitToGetProperty(selectors.clientSummary.street, 'innerText'); const result = await page.waitToGetProperty(selectors.clientSummary.street, 'innerText');
expect(result).toContain('20 Ingram Street'); expect(result).toContain('20 INGRAM STREET');
}); });
it('should display some fiscal data', async() => { it('should display some fiscal data', async() => {

View File

@ -23,7 +23,7 @@ describe('Worker create path', () => {
await page.write(selectors.workerCreate.fi, '78457139E'); await page.write(selectors.workerCreate.fi, '78457139E');
await page.write(selectors.workerCreate.phone, '12356789'); await page.write(selectors.workerCreate.phone, '12356789');
await page.write(selectors.workerCreate.postcode, '46680'); await page.write(selectors.workerCreate.postcode, '46680');
await page.write(selectors.workerCreate.street, 'S/ Doomstadt'); await page.write(selectors.workerCreate.street, 'S/ DOOMSTADT');
await page.write(selectors.workerCreate.email, 'doctorDoom@marvel.com'); await page.write(selectors.workerCreate.email, 'doctorDoom@marvel.com');
await page.write(selectors.workerCreate.iban, 'ES9121000418450200051332'); await page.write(selectors.workerCreate.iban, 'ES9121000418450200051332');

View File

@ -10,7 +10,7 @@ describe('Ticket create path', () => {
beforeAll(async() => { beforeAll(async() => {
browser = await getBrowser(); browser = await getBrowser();
page = browser.page; page = browser.page;
await page.loginAndModule('employee', 'ticket'); await page.loginAndModule('salesPerson', 'ticket');
}); });
afterAll(async() => { afterAll(async() => {

View File

@ -18,8 +18,7 @@ describe('Route tickets path', () => {
}); });
it('should modify the first ticket priority', async() => { it('should modify the first ticket priority', async() => {
await page.clearInput(selectors.routeTickets.firstTicketPriority); await page.writeOnEditableTD(selectors.routeTickets.firstTicketPriority, '9');
await page.type(selectors.routeTickets.firstTicketPriority, '9');
await page.keyboard.press('Enter'); await page.keyboard.press('Enter');
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();

View File

@ -56,3 +56,4 @@ import './datalist';
import './contextmenu'; import './contextmenu';
import './rating'; import './rating';
import './smart-table'; import './smart-table';
import './support-dialog';

View File

@ -339,8 +339,9 @@ export default class SmartTable extends Component {
if (!header) return; if (!header) return;
const tbody = this.element.querySelector('tbody'); const tbody = this.element.querySelector('tbody');
const columns = header.querySelectorAll('th'); if (!tbody) return;
const columns = header.querySelectorAll('th');
const hasSearchRow = tbody.querySelector('tr#searchRow'); const hasSearchRow = tbody.querySelector('tr#searchRow');
if (hasSearchRow) { if (hasSearchRow) {
if (this.$inputsScope) if (this.$inputsScope)

View File

@ -1 +1,5 @@
<div id="shapes"></div> <div id="shapes"></div>
<vn-support-dialog
vn-id="support-dialog"
additional-data="$ctrl.additionalData">
</vn-support-dialog>

View File

@ -27,6 +27,18 @@ export default class Controller extends Component {
setTimeout(() => element.classList.add('shown'), 30); setTimeout(() => element.classList.add('shown'), 30);
shape.element = element; shape.element = element;
if (data.additionalData) {
this.additionalData = data.additionalData;
let supportButton = document.createElement('i');
supportButton.setAttribute('class', 'material-icons clickable');
supportButton.addEventListener('click', () => this.$.supportDialog.show());
element.appendChild(supportButton);
let buttonIcon = 'support_agent';
buttonIcon = document.createTextNode(buttonIcon);
supportButton.appendChild(buttonIcon);
}
if (shape.type) if (shape.type)
element.classList.add(shape.type); element.classList.add(shape.type);
@ -95,7 +107,7 @@ export default class Controller extends Component {
clearTimeout(shape.hideTimeout); clearTimeout(shape.hideTimeout);
shape.hideTimeout = setTimeout( shape.hideTimeout = setTimeout(
() => this.hide(shape), shape.timeout || 3000); () => this.hide(shape), shape.timeout || 5000);
this.lastShape = shape; this.lastShape = shape;
} }

View File

@ -20,6 +20,10 @@ vn-snackbar .shape {
margin-bottom: 15px; margin-bottom: 15px;
color: white; color: white;
padding: 12px 25px 12px 12px; padding: 12px 25px 12px 12px;
display: flex ;
flex-direction: row;
justify-content: center;
align-items: center;
& > .text { & > .text {
text-align: center; text-align: center;
@ -64,4 +68,12 @@ vn-snackbar .shape {
top: 0; top: 0;
right: 0 right: 0
} }
.clickable{
background-color: $color-main;
padding: 6px;
border-radius: 50%;
cursor: pointer;
margin-right: 7px;
}
} }

View File

@ -0,0 +1,22 @@
<tpl-body>
<section>
<h5 class="vn-py-sm" translate>Send cau</h5>
<vn-horizontal>
<vn-textarea vn-one
label="ExplainReason"
ng-model="$ctrl.reason"
rows="2"
required="true">
</vn-textarea>
</vn-horizontal>
<vn-horizontal>
<span>
{{'By sending this ticket, all the data related to the error, the section, the user, etc., are already sent.' | translate}}
</span>
</vn-horizontal>
</section>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Send</button>
</tpl-buttons>

View File

@ -0,0 +1,27 @@
import ngModule from '../../module';
import Dialog from '../dialog';
export default class Controller extends Dialog {
constructor($element, $, $transclude) {
super($element, $, $transclude);
}
responseHandler(response) {
this.$http.post('Ostickets/send-to-support', {
reason: this.reason,
additionalData: this.additionalData
})
.then(() => super.responseHandler(response))
.then(() => this.vnApp.showSuccess(this.$t('Email sended!')));
}
}
Controller.$inject = ['$element', '$scope', '$transclude'];
ngModule.vnComponent('vnSupportDialog', {
slotTemplate: require('./index.html'),
controller: Controller,
bindings: {
additionalData: '<?'
}
});

View File

@ -14,3 +14,4 @@ Previous: Back
Load more: Load more Load more: Load more
Auto-scroll interrupted, please adjust the search: Auto-scroll interrupted, please adjust the search Auto-scroll interrupted, please adjust the search: Auto-scroll interrupted, please adjust the search
General search: General search General search: General search
ExplainReason: Explain the reason why this error should not occur

View File

@ -64,3 +64,6 @@ No results found: Sin resultados
No data: Sin datos No data: Sin datos
Undo changes: Deshacer cambios Undo changes: Deshacer cambios
Load more results: Cargar más resultados Load more results: Cargar más resultados
Send cau: Enviar cau
By sending this ticket, all the data related to the error, the section, the user, etc., are already sent.: Al enviar este cau ya se envían todos los datos relacionados con el error, la sección, el usuario, etc
ExplainReason: Explique el motivo por el que no deberia aparecer este fallo

View File

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

View File

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

@ -25,15 +25,15 @@ export default class App extends Component {
} }
showMessage(message) { showMessage(message) {
this.$.snackbar.show({message: message}); this.$.snackbar.show({message});
} }
showSuccess(message) { showSuccess(message) {
this.$.snackbar.showSuccess({message: message}); this.$.snackbar.showSuccess({message});
} }
showError(message) { showError(message, additionalData) {
this.$.snackbar.showError({message: message}); this.$.snackbar.showError({message, additionalData});
} }
} }

View File

@ -15,9 +15,6 @@ export default class Controller {
} }
$onInit() { $onInit() {
if (!this.$state.params.id)
this.$state.go('login');
this.$http.get('UserPasswords/findOne') this.$http.get('UserPasswords/findOne')
.then(res => { .then(res => {
this.passRequirements = res.data; this.passRequirements = res.data;
@ -25,7 +22,7 @@ export default class Controller {
} }
submit() { submit() {
const userId = this.$state.params.userId; const userId = parseInt(this.$state.params.userId);
const oldPassword = this.oldPassword; const oldPassword = this.oldPassword;
const newPassword = this.newPassword; const newPassword = this.newPassword;
const repeatPassword = this.repeatPassword; const repeatPassword = this.repeatPassword;
@ -36,18 +33,13 @@ export default class Controller {
if (newPassword != this.repeatPassword) if (newPassword != this.repeatPassword)
throw new UserError(`Passwords don't match`); throw new UserError(`Passwords don't match`);
const headers = {
Authorization: this.$state.params.id
};
this.$http.patch('Accounts/change-password', this.$http.patch('Accounts/change-password',
{ {
id: userId, userId,
oldPassword, oldPassword,
newPassword, newPassword,
code code
}, }
{headers}
).then(() => { ).then(() => {
this.vnApp.showSuccess(this.$translate.instant('Password updated!')); this.vnApp.showSuccess(this.$translate.instant('Password updated!'));
this.$state.go('login'); this.$state.go('login');

View File

@ -36,7 +36,7 @@ export default class Controller {
const err = req.data?.error; const err = req.data?.error;
if (err?.code == 'passExpired') if (err?.code == 'passExpired')
this.$state.go('change-password', err.details.token); this.$state.go('change-password', err.details);
this.loading = false; this.loading = false;
this.password = ''; this.password = '';

View File

@ -148,7 +148,13 @@ function $exceptionHandler(vnApp, $window, $state, $injector) {
if (messageT) if (messageT)
message = $translate.instant(messageT); message = $translate.instant(messageT);
vnApp.showError(message);
const additonalData = {
frontPath: $state.current.name,
httpRequest: cause?.replace('Possibly unhandled rejection: ', ''),
backError: exception
};
vnApp.showError(message, additonalData);
}; };
} }
ngModule.factory('$exceptionHandler', $exceptionHandler); ngModule.factory('$exceptionHandler', $exceptionHandler);

View File

@ -45,7 +45,7 @@ function config($stateProvider, $urlRouterProvider) {
}) })
.state('change-password', { .state('change-password', {
parent: 'outLayout', parent: 'outLayout',
url: '/change-password?id&userId&twoFactor', url: '/change-password?userId&twoFactor',
description: 'Change password', description: 'Change password',
template: '<vn-change-password></vn-change-password>' template: '<vn-change-password></vn-change-password>'
}) })

View File

@ -178,5 +178,12 @@
"The renew period has not been exceeded": "The renew period has not been exceeded", "The renew period has not been exceeded": "The renew period has not been exceeded",
"You can not use the same password": "You can not use the same password", "You can not use the same password": "You can not use the same password",
"Valid priorities": "Valid priorities: %d", "Valid priorities": "Valid priorities: %d",
"Negative basis of tickets": "Negative basis of tickets: {{ticketsIds}}" "Negative basis of tickets": "Negative basis of tickets: {{ticketsIds}}",
"This ticket cannot be left empty.": "This ticket cannot be left empty. %s",
"Social name should be uppercase": "Social name should be uppercase",
"Street should be uppercase": "Street should be uppercase",
"You don't have enough privileges.": "You don't have enough privileges.",
"This ticket is locked.": "This ticket is locked.",
"This ticket is not editable.": "This ticket is not editable.",
"The ticket doesn't exist.": "The ticket doesn't exist."
} }

View File

@ -305,6 +305,15 @@
"The renew period has not been exceeded": "El periodo de renovación no ha sido superado", "The renew period has not been exceeded": "El periodo de renovación no ha sido superado",
"Valid priorities": "Prioridades válidas: %d", "Valid priorities": "Prioridades válidas: %d",
"Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}", "Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}",
"You cannot assign an alias that you are not assigned to": "No puede asignar un alias que no tenga asignado",
"This ticket cannot be left empty.": "Este ticket no se puede dejar vacío. %s",
"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", "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" "This invoice has a linked vehicle.": "Esta factura tiene un vehiculo vinculado",
"You don't have enough privileges.": "No tienes suficientes permisos.",
"This ticket is locked.": "Este ticket está bloqueado.",
"This ticket is not editable.": "Este ticket no es editable.",
"The ticket doesn't exist.": "No existe el ticket.",
"Social name should be uppercase": "La razón social debe ir en mayúscula",
"Street should be uppercase": "La dirección fiscal debe ir en mayúscula"
} }

View File

@ -1,12 +1,15 @@
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('changePassword', { Self.remoteMethod('changePassword', {
description: 'Changes the user password', description: 'Changes the user password',
accessType: 'WRITE',
accessScopes: ['changePassword'],
accepts: [ accepts: [
{ {
arg: 'userId',
type: 'integer',
description: 'The user id',
required: true
}, {
arg: 'oldPassword', arg: 'oldPassword',
type: 'string', type: 'string',
description: 'The old password', description: 'The old password',
@ -28,9 +31,7 @@ module.exports = Self => {
} }
}); });
Self.changePassword = async function(ctx, oldPassword, newPassword, code, options) { Self.changePassword = async function(userId, oldPassword, newPassword, code, options) {
const userId = ctx.req.accessToken.userId;
const myOptions = {}; const myOptions = {};
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);

View File

@ -1,7 +1,7 @@
const {models} = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('account changePassword()', () => { describe('account changePassword()', () => {
const ctx = {req: {accessToken: {userId: 70}}}; const userId = 70;
const unauthCtx = { const unauthCtx = {
req: { req: {
headers: {}, headers: {},
@ -20,7 +20,7 @@ describe('account changePassword()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
await models.Account.changePassword(ctx, 'wrongPassword', 'nightmare.9999', null, options); await models.Account.changePassword(userId, 'wrongPassword', 'nightmare.9999', null, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback(); await tx.rollback();
@ -37,8 +37,8 @@ describe('account changePassword()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
await models.Account.changePassword(ctx, 'nightmare', 'nightmare.9999', null, options); await models.Account.changePassword(userId, 'nightmare', 'nightmare.9999', null, options);
await models.Account.changePassword(ctx, 'nightmare.9999', 'nightmare.9999', null, options); await models.Account.changePassword(userId, 'nightmare.9999', 'nightmare.9999', null, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback(); await tx.rollback();
@ -54,7 +54,7 @@ describe('account changePassword()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
await models.Account.changePassword(ctx, 'nightmare', 'nightmare.9999', null, options); await models.Account.changePassword(userId, 'nightmare', 'nightmare.9999', null, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback(); await tx.rollback();
@ -86,8 +86,8 @@ describe('account changePassword()', () => {
} }
try { try {
const authCode = await models.AuthCode.findOne({where: {userFk: 70}}, options); const authCode = await models.AuthCode.findOne({where: {userFk: userId}}, options);
await models.Account.changePassword(ctx, 'nightmare', 'nightmare.9999', authCode.code, options); await models.Account.changePassword(userId, 'nightmare', 'nightmare.9999', authCode.code, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback(); await tx.rollback();

View File

@ -1,3 +1,5 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = function(Self) { module.exports = function(Self) {
Self.remoteMethodCtx('canBeInvoiced', { Self.remoteMethodCtx('canBeInvoiced', {
description: 'Change property isEqualizated in all client addresses', description: 'Change property isEqualizated in all client addresses',
@ -9,6 +11,12 @@ module.exports = function(Self) {
required: true, required: true,
description: 'Client id', description: 'Client id',
http: {source: 'path'} http: {source: 'path'}
},
{
arg: 'companyFk',
description: 'The company id',
type: 'number',
required: true
} }
], ],
returns: { 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 models = Self.app.models;
const myOptions = {}; const myOptions = {};
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const client = await models.Client.findById(id, { const client = await models.Client.findById(id, {
fields: ['id', 'isTaxDataChecked', 'hasToInvoice'] fields: ['id', 'isTaxDataChecked', 'hasToInvoice', 'payMethodFk'],
include:
{
relation: 'payMethod',
scope: {
fields: ['code']
}
}
}, myOptions); }, 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) if (client.isTaxDataChecked && client.hasToInvoice)
return true; return true;

View File

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

View File

@ -7,8 +7,8 @@ describe('Client Create', () => {
email: 'Deadpool@marvel.com', email: 'Deadpool@marvel.com',
fi: '16195279J', fi: '16195279J',
name: 'Wade', name: 'Wade',
socialName: 'Deadpool Marvel', socialName: 'DEADPOOL MARVEL',
street: 'Wall Street', street: 'WALL STREET',
city: 'New York', city: 'New York',
businessTypeFk: 'florist', businessTypeFk: 'florist',
provinceFk: 1 provinceFk: 1

View File

@ -1,7 +1,7 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
describe('Client last active tickets', () => { describe('Client last active tickets', () => {
it('should receive an array of last active tickets of Bruce Wayne', async() => { it('should receive an array of last active tickets of BRUCE WAYNE', async() => {
const tx = await models.Client.beginTransaction({}); const tx = await models.Client.beginTransaction({});
try { try {

View File

@ -1,7 +1,7 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
describe('Client transactions', () => { describe('Client transactions', () => {
it('should call transactions() method to receive a list of Web Payments from Bruce Wayne', async() => { it('should call transactions() method to receive a list of Web Payments from BRUCE WAYNE', async() => {
const tx = await models.Client.beginTransaction({}); const tx = await models.Client.beginTransaction({});
try { try {

View File

@ -36,6 +36,20 @@ module.exports = Self => {
min: 3, max: 10 min: 3, max: 10
}); });
Self.validatesFormatOf('street', {
message: 'Street should be uppercase',
allowNull: false,
allowBlank: false,
with: /^[^a-z]*$/
});
Self.validatesFormatOf('socialName', {
message: 'Social name should be uppercase',
allowNull: false,
allowBlank: false,
with: /^[^a-z]*$/
});
Self.validateAsync('socialName', socialNameIsUnique, { Self.validateAsync('socialName', socialNameIsUnique, {
message: 'The company name must be unique' message: 'The company name must be unique'
}); });

View File

@ -33,6 +33,7 @@
ng-model="$ctrl.client.socialName" ng-model="$ctrl.client.socialName"
info="Only letters, numbers and spaces can be used" info="Only letters, numbers and spaces can be used"
required="true" required="true"
ng-keyup="$ctrl.client.socialName = $ctrl.client.socialName.toUpperCase()"
rule> rule>
</vn-textfield> </vn-textfield>
<vn-textfield <vn-textfield
@ -46,6 +47,7 @@
vn-two vn-two
label="Street" label="Street"
ng-model="$ctrl.client.street" ng-model="$ctrl.client.street"
ng-keyup="$ctrl.client.street = $ctrl.client.street.toUpperCase()"
rule> rule>
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>

View File

@ -8,7 +8,7 @@
auto-load="true"> auto-load="true">
</vn-crud-model> </vn-crud-model>
<vn-data-viewer model="model"> <vn-data-viewer model="model">
<vn-card class="vn-w-md"> <vn-card class="vn-w-lg">
<vn-table model="model" auto-load="false"> <vn-table model="model" auto-load="false">
<vn-thead> <vn-thead>
<vn-tr> <vn-tr>
@ -27,7 +27,7 @@
</span> </span>
</vn-td> </vn-td>
<vn-td number expand>{{::clientSms.sms.destination}}</vn-td> <vn-td number expand>{{::clientSms.sms.destination}}</vn-td>
<vn-td>{{::clientSms.sms.message}}</vn-td> <vn-td expand vn-tooltip="{{::clientSms.sms.message}}">{{::clientSms.sms.message}}</vn-td>
<vn-td>{{::clientSms.sms.status}}</vn-td> <vn-td>{{::clientSms.sms.status}}</vn-td>
<vn-td shrink-datetime>{{::clientSms.sms.created | date:'dd/MM/yyyy HH:mm'}}</vn-td> <vn-td shrink-datetime>{{::clientSms.sms.created | date:'dd/MM/yyyy HH:mm'}}</vn-td>
</vn-tr> </vn-tr>

View File

@ -39,7 +39,7 @@
label="Recovery email" label="Recovery email"
ng-model="$ctrl.account.email" ng-model="$ctrl.account.email"
info="This email is used for user to regain access their account." info="This email is used for user to regain access their account."
rule="VnUser.name"> rule="VnUser.email">
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>
</vn-card> </vn-card>

View File

@ -0,0 +1,20 @@
name: invoice in
columns:
id: id
serialNumber: serial number
serial: serial
supplierFk: supplier
issued: issued
supplierRef: supplierRef
isBooked: is booked
currencyFk: currency
created: created
companyFk: company
docFk: document
booked: booked
operated: operated
bookEntried: book entried
isVatDeductible: is VAT deductible
withholdingSageFk: withholding
expenceFkDeductible: expence deductible
editorFk: editor

View File

@ -0,0 +1,20 @@
name: factura recibida
columns:
id: id
serialNumber: número de serie
serial: serie
supplierFk: proveedor
issued: fecha emisión
supplierRef: referéncia proveedor
isBooked: facturado
currencyFk: moneda
created: creado
companyFk: empresa
docFk: documento
booked: fecha contabilización
operated: fecha entrega
bookEntried: fecha asiento
isVatDeductible: impuesto deducible
withholdingSageFk: código de retención
expenceFkDeductible: gasto deducible
editorFk: editor

View File

@ -0,0 +1,9 @@
name: invoice in due day
columns:
id: id
invoiceInFk: invoice in
dueDated: due date
bankFk: bank
amount: amount
foreignValue : foreign amount
created: created

View File

@ -0,0 +1,9 @@
name: vencimientos factura recibida
columns:
id: id
invoiceInFk: factura
dueDated: fecha vto.
bankFk: banco
amount: importe
foreignValue : importe divisa
created: creado

View File

@ -0,0 +1,12 @@
name: invoice in tax
columns:
id: id
invoiceInFk: invoice in
taxCodeFk: tax
taxableBase: taxable base
expenceFk: expence
foreignValue: foreign amount
taxTypeSageFk: tax type
transactionTypeSageFk: transaction type
created: created
editorFk: editor

View File

@ -0,0 +1,12 @@
name: factura recibida impuesto
columns:
id: id
invoiceInFk: factura recibida
taxCodeFk: código IVA
taxableBase: base imponible
expenceFk: código gasto
foreignValue: importe divisa
taxTypeSageFk: código impuesto
transactionTypeSageFk: código transacción
created: creado
editorFk: editor

View File

@ -28,11 +28,15 @@ module.exports = Self => {
Object.assign(myOptions, options); Object.assign(myOptions, options);
const stmt = new ParameterizedSQL(` const stmt = new ParameterizedSQL(`
SELECT iss.created, SELECT
iss.id,
iss.created,
iss.saleFk, iss.saleFk,
iss.quantity, iss.quantity,
iss.userFk, iss.userFk,
ish.id itemShelvingFk,
ish.shelvingFk, ish.shelvingFk,
s.parkingFk,
p.code, p.code,
u.name u.name
FROM itemShelvingSale iss FROM itemShelvingSale iss

View File

@ -1,44 +0,0 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
module.exports = Self => {
Self.remoteMethod('activeBuyers', {
description: 'Returns a list of buyers for the given item type',
accepts: [{
arg: 'filter',
type: 'object',
description: `Filter defining where, order, offset, and limit - must be a JSON-encoded string`
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/activeBuyers`,
verb: 'GET'
}
});
Self.activeBuyers = async(filter, options) => {
const conn = Self.dataSource.connector;
const where = {isActive: true};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
filter = mergeFilters(filter, {where});
let stmt = new ParameterizedSQL(
`SELECT DISTINCT w.id workerFk, w.firstName, w.lastName, u.name, u.nickname
FROM worker w
JOIN itemType it ON it.workerFk = w.id
JOIN account.user u ON u.id = w.id
JOIN item i ON i.typeFk = it.id`,
null, myOptions);
stmt.merge(conn.makeSuffix(filter));
return conn.executeStmt(stmt);
};
};

View File

@ -1,24 +0,0 @@
const models = require('vn-loopback/server/server').models;
describe('Worker activeBuyers', () => {
it('should return the buyers in itemType as result', async() => {
const tx = await models.Item.beginTransaction({});
try {
const options = {transaction: tx};
const filter = {};
const result = await models.Item.activeBuyers(filter, options);
const firstWorker = result[0];
const secondWorker = result[1];
expect(result.length).toEqual(2);
expect(firstWorker.nickname).toEqual('logisticBossNick');
expect(secondWorker.nickname).toEqual('buyerNick');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -25,6 +25,9 @@
"type": "boolean" "type": "boolean"
}, },
"visible": { "visible": {
"type": "number"
},
"userFk": {
"type": "number" "type": "number"
} }
}, },
@ -38,11 +41,6 @@
"type": "belongsTo", "type": "belongsTo",
"model": "VnUser", "model": "VnUser",
"foreignKey": "userFk" "foreignKey": "userFk"
},
"shelving": {
"type": "belongsTo",
"model": "Shelving",
"foreignKey": "shelvingFk"
} }
} }
} }

View File

@ -14,7 +14,6 @@ module.exports = Self => {
require('../methods/item/getWasteByWorker')(Self); require('../methods/item/getWasteByWorker')(Self);
require('../methods/item/getWasteByItem')(Self); require('../methods/item/getWasteByItem')(Self);
require('../methods/item/createIntrastat')(Self); require('../methods/item/createIntrastat')(Self);
require('../methods/item/activeBuyers')(Self);
require('../methods/item/buyerWasteEmail')(Self); require('../methods/item/buyerWasteEmail')(Self);
require('../methods/item/labelPdf')(Self); require('../methods/item/labelPdf')(Self);

View File

@ -2,37 +2,37 @@
<vn-auto> <vn-auto>
<section <section
class="inline-tag ellipsize" class="inline-tag ellipsize"
ng-class="::{empty: !$ctrl.item.value5}" ng-class="::{empty: !$ctrl.item.tag5}"
title="{{::$ctrl.item.tag5}}: {{::$ctrl.item.value5}}"> title="{{::$ctrl.item.tag5}}: {{::$ctrl.item.value5}}">
{{::$ctrl.item.value5}} {{::$ctrl.item.value5}}
</section> </section>
<section <section
class="inline-tag ellipsize" class="inline-tag ellipsize"
ng-class="::{empty: !$ctrl.item.value6}" ng-class="::{empty: !$ctrl.item.tag6}"
title="{{::$ctrl.item.tag6}}: {{::$ctrl.item.value6}}"> title="{{::$ctrl.item.tag6}}: {{::$ctrl.item.value6}}">
{{::$ctrl.item.value6}} {{::$ctrl.item.value6}}
</section> </section>
<section <section
class="inline-tag ellipsize" class="inline-tag ellipsize"
ng-class="::{empty: !$ctrl.item.value7}" ng-class="::{empty: !$ctrl.item.tag7}"
title="{{::$ctrl.item.tag7}}: {{::$ctrl.item.value7}}"> title="{{::$ctrl.item.tag7}}: {{::$ctrl.item.value7}}">
{{::$ctrl.item.value7}} {{::$ctrl.item.value7}}
</section> </section>
<section <section
class="inline-tag ellipsize" class="inline-tag ellipsize"
ng-class="::{empty: !$ctrl.item.value8}" ng-class="::{empty: !$ctrl.item.tag8}"
title="{{::$ctrl.item.tag8}}: {{::$ctrl.item.value8}}"> title="{{::$ctrl.item.tag8}}: {{::$ctrl.item.value8}}">
{{::$ctrl.item.value8}} {{::$ctrl.item.value8}}
</section> </section>
<section <section
class="inline-tag ellipsize" class="inline-tag ellipsize"
ng-class="::{empty: !$ctrl.item.value9}" ng-class="::{empty: !$ctrl.item.tag9}"
title="{{::$ctrl.item.tag9}}: {{::$ctrl.item.value9}}"> title="{{::$ctrl.item.tag9}}: {{::$ctrl.item.value9}}">
{{::$ctrl.item.value9}} {{::$ctrl.item.value9}}
</section> </section>
<section <section
class="inline-tag ellipsize" class="inline-tag ellipsize"
ng-class="::{empty: !$ctrl.item.value10}" ng-class="::{empty: !$ctrl.item.tag10}"
title="{{::$ctrl.item.tag10}}: {{::$ctrl.item.value10}}"> title="{{::$ctrl.item.tag10}}: {{::$ctrl.item.value10}}">
{{::$ctrl.item.value10}} {{::$ctrl.item.value10}}
</section> </section>

View File

@ -28,7 +28,7 @@
vn-fetched-tags { vn-fetched-tags {
& > vn-horizontal { & > vn-horizontal {
align-items: center; align-items: center;
max-width: 210px;
& > vn-auto { & > vn-auto {
flex-wrap: wrap; flex-wrap: wrap;
@ -43,17 +43,17 @@ vn-fetched-tags {
& > .inline-tag { & > .inline-tag {
color: $color-font-secondary; color: $color-font-secondary;
text-align: center; text-align: center;
font-size: .75rem; font-size: .8rem;
height: 12px; height: 13px;
padding: 1px; padding: 1px;
width: 64px; width: 64px;
min-width: 64px; min-width: 64px;
max-width: 64px; max-width: 64px;
flex: 1; flex: 1;
border: 1px solid $color-spacer; border: 1px solid $color-font-secondary;
&.empty { &.empty {
border: 1px solid $color-spacer-light; border: 1px solid darken($color-font-secondary, 30%);
} }
} }
} }

View File

@ -41,10 +41,10 @@
<vn-autocomplete <vn-autocomplete
vn-one vn-one
ng-model="filter.buyerFk" ng-model="filter.buyerFk"
url="Items/activeBuyers" url="TicketRequests/getItemTypeWorker"
search-function="{firstName: $search}"
show-field="nickname" show-field="nickname"
search-function="{nickname: {like: '%'+ $search +'%'}}" value-field="id"
value-field="workerFk"
label="Buyer"> label="Buyer">
</vn-autocomplete> </vn-autocomplete>
<vn-autocomplete <vn-autocomplete

View File

@ -0,0 +1,36 @@
module.exports = Self => {
Self.remoteMethodCtx('cmr', {
description: 'Returns the cmr',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The cmr 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/cmr',
verb: 'GET'
}
});
Self.cmr = (ctx, id) => Self.printReport(ctx, id, 'cmr');
};

View File

@ -0,0 +1,133 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
const buildFilter = require('vn-loopback/util/filter').buildFilter;
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
module.exports = Self => {
Self.remoteMethod('getExternalCmrs', {
description: 'Returns an array of external cmrs',
accessType: 'READ',
accepts: [
{
arg: 'filter',
type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
},
{
arg: 'cmrFk',
type: 'integer',
description: 'Searchs the route by id',
},
{
arg: 'ticketFk',
type: 'integer',
description: 'The worker id',
},
{
arg: 'country',
type: 'string',
description: 'The agencyMode id',
},
{
arg: 'clientFk',
type: 'integer',
description: 'The vehicle id',
},
{
arg: 'hasCmrDms',
type: 'boolean',
description: 'The vehicle id',
},
{
arg: 'shipped',
type: 'date',
description: 'The to date filter',
},
],
returns: {
type: ['object'],
root: true
},
http: {
path: `/getExternalCmrs`,
verb: 'GET'
}
});
Self.getExternalCmrs = async(
filter,
cmrFk,
ticketFk,
country,
clientFk,
hasCmrDms,
shipped,
options
) => {
const params = {
cmrFk,
ticketFk,
country,
clientFk,
hasCmrDms,
shipped,
};
const conn = Self.dataSource.connector;
let where = buildFilter(params, (param, value) => {return {[param]: value}});
filter = mergeFilters(filter, {where});
if (!filter.where) {
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
filter.where = {'shipped': yesterday.toISOString().split('T')[0]}
}
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
let stmts = [];
const stmt = new ParameterizedSQL(`
SELECT *
FROM (
SELECT t.cmrFk,
t.id ticketFk,
co.country,
t.clientFk,
IF(sub.id, TRUE, FALSE) hasCmrDms,
DATE(t.shipped) shipped
FROM ticket t
JOIN ticketState ts ON ts.ticketFk = t.id
JOIN state s ON s.id = ts.stateFk
JOIN alertLevel al ON al.id = s.alertLevel
JOIN client c ON c.id = t.clientFk
JOIN address a ON a.id = t.addressFk
JOIN province p ON p.id = a.provinceFk
JOIN country co ON co.id = p.countryFk
JOIN agencyMode am ON am.id = t.agencyModeFk
JOIN deliveryMethod dm ON dm.id = am.deliveryMethodFk
JOIN warehouse w ON w.id = t.warehouseFk
LEFT JOIN (
SELECT td.ticketFk, d.id
FROM ticketDms td
JOIN dms d ON d.id = td.dmsFk
JOIN dmsType dt ON dt.id = d.dmsTypeFk
WHERE dt.name = 'cmr'
) sub ON sub.ticketFk = t.id
WHERE co.code <> 'ES'
AND am.name <> 'ABONO'
AND w.code = 'ALG'
AND dm.code = 'DELIVERY'
AND t.cmrFk
) sub
`);
stmt.merge(conn.makeSuffix(filter));
const itemsIndex = stmts.push(stmt) - 1;
const sql = ParameterizedSQL.join(stmts, ';');
const result = await conn.executeStmt(sql);
return itemsIndex === 0 ? result : result[itemsIndex];
};
};

View File

@ -14,6 +14,8 @@ module.exports = Self => {
require('../methods/route/driverRouteEmail')(Self); require('../methods/route/driverRouteEmail')(Self);
require('../methods/route/sendSms')(Self); require('../methods/route/sendSms')(Self);
require('../methods/route/downloadZip')(Self); require('../methods/route/downloadZip')(Self);
require('../methods/route/cmr')(Self);
require('../methods/route/getExternalCmrs')(Self);
Self.validate('kmStart', validateDistance, { Self.validate('kmStart', validateDistance, {
message: 'Distance must be lesser than 1000' message: 'Distance must be lesser than 1000'
@ -28,5 +30,5 @@ module.exports = Self => {
const routeMaxKm = 1000; const routeMaxKm = 1000;
if (routeTotalKm > routeMaxKm || this.kmStart > this.kmEnd) if (routeTotalKm > routeMaxKm || this.kmStart > this.kmEnd)
err(); err();
} };
}; };

View File

@ -46,8 +46,6 @@ class Controller extends Section {
} }
deleteRoadmaps() { deleteRoadmaps() {
console.log(this.checked);
for (const roadmap of this.checked) { for (const roadmap of this.checked) {
this.$http.delete(`Roadmaps/${roadmap.id}`) this.$http.delete(`Roadmaps/${roadmap.id}`)
.then(() => this.$.model.refresh()) .then(() => this.$.model.refresh())

View File

@ -84,14 +84,21 @@
tabindex="-1"> tabindex="-1">
</vn-icon-button> </vn-icon-button>
</vn-td> </vn-td>
<vn-td> <vn-td-editable number>
<vn-input-number <text>
on-change="$ctrl.setPriority(ticket.id, ticket.priority)" <strong>{{ticket.priority}}</strong>
ng-model="ticket.priority" </text>
rule="Ticket" <field>
class="dense"> <vn-input-number
</vn-input-number> rule="Ticket"
</vn-td> ng-model="ticket.priority"
on-change="$ctrl.setPriority(ticket.id, ticket.priority)"
step="1"
class="dense"
vn-focus>
</vn-input-number>
</field>
</vn-td-editable>
<vn-td expand title="{{::ticket.street}}">{{::ticket.street}}</vn-td> <vn-td expand title="{{::ticket.street}}">{{::ticket.street}}</vn-td>
<vn-td <vn-td
expand expand

View File

@ -142,12 +142,12 @@ module.exports = Self => {
const changes = ctx.data || ctx.instance; const changes = ctx.data || ctx.instance;
const orgData = ctx.currentInstance; const orgData = ctx.currentInstance;
const socialName = changes.name || orgData.name; const name = changes.name || orgData.name;
const hasChanges = orgData && changes; const hasChanges = orgData && changes;
const socialNameChanged = hasChanges const nameChanged = hasChanges
&& orgData.socialName != socialName; && orgData.name != name;
if ((socialNameChanged) && !isAlpha(socialName)) if ((nameChanged) && !isAlpha(name))
throw new UserError('The social name has an invalid format'); throw new UserError('The social name has an invalid format');
}); });
}; };

View File

@ -0,0 +1,69 @@
module.exports = Self => {
Self.remoteMethod('delete', {
description: 'Delete sale trackings and item shelving sales',
accessType: 'READ',
accepts: [
{
arg: 'saleFk',
type: 'number',
description: 'The sale id'
},
{
arg: 'stateCode',
type: 'string'
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: `/delete`,
verb: 'POST'
}
});
Self.delete = async(saleFk, stateCode, options) => {
const models = Self.app.models;
const myOptions = {};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
if (stateCode === 'PREPARED') {
const itemShelvingSales = await models.ItemShelvingSale.find({where: {saleFk: saleFk}}, myOptions);
for (let itemShelvingSale of itemShelvingSales)
await itemShelvingSale.destroy(myOptions);
}
const state = await models.State.findOne({
where: {code: stateCode}
}, myOptions);
const filter = {
where: {
saleFk: saleFk,
stateFk: state.id
}
};
const saleTrackings = await models.SaleTracking.find(filter, myOptions);
for (let saleTracking of saleTrackings)
await saleTracking.destroy(myOptions);
if (tx) await tx.commit();
return true;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -0,0 +1,94 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => {
Self.remoteMethod('filter', {
description: 'Returns a list with the lines of a ticket and its different states of preparation',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The ticket id',
http: {source: 'path'}
},
{
arg: 'filter',
type: 'object',
description: 'Filter defining where and paginated data'
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: `/:id/filter`,
verb: 'GET'
}
});
Self.filter = async(id, filter, options) => {
const conn = Self.dataSource.connector;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const stmts = [];
let stmt;
stmts.push('CALL cache.last_buy_refresh(FALSE)');
stmt = new ParameterizedSQL(
`SELECT t.clientFk,
t.shipped,
s.ticketFk,
s.itemFk,
s.quantity,
s.concept,
s.id saleFk,
i.image,
i.subName,
IF(stPrevious.saleFk,TRUE,FALSE) as isPreviousSelected,
stPrevious.isChecked as isPrevious,
stPrepared.isChecked as isPrepared,
stControled.isChecked as isControled,
sgd.id saleGroupDetailFk,
(MAX(sgd.id) IS NOT NULL) AS hasSaleGroupDetail,
p.code AS parkingCode,
i.value5,
i.value6,
i.value7,
i.value8,
i.value9,
i.value10
FROM vn.ticket t
JOIN vn.sale s ON s.ticketFk = t.id
JOIN vn.item i ON i.id = s.itemFk
LEFT JOIN cache.last_buy lb ON lb.item_id = i.id AND lb.warehouse_id = t.warehouseFk
LEFT JOIN vn.state st ON TRUE
LEFT JOIN vn.saleTracking stPrevious ON stPrevious.saleFk = s.id
AND stPrevious.stateFk = (SELECT id FROM vn.state WHERE code = 'PREVIOUS_PREPARATION')
LEFT JOIN vn.saleTracking stPrepared ON stPrepared.saleFk = s.id
AND stPrepared.stateFk = (SELECT id FROM vn.state WHERE code = 'PREPARED')
LEFT JOIN vn.saleTracking stControled ON stControled.saleFk = s.id
AND stControled.stateFk = (SELECT id FROM vn.state s2 WHERE code = 'CHECKED')
LEFT JOIN vn.saleGroupDetail sgd ON sgd.saleFk = s.id
LEFT JOIN vn.saleGroup sg ON sg.id = sgd.saleGroupFk
LEFT JOIN vn.parking p ON p.id = sg.parkingFk
WHERE t.id = ?
GROUP BY s.id`, [id]);
stmts.push(stmt);
stmt.merge(Self.makeSuffix(filter));
const index = stmts.push(stmt) - 1;
const sql = ParameterizedSQL.join(stmts, ';');
const result = await conn.executeStmt(sql, myOptions);
return result[index];
};
};

View File

@ -0,0 +1,90 @@
module.exports = Self => {
Self.remoteMethodCtx('new', {
description: `Replaces the record or creates it if it doesn't exist`,
accessType: 'READ',
accepts: [
{
arg: 'saleFk',
type: 'number',
description: 'The sale id'
},
{
arg: 'isChecked',
type: 'boolean'
},
{
arg: 'quantity',
type: 'number'
},
{
arg: 'stateCode',
type: 'string'
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: `/new`,
verb: 'POST'
}
});
Self.new = async(ctx, saleFk, isChecked, quantity, stateCode, options) => {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const myOptions = {};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const state = await models.State.findOne({
where: {code: stateCode}
}, myOptions);
const saleTracking = await models.SaleTracking.findOne({
where: {
saleFk: saleFk,
stateFk: state.id,
workerFk: userId
}
}, myOptions);
let newSaleTracking;
if (saleTracking) {
newSaleTracking = await saleTracking.updateAttributes({
saleFk: saleFk,
stateFk: state.id,
workerFk: userId,
isChecked: isChecked,
originalQuantity: quantity,
isScanned: null
}, myOptions);
} else {
newSaleTracking = await models.SaleTracking.create({
saleFk: saleFk,
stateFk: state.id,
workerFk: userId,
isChecked: isChecked,
originalQuantity: quantity,
isScanned: null
}, myOptions);
}
if (tx) await tx.commit();
return newSaleTracking;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

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