Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 5834-billingDataAutoFillBic
gitea/salix/pipeline/head This commit looks good
Details
gitea/salix/pipeline/head This commit looks good
Details
This commit is contained in:
commit
ce9fafdcab
48
CHANGELOG.md
48
CHANGELOG.md
|
@ -5,13 +5,54 @@ 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
|
## [2346.01] - 2023-11-16
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
### Changed
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
|
||||||
|
## [2342.01] - 2023-11-02
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- (Usuarios -> Foto) Se muestra la foto del trabajador
|
||||||
|
### Fixed
|
||||||
|
- (Usuarios -> Historial) Abre el descriptor del usuario correctamente
|
||||||
|
|
||||||
|
|
||||||
|
## [2340.01] - 2023-10-05
|
||||||
|
|
||||||
|
## [2338.01] - 2023-09-21
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- (Ticket -> Servicios) Se pueden abonar servicios
|
||||||
|
- (Facturas -> Datos básicos) Muestra valores por defecto
|
||||||
|
- (Facturas -> Borrado) Notificación al borrar un asiento ya enlazado en Sage
|
||||||
|
### Changed
|
||||||
|
- (Trabajadores -> Calendario) Icono de check arreglado cuando pulsas un tipo de dia
|
||||||
|
|
||||||
|
## [2336.01] - 2023-09-07
|
||||||
|
|
||||||
|
## [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
|
### 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
|
### Fixed
|
||||||
|
- (Clientes -> SMS) Al pasar el ratón por encima muestra el mensaje completo
|
||||||
|
|
||||||
|
|
||||||
## [2330.01] - 2023-07-27
|
## [2330.01] - 2023-07-27
|
||||||
|
@ -24,10 +65,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
|
||||||
|
|
|
@ -26,15 +26,14 @@ module.exports = Self => {
|
||||||
|
|
||||||
Self.sendCheckingPresence = async(ctx, recipientId, message) => {
|
Self.sendCheckingPresence = async(ctx, recipientId, message) => {
|
||||||
if (!recipientId) return false;
|
if (!recipientId) return false;
|
||||||
|
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
|
|
||||||
const userId = ctx.req.accessToken.userId;
|
const userId = ctx.req.accessToken.userId;
|
||||||
const sender = await models.VnUser.findById(userId, {fields: ['id']});
|
const sender = await models.VnUser.findById(userId, {fields: ['id']});
|
||||||
const recipient = await models.VnUser.findById(recipientId, null);
|
const recipient = await models.VnUser.findById(recipientId, null);
|
||||||
|
|
||||||
// Prevent sending messages to yourself
|
// Prevent sending messages to yourself
|
||||||
if (recipientId == userId) return false;
|
if (recipientId == userId) return false;
|
||||||
|
|
||||||
if (!recipient)
|
if (!recipient)
|
||||||
throw new Error(`Could not send message "${message}" to worker id ${recipientId} from user ${userId}`);
|
throw new Error(`Could not send message "${message}" to worker id ${recipientId} from user ${userId}`);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,135 @@
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethodCtx('getTickets', {
|
||||||
|
description: 'Make a new collection of tickets',
|
||||||
|
accessType: 'WRITE',
|
||||||
|
accepts: [{
|
||||||
|
arg: 'id',
|
||||||
|
type: 'number',
|
||||||
|
description: 'The collection id',
|
||||||
|
required: true,
|
||||||
|
http: {source: 'path'}
|
||||||
|
}, {
|
||||||
|
arg: 'print',
|
||||||
|
type: 'boolean',
|
||||||
|
description: 'True if you want to print'
|
||||||
|
}],
|
||||||
|
returns: {
|
||||||
|
type: ['object'],
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/:id/getTickets`,
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.getTickets = async(ctx, id, print, options) => {
|
||||||
|
const userId = ctx.req.accessToken.userId;
|
||||||
|
const url = await Self.app.models.Url.getUrl();
|
||||||
|
const $t = ctx.req.__;
|
||||||
|
const myOptions = {};
|
||||||
|
|
||||||
|
if (typeof options == 'object')
|
||||||
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
myOptions.userId = userId;
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
const [tickets] = await Self.rawSql(`CALL vn.collection_getTickets(?)`, [id], myOptions);
|
||||||
|
const sales = await Self.rawSql(`
|
||||||
|
SELECT s.ticketFk,
|
||||||
|
sgd.saleGroupFk,
|
||||||
|
s.id saleFk,
|
||||||
|
s.itemFk,
|
||||||
|
i.longName,
|
||||||
|
i.size,
|
||||||
|
ic.color,
|
||||||
|
o.code origin,
|
||||||
|
ish.packing,
|
||||||
|
ish.grouping,
|
||||||
|
s.isAdded,
|
||||||
|
s.originalQuantity,
|
||||||
|
s.quantity saleQuantity,
|
||||||
|
iss.quantity reservedQuantity,
|
||||||
|
SUM(iss.quantity) OVER (PARTITION BY s.id ORDER BY ish.id) accumulatedQuantity,
|
||||||
|
ROW_NUMBER () OVER (PARTITION BY s.id ORDER BY pickingOrder) currentItemShelving,
|
||||||
|
COUNT(*) OVER (PARTITION BY s.id ORDER BY s.id) totalItemShelving,
|
||||||
|
sh.code,
|
||||||
|
IFNULL(p2.code, p.code) parkingCode,
|
||||||
|
IFNULL(p2.pickingOrder, p.pickingOrder) pickingOrder,
|
||||||
|
iss.id itemShelvingSaleFk,
|
||||||
|
iss.isPicked
|
||||||
|
FROM ticketCollection tc
|
||||||
|
LEFT JOIN collection c ON c.id = tc.collectionFk
|
||||||
|
JOIN ticket t ON t.id = tc.ticketFk
|
||||||
|
JOIN sale s ON s.ticketFk = t.id
|
||||||
|
LEFT JOIN saleGroupDetail sgd ON sgd.saleFk = s.id
|
||||||
|
LEFT JOIN saleGroup sg ON sg.id = sgd.saleGroupFk
|
||||||
|
LEFT JOIN parking p2 ON p2.id = sg.parkingFk
|
||||||
|
JOIN item i ON i.id = s.itemFk
|
||||||
|
LEFT JOIN itemShelvingSale iss ON iss.saleFk = s.id
|
||||||
|
LEFT JOIN itemShelving ish ON ish.id = iss.itemShelvingFk
|
||||||
|
LEFT JOIN shelving sh ON sh.code = ish.shelvingFk
|
||||||
|
LEFT JOIN parking p ON p.id = sh.parkingFk
|
||||||
|
LEFT JOIN itemColor ic ON ic.itemFk = s.itemFk
|
||||||
|
LEFT JOIN origin o ON o.id = i.originFk
|
||||||
|
WHERE tc.collectionFk = ?
|
||||||
|
GROUP BY ish.id, p.code, p2.code
|
||||||
|
ORDER BY pickingOrder;`, [id], myOptions);
|
||||||
|
|
||||||
|
if (print)
|
||||||
|
await Self.rawSql(`CALL vn.collection_printSticker(?, ?)`, [id, null], myOptions);
|
||||||
|
|
||||||
|
const collection = {collectionFk: id, tickets: []};
|
||||||
|
if (tickets && tickets.length) {
|
||||||
|
for (const ticket of tickets) {
|
||||||
|
const ticketId = ticket.ticketFk;
|
||||||
|
if (ticket.observaciones != '') {
|
||||||
|
for (observation of ticket.observaciones.split(' ')) {
|
||||||
|
if (['#', '@'].includes(observation.charAt(0))) {
|
||||||
|
promises.push(Self.app.models.Chat.send(ctx, observation,
|
||||||
|
$t('The ticket is in preparation', {
|
||||||
|
ticketId: ticketId,
|
||||||
|
ticketUrl: `${url}ticket/${ticketId}/summary`,
|
||||||
|
salesPersonId: ticket.salesPersonFk
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sales && sales.length) {
|
||||||
|
const barcodes = await Self.rawSql(`
|
||||||
|
SELECT s.id saleFk, b.code, c.id
|
||||||
|
FROM vn.sale s
|
||||||
|
LEFT JOIN vn.itemBarcode b ON b.itemFk = s.itemFk
|
||||||
|
LEFT JOIN vn.buy c ON c.itemFk = s.itemFk
|
||||||
|
LEFT JOIN vn.entry e ON e.id = c.entryFk
|
||||||
|
LEFT JOIN vn.travel tr ON tr.id = e.travelFk
|
||||||
|
WHERE s.ticketFk = ?
|
||||||
|
AND tr.landed >= util.VN_CURDATE() - INTERVAL 1 YEAR`,
|
||||||
|
[ticketId], myOptions);
|
||||||
|
ticket.sales = [];
|
||||||
|
for (const sale of sales) {
|
||||||
|
if (sale.ticketFk === ticketId) {
|
||||||
|
sale.Barcodes = [];
|
||||||
|
if (barcodes && barcodes.length) {
|
||||||
|
for (const barcode of barcodes) {
|
||||||
|
if (barcode.saleFk === sale.saleFk) {
|
||||||
|
for (const prop in barcode) {
|
||||||
|
if (['id', 'code'].includes(prop) && barcode[prop])
|
||||||
|
sale.Barcodes.push(barcode[prop].toString(), '0' + barcode[prop]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ticket.sales.push(sale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
collection.tickets.push(ticket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Promise.all(promises);
|
||||||
|
return collection;
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,39 @@
|
||||||
|
const models = require('vn-loopback/server/server').models;
|
||||||
|
|
||||||
|
describe('collection getTickets()', () => {
|
||||||
|
let ctx;
|
||||||
|
beforeAll(async() => {
|
||||||
|
ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'http://localhost'}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should get tickets, sales and barcodes from collection', async() => {
|
||||||
|
const tx = await models.Collection.beginTransaction({});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
const collectionId = 1;
|
||||||
|
|
||||||
|
const collectionTickets = await models.Collection.getTickets(ctx, collectionId, null, options);
|
||||||
|
|
||||||
|
expect(collectionTickets.collectionFk).toEqual(collectionId);
|
||||||
|
expect(collectionTickets.tickets.length).toEqual(3);
|
||||||
|
expect(collectionTickets.tickets[0].ticketFk).toEqual(1);
|
||||||
|
expect(collectionTickets.tickets[1].ticketFk).toEqual(2);
|
||||||
|
expect(collectionTickets.tickets[2].ticketFk).toEqual(23);
|
||||||
|
expect(collectionTickets.tickets[0].sales[0].ticketFk).toEqual(1);
|
||||||
|
expect(collectionTickets.tickets[0].sales[1].ticketFk).toEqual(1);
|
||||||
|
expect(collectionTickets.tickets[0].sales[2].ticketFk).toEqual(1);
|
||||||
|
expect(collectionTickets.tickets[0].sales[0].Barcodes.length).toBeTruthy();
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -18,6 +18,14 @@ describe('setSaleQuantity()', () => {
|
||||||
|
|
||||||
it('should change quantity sale', async() => {
|
it('should change quantity sale', async() => {
|
||||||
const tx = await models.Ticket.beginTransaction({});
|
const tx = await models.Ticket.beginTransaction({});
|
||||||
|
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
|
||||||
|
if (sqlStatement.includes('catalog_calcFromItem')) {
|
||||||
|
sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY
|
||||||
|
SELECT 100 as available;`;
|
||||||
|
params = null;
|
||||||
|
}
|
||||||
|
return models.Ticket.rawSql(sqlStatement, params, options);
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const options = {transaction: tx};
|
const options = {transaction: tx};
|
||||||
|
|
|
@ -71,11 +71,10 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await Self.get(fileCabinet, filter);
|
const [response] = await Self.get(fileCabinet, filter);
|
||||||
const [documents] = response.Items;
|
if (!response) return false;
|
||||||
if (!documents) return false;
|
|
||||||
|
|
||||||
return {id: documents.Id};
|
return {id: response['Document ID']};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ module.exports = Self => {
|
||||||
|
|
||||||
const email = new Email('delivery-note', params);
|
const email = new Email('delivery-note', params);
|
||||||
|
|
||||||
const docuwareFile = await models.Docuware.download(ctx, id, 'deliveryNote');
|
const docuwareFile = await models.Docuware.download(id, 'deliveryNote');
|
||||||
|
|
||||||
return email.send({
|
return email.send({
|
||||||
overrideAttachments: true,
|
overrideAttachments: true,
|
||||||
|
|
|
@ -16,19 +16,9 @@ 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 = [{
|
||||||
Items: [
|
'Document ID': docuwareId
|
||||||
{
|
}];
|
||||||
Id: docuwareId,
|
|
||||||
Fields: [
|
|
||||||
{
|
|
||||||
FieldName: 'ESTADO',
|
|
||||||
Item: 'Firmado'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
||||||
spyOn(docuwareModel, 'get').and.returnValue((new Promise(resolve => resolve(response))));
|
spyOn(docuwareModel, 'get').and.returnValue((new Promise(resolve => resolve(response))));
|
||||||
|
|
||||||
const result = await models.Docuware.checkFile(ticketId, fileCabinetName, null, true);
|
const result = await models.Docuware.checkFile(ticketId, fileCabinetName, null, true);
|
||||||
|
|
|
@ -111,7 +111,7 @@ module.exports = Self => {
|
||||||
throw new UserError('Action not allowed on the test environment');
|
throw new UserError('Action not allowed on the test environment');
|
||||||
|
|
||||||
// delete old
|
// delete old
|
||||||
const docuwareFile = await models.Docuware.checkFile(ctx, id, fileCabinet, false);
|
const docuwareFile = await models.Docuware.checkFile(id, fileCabinet, false);
|
||||||
if (docuwareFile) {
|
if (docuwareFile) {
|
||||||
const deleteJson = {
|
const deleteJson = {
|
||||||
'Field': [{'FieldName': 'ESTADO', 'Item': 'Pendiente eliminar', 'ItemElementName': 'String'}]
|
'Field': [{'FieldName': 'ESTADO', 'Item': 'Pendiente eliminar', 'ItemElementName': 'String'}]
|
||||||
|
|
|
@ -139,7 +139,7 @@ module.exports = Self => {
|
||||||
ftpClient.exec((err, response) => {
|
ftpClient.exec((err, response) => {
|
||||||
if (err || response.error) {
|
if (err || response.error) {
|
||||||
console.debug(`Error downloading checksum file... ${response.error}`);
|
console.debug(`Error downloading checksum file... ${response.error}`);
|
||||||
return reject(err);
|
return reject(response.error || err);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(response);
|
resolve(response);
|
||||||
|
|
|
@ -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/>`;
|
||||||
|
html += `<strong>Usuario</strong>:<br/>${ctx.req.accessToken.userId} ${emailUser.email}<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}, ${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, ' ').split('\n').join('<br>');
|
||||||
|
} catch {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,40 @@
|
||||||
|
module.exports = function(Self) {
|
||||||
|
Self.remoteMethod('getByUser', {
|
||||||
|
description: 'returns the starred modules for the current user',
|
||||||
|
accessType: 'READ',
|
||||||
|
accepts: [{
|
||||||
|
arg: 'userId',
|
||||||
|
type: 'number',
|
||||||
|
description: 'The user id',
|
||||||
|
required: true,
|
||||||
|
http: {source: 'path'}
|
||||||
|
}],
|
||||||
|
returns: {
|
||||||
|
type: 'object',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/:userId/get-by-user`,
|
||||||
|
verb: 'GET'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.getByUser = async userId => {
|
||||||
|
const models = Self.app.models;
|
||||||
|
const appNames = ['hedera'];
|
||||||
|
const filter = {
|
||||||
|
fields: ['appName', 'url'],
|
||||||
|
where: {
|
||||||
|
appName: {inq: appNames},
|
||||||
|
environment: process.env.NODE_ENV ?? 'development',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isWorker = await models.Account.findById(userId, {fields: ['id']});
|
||||||
|
if (!isWorker)
|
||||||
|
return models.Url.find(filter);
|
||||||
|
|
||||||
|
appNames.push('salix');
|
||||||
|
return models.Url.find(filter);
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,30 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('getUrl', {
|
||||||
|
description: 'Returns the colling app name',
|
||||||
|
accessType: 'READ',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'app',
|
||||||
|
type: 'string',
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: 'object',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/getUrl`,
|
||||||
|
verb: 'get'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Self.getUrl = async(appName = 'salix') => {
|
||||||
|
const {url} = await Self.app.models.Url.findOne({
|
||||||
|
where: {
|
||||||
|
appName,
|
||||||
|
enviroment: process.env.NODE_ENV || 'development'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,19 @@
|
||||||
|
const {models} = require('vn-loopback/server/server');
|
||||||
|
|
||||||
|
describe('getByUser()', () => {
|
||||||
|
const worker = 1;
|
||||||
|
const notWorker = 2;
|
||||||
|
it(`should return only hedera url if not is worker`, async() => {
|
||||||
|
const urls = await models.Url.getByUser(notWorker);
|
||||||
|
|
||||||
|
expect(urls.length).toEqual(1);
|
||||||
|
expect(urls[0].appName).toEqual('hedera');
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should return more than hedera url`, async() => {
|
||||||
|
const urls = await models.Url.getByUser(worker);
|
||||||
|
|
||||||
|
expect(urls.length).toBeGreaterThan(1);
|
||||||
|
expect(urls.find(url => url.appName == 'salix').appName).toEqual('salix');
|
||||||
|
});
|
||||||
|
});
|
|
@ -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;
|
||||||
|
};
|
||||||
|
};
|
|
@ -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;
|
||||||
|
};
|
||||||
|
};
|
|
@ -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>
|
|
@ -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: {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethodCtx('updateUser', {
|
||||||
|
description: 'Update user data',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'id',
|
||||||
|
type: 'integer',
|
||||||
|
description: 'The user id',
|
||||||
|
required: true,
|
||||||
|
http: {source: 'path'}
|
||||||
|
}, {
|
||||||
|
arg: 'name',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The user name',
|
||||||
|
}, {
|
||||||
|
arg: 'nickname',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The user nickname',
|
||||||
|
}, {
|
||||||
|
arg: 'email',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The user email'
|
||||||
|
}, {
|
||||||
|
arg: 'lang',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The user lang'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
http: {
|
||||||
|
path: `/:id/update-user`,
|
||||||
|
verb: 'PATCH'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.updateUser = async(ctx, id, name, nickname, email, lang) => {
|
||||||
|
await Self.userSecurity(ctx, id);
|
||||||
|
await Self.upsertWithWhere({id}, {name, nickname, email, lang});
|
||||||
|
};
|
||||||
|
};
|
|
@ -15,6 +15,9 @@
|
||||||
},
|
},
|
||||||
"Bank": {
|
"Bank": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"Buyer": {
|
||||||
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
"Campaign": {
|
"Campaign": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
|
@ -150,6 +153,9 @@
|
||||||
},
|
},
|
||||||
"PrintConfig": {
|
"PrintConfig": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"ViaexpressConfig": {
|
||||||
|
"dataSource": "vn"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"name": "Buyer",
|
||||||
|
"base": "VnModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "buyer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"userFk": {
|
||||||
|
"type": "number",
|
||||||
|
"required": true,
|
||||||
|
"id": true
|
||||||
|
},
|
||||||
|
"nickname": {
|
||||||
|
"type": "string",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"acls": [
|
||||||
|
{
|
||||||
|
"accessType": "READ",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "employee",
|
||||||
|
"permission": "ALLOW"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -7,17 +7,14 @@ module.exports = Self => {
|
||||||
|
|
||||||
Self.observe('before save', async function(ctx) {
|
Self.observe('before save', async function(ctx) {
|
||||||
if (!ctx.isNewInstance) return;
|
if (!ctx.isNewInstance) return;
|
||||||
|
|
||||||
let {message} = ctx.instance;
|
let {message} = ctx.instance;
|
||||||
if (!message) return;
|
if (!message) return;
|
||||||
|
|
||||||
const parts = message.match(/(?<=\[)[a-zA-Z0-9_\-+!@#$%^&*()={};':"\\|,.<>/?\s]*(?=])/g);
|
const parts = message.match(/(?<=\[)[a-zA-Z0-9_\-+!@#$%^&*()={};':"\\|,.<>/?\s]*(?=])/g);
|
||||||
if (!parts) return;
|
if (!parts) return;
|
||||||
|
|
||||||
const replacedParts = parts.map(part => {
|
const replacedParts = parts.map(part => {
|
||||||
return part.replace(/[!$%^&*()={};':"\\,.<>/?]/g, '');
|
return part.replace(/[!$%^&*()={};':"\\,.<>/?]/g, '');
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const [index, part] of parts.entries())
|
for (const [index, part] of parts.entries())
|
||||||
message = message.replace(part, replacedParts[index]);
|
message = message.replace(part, replacedParts[index]);
|
||||||
|
|
||||||
|
|
|
@ -4,4 +4,5 @@ module.exports = Self => {
|
||||||
require('../methods/collection/getSectors')(Self);
|
require('../methods/collection/getSectors')(Self);
|
||||||
require('../methods/collection/setSaleQuantity')(Self);
|
require('../methods/collection/setSaleQuantity')(Self);
|
||||||
require('../methods/collection/previousLabel')(Self);
|
require('../methods/collection/previousLabel')(Self);
|
||||||
|
require('../methods/collection/getTickets')(Self);
|
||||||
};
|
};
|
||||||
|
|
|
@ -27,5 +27,12 @@
|
||||||
"where" :{
|
"where" :{
|
||||||
"expired": null
|
"expired": null
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"relations": {
|
||||||
|
"client": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "Client",
|
||||||
|
"foreignKey": "clientFk"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const models = require('vn-loopback/server/server').models;
|
const models = require('vn-loopback/server/server').models;
|
||||||
|
const ForbiddenError = require('vn-loopback/util/forbiddenError');
|
||||||
|
|
||||||
describe('loopback model VnUser', () => {
|
describe('loopback model VnUser', () => {
|
||||||
it('should return true if the user has the given role', async() => {
|
it('should return true if the user has the given role', async() => {
|
||||||
|
@ -12,4 +13,42 @@ describe('loopback model VnUser', () => {
|
||||||
|
|
||||||
expect(result).toBeFalsy();
|
expect(result).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('userSecurity', () => {
|
||||||
|
const itManagementId = 115;
|
||||||
|
const hrId = 37;
|
||||||
|
const employeeId = 1;
|
||||||
|
|
||||||
|
it('should check if you are the same user', async() => {
|
||||||
|
const ctx = {options: {accessToken: {userId: employeeId}}};
|
||||||
|
await models.VnUser.userSecurity(ctx, employeeId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check for higher privileges', async() => {
|
||||||
|
const ctx = {options: {accessToken: {userId: itManagementId}}};
|
||||||
|
await models.VnUser.userSecurity(ctx, employeeId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check if you have medium privileges and the user email is not verified', async() => {
|
||||||
|
const ctx = {options: {accessToken: {userId: hrId}}};
|
||||||
|
await models.VnUser.userSecurity(ctx, employeeId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if you have medium privileges and the users email is verified', async() => {
|
||||||
|
const tx = await models.VnUser.beginTransaction({});
|
||||||
|
const ctx = {options: {accessToken: {userId: hrId}}};
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
const userToUpdate = await models.VnUser.findById(1, null, options);
|
||||||
|
userToUpdate.updateAttribute('emailVerified', 1, options);
|
||||||
|
|
||||||
|
await models.VnUser.userSecurity(ctx, employeeId, options);
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (error) {
|
||||||
|
await tx.rollback();
|
||||||
|
|
||||||
|
expect(error).toEqual(new ForbiddenError());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
require('../methods/url/getByUser')(Self);
|
||||||
|
require('../methods/url/getUrl')(Self);
|
||||||
|
};
|
|
@ -0,0 +1,4 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
require('../methods/viaexpress-config/internationalExpedition')(Self);
|
||||||
|
require('../methods/viaexpress-config/renderer')(Self);
|
||||||
|
};
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
const vnModel = require('vn-loopback/common/models/vn-model');
|
const vnModel = require('vn-loopback/common/models/vn-model');
|
||||||
const LoopBackContext = require('loopback-context');
|
|
||||||
const {Email} = require('vn-print');
|
const {Email} = require('vn-print');
|
||||||
|
const ForbiddenError = require('vn-loopback/util/forbiddenError');
|
||||||
|
const LoopBackContext = require('loopback-context');
|
||||||
|
|
||||||
module.exports = function(Self) {
|
module.exports = function(Self) {
|
||||||
vnModel(Self);
|
vnModel(Self);
|
||||||
|
@ -12,6 +13,7 @@ module.exports = function(Self) {
|
||||||
require('../methods/vn-user/privileges')(Self);
|
require('../methods/vn-user/privileges')(Self);
|
||||||
require('../methods/vn-user/validate-auth')(Self);
|
require('../methods/vn-user/validate-auth')(Self);
|
||||||
require('../methods/vn-user/renew-token')(Self);
|
require('../methods/vn-user/renew-token')(Self);
|
||||||
|
require('../methods/vn-user/update-user')(Self);
|
||||||
|
|
||||||
Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create');
|
Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create');
|
||||||
|
|
||||||
|
@ -20,7 +22,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|-]+)*)*$/
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -90,17 +92,23 @@ module.exports = function(Self) {
|
||||||
};
|
};
|
||||||
|
|
||||||
Self.on('resetPasswordRequest', async function(info) {
|
Self.on('resetPasswordRequest', async function(info) {
|
||||||
const loopBackContext = LoopBackContext.getCurrentContext();
|
const url = await Self.app.models.Url.getUrl();
|
||||||
const httpCtx = {req: loopBackContext.active};
|
|
||||||
const httpRequest = httpCtx.req.http.req;
|
const defaultHash = '/reset-password?access_token=$token$';
|
||||||
const headers = httpRequest.headers;
|
const recoverHashes = {
|
||||||
const origin = headers.origin;
|
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: url.slice(0, -1) + recoverHash
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = Object.assign({}, info.options);
|
const options = Object.assign({}, info.options);
|
||||||
|
@ -115,6 +123,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};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -160,45 +176,75 @@ module.exports = function(Self) {
|
||||||
Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls
|
Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls
|
||||||
.filter(acl => acl.property != 'changePassword');
|
.filter(acl => acl.property != 'changePassword');
|
||||||
|
|
||||||
// FIXME: https://redmine.verdnatura.es/issues/5761
|
Self.userSecurity = async(ctx, userId, options) => {
|
||||||
// Self.afterRemote('prototype.patchAttributes', async(ctx, instance) => {
|
const models = Self.app.models;
|
||||||
// if (!ctx.args || !ctx.args.data.email) return;
|
const accessToken = ctx?.options?.accessToken || LoopBackContext.getCurrentContext().active.accessToken;
|
||||||
|
const ctxToken = {req: {accessToken}};
|
||||||
|
|
||||||
// const loopBackContext = LoopBackContext.getCurrentContext();
|
if (userId === accessToken.userId) return;
|
||||||
// const httpCtx = {req: loopBackContext.active};
|
|
||||||
// const httpRequest = httpCtx.req.http.req;
|
|
||||||
// const headers = httpRequest.headers;
|
|
||||||
// const origin = headers.origin;
|
|
||||||
// const url = origin.split(':');
|
|
||||||
|
|
||||||
// class Mailer {
|
const myOptions = {};
|
||||||
// async send(verifyOptions, cb) {
|
if (typeof options == 'object')
|
||||||
// const params = {
|
Object.assign(myOptions, options);
|
||||||
// url: verifyOptions.verifyHref,
|
|
||||||
// recipient: verifyOptions.to,
|
|
||||||
// lang: ctx.req.getLocale()
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const email = new Email('email-verify', params);
|
const hasHigherPrivileges = await models.ACL.checkAccessAcl(ctxToken, 'VnUser', 'higherPrivileges', myOptions);
|
||||||
// email.send();
|
if (hasHigherPrivileges) return;
|
||||||
|
|
||||||
// cb(null, verifyOptions.to);
|
const hasMediumPrivileges = await models.ACL.checkAccessAcl(ctxToken, 'VnUser', 'mediumPrivileges', myOptions);
|
||||||
// }
|
const user = await models.VnUser.findById(userId, {fields: ['id', 'emailVerified']}, myOptions);
|
||||||
// }
|
if (!user.emailVerified && hasMediumPrivileges) return;
|
||||||
|
|
||||||
// const options = {
|
throw new ForbiddenError();
|
||||||
// type: 'email',
|
};
|
||||||
// to: instance.email,
|
|
||||||
// from: {},
|
|
||||||
// redirect: `${origin}/#!/account/${instance.id}/basic-data?emailConfirmed`,
|
|
||||||
// template: false,
|
|
||||||
// mailer: new Mailer,
|
|
||||||
// host: url[1].split('/')[2],
|
|
||||||
// port: url[2],
|
|
||||||
// protocol: url[0],
|
|
||||||
// user: Self
|
|
||||||
// };
|
|
||||||
|
|
||||||
// await instance.verify(options);
|
Self.observe('after save', async ctx => {
|
||||||
// });
|
const instance = ctx?.instance;
|
||||||
|
const newEmail = instance?.email;
|
||||||
|
const oldEmail = ctx?.hookState?.oldInstance?.email;
|
||||||
|
if (!ctx.isNewInstance && (!newEmail || !oldEmail || newEmail == oldEmail)) return;
|
||||||
|
|
||||||
|
const loopBackContext = LoopBackContext.getCurrentContext();
|
||||||
|
const httpCtx = {req: loopBackContext.active};
|
||||||
|
const httpRequest = httpCtx.req.http.req;
|
||||||
|
const headers = httpRequest.headers;
|
||||||
|
const origin = headers.origin;
|
||||||
|
const url = origin.split(':');
|
||||||
|
|
||||||
|
const env = process.env.NODE_ENV;
|
||||||
|
const liliumUrl = await Self.app.models.Url.findOne({
|
||||||
|
where: {and: [
|
||||||
|
{appName: 'lilium'},
|
||||||
|
{environment: env}
|
||||||
|
]}
|
||||||
|
});
|
||||||
|
|
||||||
|
class Mailer {
|
||||||
|
async send(verifyOptions, cb) {
|
||||||
|
const params = {
|
||||||
|
url: verifyOptions.verifyHref,
|
||||||
|
recipient: verifyOptions.to
|
||||||
|
};
|
||||||
|
|
||||||
|
const email = new Email('email-verify', params);
|
||||||
|
email.send();
|
||||||
|
|
||||||
|
cb(null, verifyOptions.to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
type: 'email',
|
||||||
|
to: newEmail,
|
||||||
|
from: {},
|
||||||
|
redirect: `${liliumUrl.url}verifyEmail?userId=${instance.id}`,
|
||||||
|
template: false,
|
||||||
|
mailer: new Mailer,
|
||||||
|
host: url[1].split('/')[2],
|
||||||
|
port: url[2],
|
||||||
|
protocol: url[0],
|
||||||
|
user: Self
|
||||||
|
};
|
||||||
|
|
||||||
|
await instance.verify(options, ctx.options);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,19 +13,12 @@
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"id": true
|
"id": true
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
"username": {
|
"username": {
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"mysql": {
|
|
||||||
"columnName": "name"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"password": {
|
|
||||||
"type": "string",
|
|
||||||
"required": true
|
|
||||||
},
|
},
|
||||||
"roleFk": {
|
"roleFk": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
@ -45,6 +38,9 @@
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"emailVerified": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"created": {
|
"created": {
|
||||||
"type": "date"
|
"type": "date"
|
||||||
},
|
},
|
||||||
|
@ -84,7 +80,7 @@
|
||||||
"worker": {
|
"worker": {
|
||||||
"type": "hasOne",
|
"type": "hasOne",
|
||||||
"model": "Worker",
|
"model": "Worker",
|
||||||
"foreignKey": "userFk"
|
"foreignKey": "id"
|
||||||
},
|
},
|
||||||
"userConfig": {
|
"userConfig": {
|
||||||
"type": "hasOne",
|
"type": "hasOne",
|
||||||
|
@ -144,7 +140,8 @@
|
||||||
"image",
|
"image",
|
||||||
"hasGrant",
|
"hasGrant",
|
||||||
"realm",
|
"realm",
|
||||||
"email"
|
"email",
|
||||||
|
"emailVerified"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ BEGIN
|
||||||
isAllowedToWork
|
isAllowedToWork
|
||||||
FROM(SELECT t.dated,
|
FROM(SELECT t.dated,
|
||||||
b.id businessFk,
|
b.id businessFk,
|
||||||
w.userFk,
|
w.id,
|
||||||
b.departmentFk,
|
b.departmentFk,
|
||||||
IF(j.start = NULL, NULL, GROUP_CONCAT(DISTINCT LEFT(j.start,5) ORDER BY j.start ASC SEPARATOR ' - ')) hourStart ,
|
IF(j.start = NULL, NULL, GROUP_CONCAT(DISTINCT LEFT(j.start,5) ORDER BY j.start ASC SEPARATOR ' - ')) hourStart ,
|
||||||
IF(j.start = NULL, NULL, GROUP_CONCAT(DISTINCT LEFT(j.end,5) ORDER BY j.end ASC SEPARATOR ' - ')) hourEnd,
|
IF(j.start = NULL, NULL, GROUP_CONCAT(DISTINCT LEFT(j.end,5) ORDER BY j.end ASC SEPARATOR ' - ')) hourEnd,
|
||||||
|
@ -48,14 +48,14 @@ BEGIN
|
||||||
FROM time t
|
FROM time t
|
||||||
LEFT JOIN business b ON t.dated BETWEEN b.started AND IFNULL(b.ended, vDatedTo)
|
LEFT JOIN business b ON t.dated BETWEEN b.started AND IFNULL(b.ended, vDatedTo)
|
||||||
LEFT JOIN worker w ON w.id = b.workerFk
|
LEFT JOIN worker w ON w.id = b.workerFk
|
||||||
JOIN tmp.`user` u ON u.userFK = w.userFK
|
JOIN tmp.`user` u ON u.userFK = w.id
|
||||||
LEFT JOIN workCenter wc ON wc.id = b.workcenterFK
|
LEFT JOIN workCenter wc ON wc.id = b.workcenterFK
|
||||||
LEFT JOIN postgresql.calendar_labour_type cl ON cl.calendar_labour_type_id = b.calendarTypeFk
|
LEFT JOIN postgresql.calendar_labour_type cl ON cl.calendar_labour_type_id = b.calendarTypeFk
|
||||||
LEFT JOIN postgresql.journey j ON j.business_id = b.id AND j.day_id = WEEKDAY(t.dated) + 1
|
LEFT JOIN postgresql.journey j ON j.business_id = b.id AND j.day_id = WEEKDAY(t.dated) + 1
|
||||||
LEFT JOIN postgresql.calendar_employee ce ON ce.businessFk = b.id AND ce.date = t.dated
|
LEFT JOIN postgresql.calendar_employee ce ON ce.businessFk = b.id AND ce.date = t.dated
|
||||||
LEFT JOIN absenceType at2 ON at2.id = ce.calendar_state_id
|
LEFT JOIN absenceType at2 ON at2.id = ce.calendar_state_id
|
||||||
WHERE t.dated BETWEEN vDatedFrom AND vDatedTo
|
WHERE t.dated BETWEEN vDatedFrom AND vDatedTo
|
||||||
GROUP BY w.userFk, t.dated
|
GROUP BY w.id, t.dated
|
||||||
)sub;
|
)sub;
|
||||||
|
|
||||||
UPDATE tmp.timeBusinessCalculate t
|
UPDATE tmp.timeBusinessCalculate t
|
||||||
|
|
|
@ -46,7 +46,7 @@ BEGIN
|
||||||
CONCAT('Cliente ', NEW.id),
|
CONCAT('Cliente ', NEW.id),
|
||||||
CONCAT('Recibida la documentación: ', vText)
|
CONCAT('Recibida la documentación: ', vText)
|
||||||
FROM worker w
|
FROM worker w
|
||||||
LEFT JOIN account.user u ON w.userFk = u.id AND u.active
|
LEFT JOIN account.user u ON w.id = u.id AND u.active
|
||||||
LEFT JOIN account.account ac ON ac.id = u.id
|
LEFT JOIN account.account ac ON ac.id = u.id
|
||||||
WHERE w.id = NEW.salesPersonFk;
|
WHERE w.id = NEW.salesPersonFk;
|
||||||
END IF;
|
END IF;
|
|
@ -3,11 +3,11 @@ INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `pri
|
||||||
('Ticket', 'editDiscount', 'WRITE', 'ALLOW', 'ROLE', 'claimManager'),
|
('Ticket', 'editDiscount', 'WRITE', 'ALLOW', 'ROLE', 'claimManager'),
|
||||||
('Ticket', 'editDiscount', 'WRITE', 'ALLOW', 'ROLE', 'salesPerson'),
|
('Ticket', 'editDiscount', 'WRITE', 'ALLOW', 'ROLE', 'salesPerson'),
|
||||||
('Ticket', 'isRoleAdvanced', '*', 'ALLOW', 'ROLE', 'salesAssistant'),
|
('Ticket', 'isRoleAdvanced', '*', 'ALLOW', 'ROLE', 'salesAssistant'),
|
||||||
('Ticket', 'isRoleAdvanced', '*', 'ALLOW', 'ROLE', 'deliveryBoss'),
|
('Ticket', 'isRoleAdvanced', '*', 'ALLOW', 'ROLE', 'deliveryAssistant'),
|
||||||
('Ticket', 'isRoleAdvanced', '*', 'ALLOW', 'ROLE', 'buyer'),
|
('Ticket', 'isRoleAdvanced', '*', 'ALLOW', 'ROLE', 'buyer'),
|
||||||
('Ticket', 'isRoleAdvanced', '*', 'ALLOW', 'ROLE', 'claimManager'),
|
('Ticket', 'isRoleAdvanced', '*', 'ALLOW', 'ROLE', 'claimManager'),
|
||||||
('Ticket', 'deleteTicketWithPartPrepared', 'WRITE', 'ALLOW', 'ROLE', 'salesAssistant'),
|
('Ticket', 'deleteTicketWithPartPrepared', 'WRITE', 'ALLOW', 'ROLE', 'salesAssistant'),
|
||||||
('Ticket', 'editZone', 'WRITE', 'ALLOW', 'ROLE', 'deliveryBoss'),
|
('Ticket', 'editZone', 'WRITE', 'ALLOW', 'ROLE', 'deliveryAssistant'),
|
||||||
('State', 'editableStates', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
('State', 'editableStates', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||||
('State', 'seeEditableStates', 'READ', 'ALLOW', 'ROLE', 'administrative'),
|
('State', 'seeEditableStates', 'READ', 'ALLOW', 'ROLE', 'administrative'),
|
||||||
('State', 'seeEditableStates', 'READ', 'ALLOW', 'ROLE', 'production'),
|
('State', 'seeEditableStates', 'READ', 'ALLOW', 'ROLE', 'production'),
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue