Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 5112-entryUpdateComission
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Robert Ferrús 2023-06-12 10:25:46 +02:00
commit 77c7e58f80
1503 changed files with 51385 additions and 49980 deletions

View File

@ -1,6 +1,6 @@
extends: [eslint:recommended, google, plugin:jasmine/recommended] extends: [eslint:recommended, google, plugin:jasmine/recommended]
parserOptions: parserOptions:
ecmaVersion: 2018 ecmaVersion: 2020
sourceType: "module" sourceType: "module"
plugins: plugins:
- jasmine - jasmine
@ -35,4 +35,4 @@ rules:
space-in-parens: ["error", "never"] space-in-parens: ["error", "never"]
jasmine/no-focused-tests: 0 jasmine/no-focused-tests: 0
jasmine/prefer-toHaveBeenCalledWith: 0 jasmine/prefer-toHaveBeenCalledWith: 0
arrow-spacing: ["error", { "before": true, "after": true }] arrow-spacing: ["error", { "before": true, "after": true }]

View File

@ -4,5 +4,11 @@
"files.eol": "\n", "files.eol": "\n",
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": true "source.fixAll.eslint": true
},
"search.useIgnoreFiles": false,
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"eslint.format.enable": true,
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
} }
} }

View File

@ -5,19 +5,172 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2302.01] - 2023-01-12 ## [2326.01] - 2023-06-29
### Added ### Added
- [General](Inicio) Permite recuperar la contraseña
- [Ticket](Opciones) Subir albarán a Docuware
- [Ticket](Opciones) Enviar correo con PDF de Docuware
- [Artículo](Datos Básicos) Añadido campo Unidades/Caja
### Changed ### Changed
- [Tickets](Líneas preparadas) Actualizada sección para que sea más visual
### Fixed ### Fixed
- [General] Al utilizar el traductor de Google se descuadraban los iconos -
## [2324.01] - 2023-06-08
### Added
- (Tickets -> Abono) Al abonar permite crear el ticket abono con almacén o sin almmacén
- (General -> Desplegables) Mejorada eficiencia de carga de datos
- (General -> Históricos) Ahora, ademas de los ids, se muestra la descripión de los atributos
- (General -> Históricos) Botón para hacer más ágil mostrar sólo los cambios en un registro
- (General -> Históricos) Filtro por cambios
### Changed
- (General -> Permisos) Mejorada seguridad
- (General -> Históricos) Elementos de la interfaz reorganizados para hacerla más ágil e intuitiva
### Fixed
-
## [2322.01] - 2023-06-01
### Added
- (Tickets -> Crear Factura) Al facturar se envia automáticamente el pdf al cliente
- (Artículos -> Histórico) Filtro para mostrar lo anterior al inventario
- (Trabajadores -> Nuevo trabajador) Permite elegir el método de pago
### Changed
- (Trabajadores -> Nuevo trabajador) Los clientes se crean sin 'TR' pero se añade tipo de negocio 'Trabajador'
### Fixed
- (Tickets -> Líneas) Se permite hacer split de líneas al mismo ticket
- (Tickets -> Cambiar estado) Ahora muestra la lista completa de todos los estados
## [2320.01] - 2023-05-25
### Added
- (Tickets -> Crear Factura) Al facturar se envia automáticamente el pdf al cliente
### Changed
- (Trabajadores -> Nuevo trabajador) Los clientes se crean sin 'TR' pero se añade tipo de negocio 'Trabajador'
### Fixed
-
## [2318.01] - 2023-05-08
### Added
- (Usuarios -> Histórico) Nueva sección
- (Roles -> Histórico) Nueva sección
- (Trabajadores -> Dar de alta) Permite elegir el método de pago
### Changed
- (Artículo -> Precio fijado) Modificado el buscador superior por uno lateral
- (Trabajadores -> Dar de alta) Quitada obligatoriedad del iban
### Fixed
- (Ticket -> Boxing) Arreglado selección de horas
- (Cesta -> Índice) Optimizada búsqueda
## [2314.01] - 2023-04-20
### Added
- (Clientes -> Morosos) Ahora se puede filtrar por las columnas "Desde" y "Fecha Ú. O.". También se envia un email al comercial cuando se añade una nota.
- (Monitor tickets) Muestra un icono al lado de la zona, si el ticket es frágil y se envía por agencia
- (Facturas recibidas -> Bases negativas) Nueva sección
### Fixed
- (Clientes -> Morosos) Ahora se mantienen los elementos seleccionados al hacer sroll.
## [2312.01] - 2023-04-06
### Added
- (Monitor tickets) Muestra un icono al lado de la zona, si el ticket es frágil y se envía por agencia
### Changed
- (Monitor tickets) Cuando se filtra por 'Pendiente' ya no muestra los estados de 'Previa'
- (Envíos -> Extra comunitarios) Se agrupan las entradas del mismo travel. Añadidos campos Referencia y Importe.
- (Envíos -> Índice) Cambiado el buscador superior por uno lateral
## [2310.01] - 2023-03-23
### Added
- (Trabajadores -> Control de horario) Ahora se puede confirmar/no confirmar el registro horario de cada semana desde esta sección
### Fixed
- (Clientes -> Listado extendido) Resuelto error al filtrar por clientes inactivos desde la columna "Activo"
- (General) Al pasar el ratón por encima del icono de "Borrar" en un campo, se hacía más grande afectando a la interfaz
## [2308.01] - 2023-03-09
### Added
- (Proveedores -> Datos fiscales) Añadido checkbox 'Vies'
- (Client -> Descriptor) Nuevo icono $ con barrotes para los clientes con impago
- (Trabajador -> Datos Básicos) Añadido nuevo campo Taquilla
- (Trabajador -> PDA) Nueva sección
### Changed
- (Ticket -> Borrar ticket) Restringido el borrado de tickets con abono
## [2306.01] - 2023-02-23
### Added
- (Tickets -> Datos Básicos) Mensaje de confirmación al intentar generar tickets con negativos
- (Artículos) El visible y disponible se calcula a partir de un almacén diferente dependiendo de la sección en la que te encuentres. Se ha añadido un icono que informa sobre a partir de que almacén se esta calculando.
### Changed
- (General -> Inicio) Ahora permite recuperar la contraseña tanto con el correo de recuperación como el usuario
### Fixed
- (Monitor de tickets) Cuando ordenas por columna, ya no se queda deshabilitado el botón de 'Actualizar'
- (Zone -> Días de entrega) Al hacer click en un día, muestra correctamente las zonas
- (Artículos) El disponible en la vista previa se muestra correctamente
## [2304.01] - 2023-02-09
### Added
- (Rutas) Al descargar varias facturas se comprime en un zip
- (Trabajadores -> Nuevo trabajador) Nueva sección
- (Tickets -> Adelantar tickets) Añadidos campos "líneas" y "litros" al ticket origen
- (Tickets -> Adelantar tickets) Nuevo icono muestra cuando las agencias de los tickets origen/destino son distintas
### Changed
- (Entradas -> Compras) Cambiados los campos "Precio Grouping/Packing" por "PVP" y "Precio" por "Coste"
- (Artículos -> Últimas entradas) Cambiados los campos "P.P.U." y "P.P.P." por "PVP"
- (Rutas -> Sumario/Tickets) Actualizados campos de los tickets
- (Proveedores -> Crear/Editar) Permite añadir Proveedores con la misma razón social pero con países distintos
- (Tickets -> Adelantar tickets) Cambiados selectores de estado por checks "Pendiente origen/destino"
- (Tickets -> Adelantar tickets) Cambiado stock de destino a origen.
### Fixed
- (Artículos -> Etiquetas) Permite intercambiar la relevancia entre dos etiquetas.
- (Cliente -> Datos Fiscales) No se permite seleccionar 'Notificar vía e-mail' a los clientes sin e-mail
- (Tickets -> Datos básicos) Permite guardar la hora de envío
- (Tickets -> Añadir pago) Eliminado "null" en las referencias
- (Tickets -> Adelantar tickets) Permite ordenar por importe
- (Tickets -> Adelantar tickets) El filtrado por encajado muestra también los tickets sin tipo de encajado
## [2302.01] - 2023-01-26
### Added
- (General -> Inicio) Permite recuperar la contraseña
- (Tickets -> Opciones) Subir albarán a Docuware
- (Tickets -> Opciones) Enviar correo con PDF de Docuware
- (Artículos -> Datos Básicos) Añadido campo Unidades/Caja
### Changed
- (Reclamaciones -> Descriptor) Cambiado el campo Agencia por Zona
- (Tickets -> Líneas preparadas) Actualizada sección para que sea más visual
### Fixed
- (General) Al utilizar el traductor de Google se descuadraban los iconos
### Removed ### Removed
- [Tickets](Control clientes) Eliminada sección - (Tickets -> Control clientes) Eliminada sección

View File

@ -10,9 +10,10 @@ RUN apt-get update \
curl \ curl \
ca-certificates \ ca-certificates \
gnupg2 \ gnupg2 \
&& curl -fsSL https://deb.nodesource.com/setup_14.x | bash - \ graphicsmagick \
&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y --no-install-recommends nodejs \ && apt-get install -y --no-install-recommends nodejs \
&& npm install -g npm@8.19.2 && npm install -g npm@9.6.6
# Puppeteer # Puppeteer

9
Jenkinsfile vendored
View File

@ -39,7 +39,7 @@ pipeline {
NODE_ENV = "" NODE_ENV = ""
} }
steps { steps {
nodejs('node-v14') { nodejs('node-v20') {
sh 'npm install --no-audit --prefer-offline' sh 'npm install --no-audit --prefer-offline'
sh 'gulp install --ci' sh 'gulp install --ci'
} }
@ -52,18 +52,19 @@ pipeline {
}}} }}}
environment { environment {
NODE_ENV = "" NODE_ENV = ""
TZ = 'Europe/Madrid'
} }
parallel { parallel {
stage('Frontend') { stage('Frontend') {
steps { steps {
nodejs('node-v14') { nodejs('node-v20') {
sh 'jest --ci --reporters=default --reporters=jest-junit --maxWorkers=2' sh 'jest --ci --reporters=default --reporters=jest-junit --maxWorkers=2'
} }
} }
} }
stage('Backend') { stage('Backend') {
steps { steps {
nodejs('node-v14') { nodejs('node-v20') {
sh 'npm run test:back:ci' sh 'npm run test:back:ci'
} }
} }
@ -79,7 +80,7 @@ pipeline {
CREDENTIALS = credentials('docker-registry') CREDENTIALS = credentials('docker-registry')
} }
steps { steps {
nodejs('node-v14') { nodejs('node-v20') {
sh 'gulp build' sh 'gulp build'
} }

View File

@ -1,15 +0,0 @@
const app = require('vn-loopback/server/server');
describe('account setPassword()', () => {
it('should throw an error when password does not meet requirements', async() => {
let req = app.models.Account.setPassword(1, 'insecurePass');
await expectAsync(req).toBeRejected();
});
it('should update password when it passes requirements', async() => {
let req = app.models.Account.setPassword(1, 'Very$ecurePa22.');
await expectAsync(req).toBeResolved();
});
});

View File

@ -22,7 +22,7 @@ module.exports = Self => {
Self.latest = async filter => { Self.latest = async filter => {
const conn = Self.dataSource.connector; const conn = Self.dataSource.connector;
const minDate = new Date(); const minDate = Date.vnNew();
minDate.setFullYear(minDate.getFullYear() - 1); minDate.setFullYear(minDate.getFullYear() - 1);
const where = {dated: {gte: minDate}}; const where = {dated: {gte: minDate}};

View File

@ -1,9 +1,9 @@
const app = require('vn-loopback/server/server'); const models = require('vn-loopback/server/server').models;
describe('campaign latest()', () => { describe('campaign latest()', () => {
it('should return the campaigns from the last year', async() => { it('should return the campaigns from the last year', async() => {
const now = new Date(); const now = Date.vnNew();
const result = await app.models.Campaign.latest(); const result = await models.Campaign.latest();
const randomIndex = Math.floor(Math.random() * result.length); const randomIndex = Math.floor(Math.random() * result.length);
const campaignDated = result[randomIndex].dated; const campaignDated = result[randomIndex].dated;
@ -12,9 +12,9 @@ describe('campaign latest()', () => {
}); });
it('should return the campaigns from the current year', async() => { it('should return the campaigns from the current year', async() => {
const now = new Date(); const now = Date.vnNew();
const currentYear = now.getFullYear(); const currentYear = now.getFullYear();
const result = await app.models.Campaign.latest({ const result = await models.Campaign.latest({
where: {dated: {like: `%${currentYear}%`}} where: {dated: {like: `%${currentYear}%`}}
}); });

View File

@ -1,10 +1,10 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('campaign upcoming()', () => { describe('campaign upcoming()', () => {
it('should return the upcoming campaign but from the last year', async() => { it('should return the upcoming campaign but from the last year', async() => {
const response = await app.models.Campaign.upcoming(); const response = await models.Campaign.upcoming();
const campaignDated = response.dated; const campaignDated = response.dated;
const now = new Date(); const now = Date.vnNew();
expect(campaignDated).toEqual(jasmine.any(Date)); expect(campaignDated).toEqual(jasmine.any(Date));
expect(campaignDated).toBeLessThanOrEqual(now); expect(campaignDated).toBeLessThanOrEqual(now);

View File

@ -14,7 +14,7 @@ module.exports = Self => {
}); });
Self.upcoming = async() => { Self.upcoming = async() => {
const minDate = new Date(); const minDate = Date.vnNew();
minDate.setFullYear(minDate.getFullYear() - 1); minDate.setFullYear(minDate.getFullYear() - 1);
return Self.findOne({ return Self.findOne({

View File

@ -21,7 +21,7 @@ module.exports = Self => {
if (!this.login) return; if (!this.login) return;
if (Date.now() > this.login.expires) if (Date.vnNow() > this.login.expires)
this.login = await requestToken(); this.login = await requestToken();
return this.login; return this.login;
@ -48,7 +48,7 @@ module.exports = Self => {
userId: requestData.userId, userId: requestData.userId,
token: requestData.authToken token: requestData.authToken
}, },
expires: Date.now() + (1000 * 60 * tokenLifespan) expires: Date.vnNow() + (1000 * 60 * tokenLifespan)
}; };
} }
} }

View File

@ -26,20 +26,27 @@ module.exports = Self => {
Self.send = async(ctx, to, message) => { Self.send = async(ctx, to, message) => {
const models = Self.app.models; const models = Self.app.models;
const accessToken = ctx.req.accessToken; const accessToken = ctx.req.accessToken;
const sender = await models.Account.findById(accessToken.userId); const sender = await models.VnUser.findById(accessToken.userId);
const recipient = to.replace('@', ''); const recipient = to.replace('@', '');
if (sender.name != recipient) { if (sender.name != recipient) {
await models.Chat.create({ const chat = await models.Chat.create({
senderFk: sender.id, senderFk: sender.id,
recipient: to, recipient: to,
dated: new Date(), dated: Date.vnNew(),
checkUserStatus: 0, checkUserStatus: 0,
message: message, message: message,
status: 0, status: 'sending',
attempts: 0 attempts: 0
}); });
try {
await Self.sendMessage(chat.senderFk, chat.recipient, chat.message);
await Self.updateChat(chat, 'sent');
} catch (error) {
await Self.updateChat(chat, 'error', error);
}
return true; return true;
} }

View File

@ -24,18 +24,13 @@ module.exports = Self => {
} }
}); });
Self.sendCheckingPresence = async(ctx, recipientId, message, options) => { Self.sendCheckingPresence = async(ctx, recipientId, message) => {
if (!recipientId) return false; if (!recipientId) return false;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
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.Account.findById(userId); const sender = await models.VnUser.findById(userId, {fields: ['id']});
const recipient = await models.Account.findById(recipientId, null, myOptions); 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;
@ -46,16 +41,23 @@ module.exports = Self => {
if (process.env.NODE_ENV == 'test') if (process.env.NODE_ENV == 'test')
message = `[Test:Environment to user ${userId}] ` + message; message = `[Test:Environment to user ${userId}] ` + message;
await models.Chat.create({ const chat = await models.Chat.create({
senderFk: sender.id, senderFk: sender.id,
recipient: `@${recipient.name}`, recipient: `@${recipient.name}`,
dated: new Date(), dated: Date.vnNew(),
checkUserStatus: 1, checkUserStatus: 1,
message: message, message: message,
status: 0, status: 'sending',
attempts: 0 attempts: 0
}); });
try {
await Self.sendCheckingUserStatus(chat);
await Self.updateChat(chat, 'sent');
} catch (error) {
await Self.updateChat(chat, 'error', error);
}
return true; return true;
}; };
}; };

View File

@ -3,7 +3,6 @@ module.exports = Self => {
Self.remoteMethodCtx('sendQueued', { Self.remoteMethodCtx('sendQueued', {
description: 'Send a RocketChat message', description: 'Send a RocketChat message',
accessType: 'WRITE', accessType: 'WRITE',
accepts: [],
returns: { returns: {
type: 'object', type: 'object',
root: true root: true
@ -16,14 +15,17 @@ module.exports = Self => {
Self.sendQueued = async() => { Self.sendQueued = async() => {
const models = Self.app.models; const models = Self.app.models;
const maxAttempts = 3;
const sentStatus = 1;
const errorStatus = 2;
const chats = await models.Chat.find({ const chats = await models.Chat.find({
where: { where: {
status: {neq: sentStatus}, status: {
attempts: {lt: maxAttempts} nin: [
'sent',
'sending'
]
},
attempts: {lt: 3}
} }
}); });
@ -31,16 +33,16 @@ module.exports = Self => {
if (chat.checkUserStatus) { if (chat.checkUserStatus) {
try { try {
await Self.sendCheckingUserStatus(chat); await Self.sendCheckingUserStatus(chat);
await updateChat(chat, sentStatus); await Self.updateChat(chat, 'sent');
} catch (error) { } catch (error) {
await updateChat(chat, errorStatus, error); await Self.updateChat(chat, 'error', error);
} }
} else { } else {
try { try {
await Self.sendMessage(chat.senderFk, chat.recipient, chat.message); await Self.sendMessage(chat.senderFk, chat.recipient, chat.message);
await updateChat(chat, sentStatus); await Self.updateChat(chat, 'sent');
} catch (error) { } catch (error) {
await updateChat(chat, errorStatus, error); await Self.updateChat(chat, 'error', error);
} }
} }
} }
@ -56,7 +58,7 @@ module.exports = Self => {
const models = Self.app.models; const models = Self.app.models;
const recipientName = chat.recipient.slice(1); const recipientName = chat.recipient.slice(1);
const recipient = await models.Account.findOne({ const recipient = await models.VnUser.findOne({
where: { where: {
name: recipientName name: recipientName
} }
@ -102,7 +104,7 @@ module.exports = Self => {
} }
const models = Self.app.models; const models = Self.app.models;
const sender = await models.Account.findById(senderFk); const sender = await models.VnUser.findById(senderFk);
const login = await Self.getServiceAuth(); const login = await Self.getServiceAuth();
const avatar = `${login.host}/avatar/${sender.name}`; const avatar = `${login.host}/avatar/${sender.name}`;
@ -128,15 +130,17 @@ module.exports = Self => {
* @param {object} chat - The chat * @param {object} chat - The chat
* @param {string} status - The new status * @param {string} status - The new status
* @param {string} error - The error * @param {string} error - The error
* @param {object} options - Query options
* @return {Promise} - The request promise * @return {Promise} - The request promise
*/ */
async function updateChat(chat, status, error) {
Self.updateChat = async(chat, status, error) => {
return chat.updateAttributes({ return chat.updateAttributes({
status: status, status: status,
attempts: ++chat.attempts, attempts: ++chat.attempts,
error: error error: error
}); });
} };
/** /**
* Returns the current user status on Rocketchat * Returns the current user status on Rocketchat

View File

@ -1,12 +1,12 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('Chat notifyIssue()', () => { describe('Chat notifyIssue()', () => {
const ctx = {req: {accessToken: {userId: 1}}}; const ctx = {req: {accessToken: {userId: 1}}};
ctx.req.__ = value => { ctx.req.__ = value => {
return value; return value;
}; };
const chatModel = app.models.Chat; const chatModel = models.Chat;
const osTicketModel = app.models.OsTicket; const osTicketModel = models.OsTicket;
const departmentId = 31; const departmentId = 31;
it(`should not call to the send() method and neither return a response`, async() => { it(`should not call to the send() method and neither return a response`, async() => {
@ -29,7 +29,7 @@ describe('Chat notifyIssue()', () => {
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
const expectedMessage = `@all ➔ There's a new urgent ticket:\r\n[ID: 00001 - Issue title @batman](https://cau.verdnatura.es/scp/tickets.php?id=1)`; const expectedMessage = `@all ➔ There's a new urgent ticket:\r\n[ID: 00001 - Issue title @batman](https://cau.verdnatura.es/scp/tickets.php?id=1)`;
const department = await app.models.Department.findById(departmentId); const department = await models.Department.findById(departmentId);
let orgChatName = department.chatName; let orgChatName = department.chatName;
await department.updateAttribute('chatName', 'IT'); await department.updateAttribute('chatName', 'IT');

View File

@ -1,16 +1,16 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('Chat send()', () => { describe('Chat send()', () => {
it('should return true as response', async() => { it('should return true as response', async() => {
let ctx = {req: {accessToken: {userId: 1}}}; let ctx = {req: {accessToken: {userId: 1}}};
let response = await app.models.Chat.send(ctx, '@salesPerson', 'I changed something'); let response = await models.Chat.send(ctx, '@salesPerson', 'I changed something');
expect(response).toEqual(true); expect(response).toEqual(true);
}); });
it('should return false as response', async() => { it('should return false as response', async() => {
let ctx = {req: {accessToken: {userId: 18}}}; let ctx = {req: {accessToken: {userId: 18}}};
let response = await app.models.Chat.send(ctx, '@salesPerson', 'I changed something'); let response = await models.Chat.send(ctx, '@salesPerson', 'I changed something');
expect(response).toEqual(false); expect(response).toEqual(false);
}); });

View File

@ -1,7 +1,7 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
describe('Chat sendCheckingPresence()', () => { describe('Chat sendCheckingPresence()', () => {
const today = new Date(); const today = Date.vnNew();
today.setHours(6, 0); today.setHours(6, 0);
const chatModel = models.Chat; const chatModel = models.Chat;
@ -10,7 +10,7 @@ describe('Chat sendCheckingPresence()', () => {
const chat = { const chat = {
checkUserStatus: 1, checkUserStatus: 1,
status: 0, status: 'pending',
attempts: 0 attempts: 0
}; };
@ -27,7 +27,7 @@ describe('Chat sendCheckingPresence()', () => {
const chat = { const chat = {
checkUserStatus: 0, checkUserStatus: 0,
status: 0, status: 'pending',
attempts: 0 attempts: 0
}; };

View File

@ -30,11 +30,11 @@ module.exports = Self => {
Self.newCollection = async(ctx, collectionFk, sectorFk, vWagons) => { Self.newCollection = async(ctx, collectionFk, sectorFk, vWagons) => {
let query = ''; let query = '';
const userId = ctx.req.accessToken.userId;
if (!collectionFk) { if (!collectionFk) {
const userId = ctx.req.accessToken.userId;
query = `CALL vn.collectionTrain_newBeta(?,?,?)`; query = `CALL vn.collectionTrain_newBeta(?,?,?)`;
const [result] = await Self.rawSql(query, [sectorFk, vWagons, userId]); const [result] = await Self.rawSql(query, [sectorFk, vWagons, userId], {userId});
if (result.length == 0) if (result.length == 0)
throw new Error(`No collections for today`); throw new Error(`No collections for today`);
@ -42,16 +42,16 @@ module.exports = Self => {
} }
query = `CALL vn.collectionTicket_get(?)`; query = `CALL vn.collectionTicket_get(?)`;
const [tickets] = await Self.rawSql(query, [collectionFk]); const [tickets] = await Self.rawSql(query, [collectionFk], {userId});
query = `CALL vn.collectionSale_get(?)`; query = `CALL vn.collectionSale_get(?)`;
const [sales] = await Self.rawSql(query, [collectionFk]); const [sales] = await Self.rawSql(query, [collectionFk], {userId});
query = `CALL vn.collectionPlacement_get(?)`; query = `CALL vn.collectionPlacement_get(?)`;
const [placements] = await Self.rawSql(query, [collectionFk]); const [placements] = await Self.rawSql(query, [collectionFk], {userId});
query = `CALL vn.collectionSticker_print(?,?)`; query = `CALL vn.collectionSticker_print(?,?)`;
await Self.rawSql(query, [collectionFk, sectorFk]); await Self.rawSql(query, [collectionFk, sectorFk], {userId});
return makeCollection(tickets, sales, placements, collectionFk); return makeCollection(tickets, sales, placements, collectionFk);
}; };

View File

@ -0,0 +1,35 @@
module.exports = Self => {
Self.remoteMethodCtx('previousLabel', {
description: 'Returns the previa label pdf',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The item id',
http: {source: 'path'}
}],
returns: [
{
arg: 'body',
type: 'file',
root: true
}, {
arg: 'Content-Type',
type: 'String',
http: {target: 'header'}
}, {
arg: 'Content-Disposition',
type: 'String',
http: {target: 'header'}
}
],
http: {
path: '/:id/previousLabel',
verb: 'GET'
}
});
Self.previousLabel = (ctx, id) => Self.printReport(ctx, id, 'previa-label');
};

View File

@ -24,7 +24,7 @@ module.exports = Self => {
} }
}); });
Self.setSaleQuantity = async(saleId, quantity) => { Self.setSaleQuantity = async(saleId, quantity, options) => {
const models = Self.app.models; const models = Self.app.models;
const myOptions = {}; const myOptions = {};
let tx; let tx;
@ -40,8 +40,7 @@ module.exports = Self => {
try { try {
const sale = await models.Sale.findById(saleId, null, myOptions); const sale = await models.Sale.findById(saleId, null, myOptions);
const saleUpdated = await sale.updateAttributes({ const saleUpdated = await sale.updateAttributes({
originalQuantity: sale.quantity, quantity
quantity: quantity
}, myOptions); }, myOptions);
if (tx) await tx.commit(); if (tx) await tx.commit();

View File

@ -1,8 +1,8 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('getSectors()', () => { describe('getSectors()', () => {
it('return list of sectors', async() => { it('return list of sectors', async() => {
let response = await app.models.Collection.getSectors(); let response = await models.Collection.getSectors();
expect(response.length).toBeGreaterThan(0); expect(response.length).toBeGreaterThan(0);
expect(response[0].id).toEqual(1); expect(response[0].id).toEqual(1);

View File

@ -1,10 +1,10 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('newCollection()', () => { describe('newCollection()', () => {
it('should return a new collection', async() => { it('should return a new collection', async() => {
pending('#3400 analizar que hacer con rutas de back collection'); pending('#3400 analizar que hacer con rutas de back collection');
let ctx = {req: {accessToken: {userId: 1106}}}; let ctx = {req: {accessToken: {userId: 1106}}};
let response = await app.models.Collection.newCollection(ctx, 1, 1, 1); let response = await models.Collection.newCollection(ctx, 1, 1, 1);
expect(response.length).toBeGreaterThan(0); expect(response.length).toBeGreaterThan(0);
expect(response[0].ticketFk).toEqual(2); expect(response[0].ticketFk).toEqual(2);

View File

@ -1,6 +1,21 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('setSaleQuantity()', () => { describe('setSaleQuantity()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should change quantity sale', async() => { it('should change quantity sale', async() => {
const tx = await models.Ticket.beginTransaction({}); const tx = await models.Ticket.beginTransaction({});
@ -15,7 +30,7 @@ describe('setSaleQuantity()', () => {
await models.Collection.setSaleQuantity(saleId, newQuantity, options); await models.Collection.setSaleQuantity(saleId, newQuantity, options);
const updateSale = await models.Sale.findById(saleId, null, options); const updateSale = await models.Sale.findById(saleId, null, options);
expect(updateSale.originalQuantity).toEqual(originalSale.quantity); expect(updateSale.quantity).not.toEqual(originalSale.quantity);
expect(updateSale.quantity).toEqual(newQuantity); expect(updateSale.quantity).toEqual(newQuantity);
await tx.rollback(); await tx.rollback();

View File

@ -32,7 +32,7 @@ module.exports = Self => {
where: {code: 'trash'} where: {code: 'trash'}
}, myOptions); }, myOptions);
const date = new Date(); const date = Date.vnNew();
date.setMonth(date.getMonth() - 4); date.setMonth(date.getMonth() - 4);
const dmsToDelete = await models.Dms.find({ const dmsToDelete = await models.Dms.find({

View File

@ -1,215 +0,0 @@
const md5 = require('md5');
const fs = require('fs-extra');
module.exports = Self => {
Self.remoteMethodCtx('saveSign', {
description: 'Save sign',
accessType: 'WRITE',
accepts:
[
{
arg: 'signContent',
type: 'string',
required: true,
description: 'The sign content'
}, {
arg: 'tickets',
type: ['number'],
required: true,
description: 'The tickets'
}, {
arg: 'signedTime',
type: 'date',
description: 'The signed time'
}, {
arg: 'addressFk',
type: 'number',
required: true,
description: 'The address fk'
}
],
returns: {
type: 'Object',
root: true
},
http: {
path: `/saveSign`,
verb: 'POST'
}
});
async function createGestDoc(ticketId, userFk) {
const models = Self.app.models;
if (!await gestDocExists(ticketId)) {
const result = await models.Ticket.findOne({
where: {
id: ticketId
},
include: [
{
relation: 'warehouse',
scope: {
fields: ['id']
}
}, {
relation: 'client',
scope: {
fields: ['name']
}
}, {
relation: 'route',
scope: {
fields: ['id']
}
}
]
});
const warehouseFk = result.warehouseFk;
const companyFk = result.companyFk;
const client = result.client.name;
const route = result.route.id;
const resultDmsType = await models.DmsType.findOne({
where: {
code: 'Ticket'
}
});
const resultDms = await models.Dms.create({
dmsTypeFk: resultDmsType.id,
reference: ticketId,
description: `Ticket ${ticketId} Cliente ${client} Ruta ${route}`,
companyFk: companyFk,
warehouseFk: warehouseFk,
workerFk: userFk
});
return resultDms.insertId;
}
}
async function gestDocExists(ticket) {
const models = Self.app.models;
const result = await models.TicketDms.findOne({
where: {
ticketFk: ticket
},
fields: ['dmsFk']
});
if (result == null)
return false;
const isSigned = await models.Ticket.findOne({
where: {
id: ticket
},
fields: ['isSigned']
});
if (isSigned)
return true;
else
await models.Dms.destroyById(ticket);
}
async function dmsRecover(ticket, signContent) {
const models = Self.app.models;
await models.DmsRecover.create({
ticketFk: ticket,
sign: signContent
});
}
async function ticketGestdoc(ticket, dmsFk) {
const models = Self.app.models;
models.TicketDms.replaceOrCreate({
ticketFk: ticket,
dmsFk: dmsFk
});
const queryVnTicketSetState = `CALL vn.ticket_setState(?, ?)`;
await Self.rawSql(queryVnTicketSetState, [ticket, 'DELIVERED']);
}
async function updateGestdoc(file, ticket) {
const models = Self.app.models;
models.Dms.updateOne({
where: {
id: ticket
},
file: file,
contentType: 'image/png'
});
}
Self.saveSign = async(ctx, signContent, tickets, signedTime) => {
const models = Self.app.models;
let tx = await Self.beginTransaction({});
try {
const userId = ctx.req.accessToken.userId;
const dmsDir = `storage/dms`;
let image = null;
for (let i = 0; i < tickets.length; i++) {
const alertLevel = await models.TicketState.findOne({
where: {
ticketFk: tickets[i]
},
fields: ['alertLevel']
});
signedTime ? signedTime != undefined : signedTime = new Date();
if (alertLevel >= 2) {
let dir;
let id = null;
let fileName = null;
if (!await gestDocExists(tickets[i])) {
id = await createGestDoc(tickets[i], userId);
const hashDir = md5(id).substring(0, 3);
dir = `${dmsDir}/${hashDir}`;
if (!fs.existsSync(dir))
fs.mkdirSync(dir);
fileName = `${id}.png`;
image = `${dir}/${fileName}`;
} else
if (image != null) {
if (!fs.existsSync(dir))
dmsRecover(tickets[i], signContent);
else {
fs.writeFile(image, signContent, 'base64', async function(err) {
if (err) {
await tx.rollback();
return err.message;
}
});
}
} else
dmsRecover(tickets[i], signContent);
if (id != null && fileName.length > 0) {
ticketGestdoc(tickets[i], id);
updateGestdoc(id, fileName);
}
}
}
if (tx) await tx.commit();
return 'OK';
} catch (err) {
await tx.rollback();
throw err.message;
}
};
};

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('dms downloadFile()', () => { describe('dms downloadFile()', () => {
let dmsId = 1; let dmsId = 1;
@ -6,7 +6,7 @@ describe('dms downloadFile()', () => {
it('should return a response for an employee with text content-type', async() => { it('should return a response for an employee with text content-type', async() => {
let workerId = 1107; let workerId = 1107;
let ctx = {req: {accessToken: {userId: workerId}}}; let ctx = {req: {accessToken: {userId: workerId}}};
const result = await app.models.Dms.downloadFile(ctx, dmsId); const result = await models.Dms.downloadFile(ctx, dmsId);
expect(result[1]).toEqual('text/plain'); expect(result[1]).toEqual('text/plain');
}); });
@ -16,7 +16,7 @@ describe('dms downloadFile()', () => {
let ctx = {req: {accessToken: {userId: clientId}}}; let ctx = {req: {accessToken: {userId: clientId}}};
let error; let error;
await app.models.Dms.downloadFile(ctx, dmsId).catch(e => { await models.Dms.downloadFile(ctx, dmsId).catch(e => {
error = e; error = e;
}).finally(() => { }).finally(() => {
expect(error.message).toEqual(`You don't have enough privileges`); expect(error.message).toEqual(`You don't have enough privileges`);

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('dms removeFile()', () => { describe('dms removeFile()', () => {
let dmsId = 1; let dmsId = 1;
@ -8,7 +8,7 @@ describe('dms removeFile()', () => {
let ctx = {req: {accessToken: {userId: clientId}}}; let ctx = {req: {accessToken: {userId: clientId}}};
let error; let error;
await app.models.Dms.removeFile(ctx, dmsId).catch(e => { await models.Dms.removeFile(ctx, dmsId).catch(e => {
error = e; error = e;
}).finally(() => { }).finally(() => {
expect(error.message).toEqual(`You don't have enough privileges`); expect(error.message).toEqual(`You don't have enough privileges`);

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('dms updateFile()', () => { describe('dms updateFile()', () => {
it(`should return an error for a user without enough privileges`, async() => { it(`should return an error for a user without enough privileges`, async() => {
@ -11,7 +11,7 @@ describe('dms updateFile()', () => {
let ctx = {req: {accessToken: {userId: clientId}}, args: {dmsTypeId: dmsTypeId}}; let ctx = {req: {accessToken: {userId: clientId}}, args: {dmsTypeId: dmsTypeId}};
let error; let error;
await app.models.Dms.updateFile(ctx, dmsId, warehouseId, companyId, dmsTypeId).catch(e => { await models.Dms.updateFile(ctx, dmsId, warehouseId, companyId, dmsTypeId).catch(e => {
error = e; error = e;
}).finally(() => { }).finally(() => {
expect(error.message).toEqual(`You don't have enough privileges`); expect(error.message).toEqual(`You don't have enough privileges`);

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('dms uploadFile()', () => { describe('dms uploadFile()', () => {
it(`should return an error for a user without enough privileges`, async() => { it(`should return an error for a user without enough privileges`, async() => {
@ -7,7 +7,7 @@ describe('dms uploadFile()', () => {
let ctx = {req: {accessToken: {userId: clientId}}, args: {dmsTypeId: ticketDmsTypeId}}; let ctx = {req: {accessToken: {userId: clientId}}, args: {dmsTypeId: ticketDmsTypeId}};
let error; let error;
await app.models.Dms.uploadFile(ctx).catch(e => { await models.Dms.uploadFile(ctx).catch(e => {
error = e; error = e;
}).finally(() => { }).finally(() => {
expect(error.message).toEqual(`You don't have enough privileges`); expect(error.message).toEqual(`You don't have enough privileges`);

View File

@ -4,25 +4,25 @@ module.exports = Self => {
Self.remoteMethodCtx('deliveryNoteEmail', { Self.remoteMethodCtx('deliveryNoteEmail', {
description: 'Sends the delivery note email with an docuware attached PDF', description: 'Sends the delivery note email with an docuware attached PDF',
accessType: 'WRITE', accessType: 'WRITE',
accessScopes: ['docuwareDeliveryNoteEmail'],
accepts: [ accepts: [
{ {
arg: 'id', arg: 'id',
type: 'string', type: 'number',
required: true, required: true,
description: 'The ticket id', description: 'The ticket id',
http: {source: 'path'}
},
{
arg: 'recipient',
type: 'string',
description: 'The recipient email',
required: true,
}, },
{ {
arg: 'recipientId', arg: 'recipientId',
type: 'number', type: 'number',
description: 'The client id', description: 'The client id',
required: false required: true
},
{
arg: 'recipient',
type: 'string',
description: 'The recipient email',
required: false,
} }
], ],
returns: [ returns: [
@ -41,12 +41,13 @@ module.exports = Self => {
} }
], ],
http: { http: {
path: '/:id/delivery-note-email', path: '/delivery-note-email',
verb: 'POST' verb: 'POST'
} }
}); });
Self.deliveryNoteEmail = async(ctx, id) => { Self.deliveryNoteEmail = async(ctx, id, recipientId, recipient) => {
const models = Self.app.models;
const args = Object.assign({}, ctx.args); const args = Object.assign({}, ctx.args);
const params = { const params = {
recipient: args.recipient, recipient: args.recipient,
@ -57,9 +58,14 @@ module.exports = Self => {
for (const param in args) for (const param in args)
params[param] = args[param]; params[param] = args[param];
if (!recipient) {
client = await models.Client.findById(recipientId, {fields: ['email']});
params.recipient = client.email;
}
const email = new Email('delivery-note', params); const email = new Email('delivery-note', params);
const docuwareFile = await Self.app.models.Docuware.download(ctx, id, 'deliveryNote'); const docuwareFile = await models.Docuware.download(ctx, id, 'deliveryNote');
return email.send({ return email.send({
overrideAttachments: true, overrideAttachments: true,

View File

@ -127,7 +127,7 @@ module.exports = Self => {
const uploadOptions = { const uploadOptions = {
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data',
'X-File-ModifiedDate': new Date(), 'X-File-ModifiedDate': Date.vnNew(),
'Cookie': options.headers.headers.Cookie, 'Cookie': options.headers.headers.Cookie,
...data.getHeaders() ...data.getHeaders()
}, },

View File

@ -1,9 +1,9 @@
LOAD DATA LOCAL INFILE ? LOAD DATA LOCAL INFILE ?
INTO TABLE `edi`.`item` INTO TABLE `edi`.`item`
CHARACTER SET ascii
FIELDS TERMINATED BY ';' FIELDS TERMINATED BY ';'
LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7, @col8, @col9, @col10, @col11, @col12) LINES TERMINATED BY '\n' (@col1, @col2, @col3, @col4, @col5, @col6, @col7, @col8, @col9, @col10, @col11, @col12)
SET SET id = @col2,
id = @col2,
product_name = @col4, product_name = @col4,
name = @col5, name = @col5,
plant_id = @col7, plant_id = @col7,
@ -11,3 +11,4 @@ LOAD DATA LOCAL INFILE ?
entry_date = STR_TO_DATE(@col10, '%Y%m%d'), entry_date = STR_TO_DATE(@col10, '%Y%m%d'),
expiry_date = IFNULL(NULL,STR_TO_DATE(@col11, '%Y%m%d')), expiry_date = IFNULL(NULL,STR_TO_DATE(@col11, '%Y%m%d')),
change_date_time = STR_TO_DATE(@col12, '%Y%m%d%H%i') change_date_time = STR_TO_DATE(@col12, '%Y%m%d%H%i')

View File

@ -16,14 +16,14 @@ module.exports = Self => {
} }
}); });
Self.updateData = async() => { Self.updateData = async ctx => {
const models = Self.app.models; const models = Self.app.models;
// Get files checksum // Get files checksum
const tx = await Self.beginTransaction({}); const tx = await Self.beginTransaction({});
try { try {
const options = {transaction: tx}; const options = {transaction: tx, userId: ctx.req.accessToken.userId};
const files = await Self.rawSql('SELECT name, checksum, keyValue FROM edi.fileConfig', null, options); const files = await Self.rawSql('SELECT name, checksum, keyValue FROM edi.fileConfig', null, options);
const updatableFiles = []; const updatableFiles = [];
@ -53,9 +53,9 @@ module.exports = Self => {
const fileNames = updatableFiles.map(file => file.name); const fileNames = updatableFiles.map(file => file.name);
const tables = await Self.rawSql(` const tables = await Self.rawSql(`
SELECT fileName, toTable, file SELECT fileName, toTable, file
FROM edi.tableConfig FROM edi.tableConfig
WHERE file IN (?)`, [fileNames], options); WHERE file IN (?)`, [fileNames], options);
for (const table of tables) { for (const table of tables) {
const fileName = table.file; const fileName = table.file;
@ -85,9 +85,9 @@ module.exports = Self => {
for (const file of updatableFiles) { for (const file of updatableFiles) {
console.log(`Updating file ${file.name} checksum...`); console.log(`Updating file ${file.name} checksum...`);
await Self.rawSql(` await Self.rawSql(`
UPDATE edi.fileConfig UPDATE edi.fileConfig
SET checksum = ? SET checksum = ?
WHERE name = ?`, WHERE name = ?`,
[file.checksum, file.name], options); [file.checksum, file.name], options);
} }
@ -121,7 +121,8 @@ module.exports = Self => {
host: ftpConfig.host, host: ftpConfig.host,
username: ftpConfig.user, username: ftpConfig.user,
password: ftpConfig.password, password: ftpConfig.password,
procotol: 'ftp' procotol: 'ftp',
additionalLftpCommands: 'set ssl:verify-certificate no'
}); });
} }
@ -132,7 +133,7 @@ module.exports = Self => {
const ftpClient = await getFtpClient(); const ftpClient = await getFtpClient();
console.debug(`Checking checksum for file ${file.name}...`); console.debug(`Checking checksum for file ${file.name}...`);
ftpClient.cat(`codes/${file.name}.txt`); ftpClient.cat(`codes/${file.name}.TXT`);
const response = await new Promise((resolve, reject) => { const response = await new Promise((resolve, reject) => {
ftpClient.exec((err, response) => { ftpClient.exec((err, response) => {
@ -227,10 +228,10 @@ module.exports = Self => {
await Self.rawSql(sqlTemplate, [filePath], options); await Self.rawSql(sqlTemplate, [filePath], options);
await Self.rawSql(` await Self.rawSql(`
UPDATE edi.tableConfig UPDATE edi.tableConfig
SET updated = ? SET updated = ?
WHERE fileName = ? WHERE fileName = ?
`, [new Date(), baseName], options); `, [Date.vnNew(), baseName], options);
} }
console.log(`Updated table ${toTable}\n`); console.log(`Updated table ${toTable}\n`);

View File

@ -67,17 +67,20 @@ module.exports = Self => {
if (!image) return false; if (!image) return false;
const hasReadRole = models.ImageCollection.hasReadRole(ctx, collection); const hasReadRole = await models.ImageCollection.hasReadRole(ctx, collection);
if (!hasReadRole) if (!hasReadRole)
throw new UserError(`You don't have enough privileges`); throw new UserError(`You don't have enough privileges`);
const container = await models.ImageContainer.getContainer(collection); const container = await models.ImageContainer.getContainer(collection);
const rootPath = container.client.root; const rootPath = container.client.root;
const fileSrc = path.join(rootPath, collection, size); const fileSrc = path.join(rootPath, collection, size);
const ext = image.name.substring((image.name.length - 4));
const fileName = ext !== '.png' ? `${image.name}.png` : image.name;
const file = { const file = {
path: `${fileSrc}/${image.name}.png`, path: `${fileSrc}/${fileName}`,
contentType: 'image/png', contentType: 'image/png',
name: `${image.name}.png` name: image.name
}; };
if (!fs.existsSync(file.path)) return []; if (!fs.existsSync(file.path)) return [];

130
back/methods/image/scrub.js Normal file
View File

@ -0,0 +1,130 @@
const fs = require('fs-extra');
const path = require('path');
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethod('scrub', {
description: 'Deletes images without database reference',
accessType: 'WRITE',
accepts: [
{
arg: 'collection',
type: 'string',
description: 'The collection name',
required: true
}, {
arg: 'remove',
type: 'boolean',
description: 'Delete instead of move images to trash'
}, {
arg: 'limit',
type: 'integer',
description: 'Maximum number of images to clean'
}, {
arg: 'dryRun',
type: 'boolean',
description: 'Simulate actions'
}, {
arg: 'skipLock',
type: 'boolean',
description: 'Wether to skip exclusive lock'
}
],
returns: {
type: 'integer',
root: true
},
http: {
path: `/scrub`,
verb: 'POST'
}
});
Self.scrub = async function(collection, remove, limit, dryRun, skipLock) {
const $ = Self.app.models;
const env = process.env.NODE_ENV;
dryRun = dryRun || (env && env !== 'production');
const instance = await $.ImageCollection.findOne({
fields: ['id'],
where: {name: collection}
});
if (!instance)
throw new UserError('Collection does not exist');
const container = await $.ImageContainer.container(collection);
const rootPath = container.client.root;
let tx;
let opts;
const lockName = 'salix.Image.scrub';
if (!skipLock) {
tx = await Self.beginTransaction({timeout: null});
opts = {transaction: tx};
const [row] = await Self.rawSql(
`SELECT GET_LOCK(?, 10) hasLock`, [lockName], opts);
if (!row.hasLock)
throw new UserError('Cannot obtain exclusive lock');
}
try {
const now = Date.vnNew().toJSON();
const scrubDir = path.join(rootPath, '.scrub', now);
const collectionDir = path.join(rootPath, collection);
const sizes = await fs.readdir(collectionDir);
let cleanCount = 0;
mainLoop: for (const size of sizes) {
const sizeDir = path.join(collectionDir, size);
const scrubSizeDir = path.join(scrubDir, collection, size);
const images = await fs.readdir(sizeDir);
for (const image of images) {
const imageName = path.parse(image).name;
const count = await Self.count({
collectionFk: collection,
name: imageName
}, opts);
const exists = count > 0;
let scrubDirCreated = false;
if (!exists) {
const srcFile = path.join(sizeDir, image);
if (remove !== true) {
if (!scrubDirCreated) {
if (!dryRun)
await fs.mkdir(scrubSizeDir, {recursive: true});
scrubDirCreated = true;
}
const dstFile = path.join(scrubSizeDir, image);
if (!dryRun) await fs.rename(srcFile, dstFile);
} else {
try {
if (!dryRun) await fs.unlink(srcFile);
} catch (err) {
console.error(err.message);
}
}
cleanCount++;
if (limit && cleanCount == limit)
break mainLoop;
}
}
}
return cleanCount;
} finally {
if (!skipLock) {
try {
await Self.rawSql(`DO RELEASE_LOCK(?)`, [lockName], opts);
await tx.rollback();
} catch (err) {
console.error(err.message);
}
}
}
};
};

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('image download()', () => { describe('image download()', () => {
const collection = 'user'; const collection = 'user';
@ -8,7 +8,7 @@ describe('image download()', () => {
it('should return the image content-type of the user', async() => { it('should return the image content-type of the user', async() => {
const userId = 9; const userId = 9;
const image = await app.models.Image.download(ctx, collection, size, userId); const image = await models.Image.download(ctx, collection, size, userId);
const contentType = image[1]; const contentType = image[1];
expect(contentType).toEqual('image/png'); expect(contentType).toEqual('image/png');
@ -16,7 +16,7 @@ describe('image download()', () => {
it(`should return false if the user doesn't have image`, async() => { it(`should return false if the user doesn't have image`, async() => {
const userId = 1110; const userId = 1110;
const image = await app.models.Image.download(ctx, collection, size, userId); const image = await models.Image.download(ctx, collection, size, userId);
expect(image).toBeFalse(); expect(image).toBeFalse();
}); });

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('image upload()', () => { describe('image upload()', () => {
describe('as buyer', () => { describe('as buyer', () => {
@ -16,7 +16,7 @@ describe('image upload()', () => {
let error; let error;
try { try {
await app.models.Image.upload(ctx); await models.Image.upload(ctx);
} catch (err) { } catch (err) {
error = err; error = err;
} }
@ -25,7 +25,7 @@ describe('image upload()', () => {
}); });
it('should call to the TempContainer upload method for the collection "catalog"', async() => { it('should call to the TempContainer upload method for the collection "catalog"', async() => {
const containerModel = app.models.TempContainer; const containerModel = models.TempContainer;
spyOn(containerModel, 'upload'); spyOn(containerModel, 'upload');
const ctx = {req: {accessToken: {userId: buyerId}}, const ctx = {req: {accessToken: {userId: buyerId}},
@ -36,7 +36,7 @@ describe('image upload()', () => {
}; };
try { try {
await app.models.Image.upload(ctx); await models.Image.upload(ctx);
} catch (err) { } } catch (err) { }
expect(containerModel.upload).toHaveBeenCalled(); expect(containerModel.upload).toHaveBeenCalled();
@ -49,7 +49,7 @@ describe('image upload()', () => {
const itemId = 4; const itemId = 4;
it('should be able to call to the TempContainer upload method for the collection "user"', async() => { it('should be able to call to the TempContainer upload method for the collection "user"', async() => {
const containerModel = app.models.TempContainer; const containerModel = models.TempContainer;
spyOn(containerModel, 'upload'); spyOn(containerModel, 'upload');
const ctx = {req: {accessToken: {userId: marketingId}}, const ctx = {req: {accessToken: {userId: marketingId}},
@ -60,14 +60,14 @@ describe('image upload()', () => {
}; };
try { try {
await app.models.Image.upload(ctx); await models.Image.upload(ctx);
} catch (err) { } } catch (err) { }
expect(containerModel.upload).toHaveBeenCalled(); expect(containerModel.upload).toHaveBeenCalled();
}); });
it('should be able to call to the TempContainer upload method for the collection "catalog"', async() => { it('should be able to call to the TempContainer upload method for the collection "catalog"', async() => {
const containerModel = app.models.TempContainer; const containerModel = models.TempContainer;
spyOn(containerModel, 'upload'); spyOn(containerModel, 'upload');
const ctx = {req: {accessToken: {userId: marketingId}}, const ctx = {req: {accessToken: {userId: marketingId}},
@ -78,7 +78,7 @@ describe('image upload()', () => {
}; };
try { try {
await app.models.Image.upload(ctx); await models.Image.upload(ctx);
} catch (err) { } } catch (err) { }
expect(containerModel.upload).toHaveBeenCalled(); expect(containerModel.upload).toHaveBeenCalled();
@ -91,7 +91,7 @@ describe('image upload()', () => {
const itemId = 4; const itemId = 4;
it('should upload a file for the collection "user" and call to the TempContainer upload method', async() => { it('should upload a file for the collection "user" and call to the TempContainer upload method', async() => {
const containerModel = app.models.TempContainer; const containerModel = models.TempContainer;
spyOn(containerModel, 'upload'); spyOn(containerModel, 'upload');
const ctx = {req: {accessToken: {userId: hhrrId}}, const ctx = {req: {accessToken: {userId: hhrrId}},
@ -102,7 +102,7 @@ describe('image upload()', () => {
}; };
try { try {
await app.models.Image.upload(ctx); await models.Image.upload(ctx);
} catch (err) { } } catch (err) { }
expect(containerModel.upload).toHaveBeenCalled(); expect(containerModel.upload).toHaveBeenCalled();
@ -118,7 +118,7 @@ describe('image upload()', () => {
let error; let error;
try { try {
await app.models.Image.upload(ctx); await models.Image.upload(ctx);
} catch (err) { } catch (err) {
error = err; error = err;
} }

View File

@ -1,5 +1,5 @@
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
const fs = require('fs-extra'); const fs = require('fs/promises');
const path = require('path'); const path = require('path');
module.exports = Self => { module.exports = Self => {
@ -12,19 +12,13 @@ module.exports = Self => {
type: 'Number', type: 'Number',
description: 'The entity id', description: 'The entity id',
required: true required: true
}, }, {
{
arg: 'collection', arg: 'collection',
type: 'string', type: 'string',
description: 'The collection name', description: 'The collection name',
required: true required: true
}, }
{ ],
arg: 'fileName',
type: 'string',
description: 'The file name',
required: true
}],
returns: { returns: {
type: 'Object', type: 'Object',
root: true root: true
@ -41,7 +35,7 @@ module.exports = Self => {
const fileOptions = {}; const fileOptions = {};
const args = ctx.args; const args = ctx.args;
let srcFile; let tempFilePath;
try { try {
const hasWriteRole = await models.ImageCollection.hasWriteRole(ctx, args.collection); const hasWriteRole = await models.ImageCollection.hasWriteRole(ctx, args.collection);
if (!hasWriteRole) if (!hasWriteRole)
@ -56,15 +50,22 @@ module.exports = Self => {
const [uploadedFile] = Object.values(uploaded.files).map(file => { const [uploadedFile] = Object.values(uploaded.files).map(file => {
return file[0]; return file[0];
}); });
const file = await TempContainer.getFile(tempContainer.name, uploadedFile.name); const file = await TempContainer.getFile(tempContainer.name, uploadedFile.name);
srcFile = path.join(file.client.root, file.container, file.name); tempFilePath = path.join(file.client.root, file.container, file.name);
await models.Image.registerImage(args.collection, srcFile, args.fileName, args.id); const fileName = `${args.id}.png`;
} catch (e) {
if (fs.existsSync(srcFile))
await fs.unlink(srcFile);
throw e; await models.Image.resize({
collectionName: args.collection,
srcFile: tempFilePath,
fileName: fileName,
entityId: args.id
});
} finally {
try {
await fs.unlink(tempFilePath);
} catch (error) { }
} }
}; };
}; };

View File

@ -32,7 +32,7 @@ module.exports = Self => {
if (!config.cleanDays) return; if (!config.cleanDays) return;
const cleanDate = new Date(); const cleanDate = Date.vnNew();
cleanDate.setDate(cleanDate.getDate() - config.cleanDays); cleanDate.setDate(cleanDate.getDate() - config.cleanDays);
await models.NotificationQueue.destroyAll({ await models.NotificationQueue.destroyAll({

View File

@ -10,7 +10,7 @@ describe('Notification Clean()', () => {
const notification = await models.Notification.findOne({}, options); const notification = await models.Notification.findOne({}, options);
const notificationConfig = await models.NotificationConfig.findOne({}); const notificationConfig = await models.NotificationConfig.findOne({});
const cleanDate = new Date(); const cleanDate = Date.vnNew();
cleanDate.setDate(cleanDate.getDate() - (notificationConfig.cleanDays + 1)); cleanDate.setDate(cleanDate.getDate() - (notificationConfig.cleanDays + 1));
let before; let before;

View File

@ -36,19 +36,26 @@ module.exports = Self => {
JOIN osticket.ost_ticket_status ots ON ots.id = ot.status_id JOIN osticket.ost_ticket_status ots ON ots.id = ot.status_id
JOIN osticket.ost_thread ot2 ON ot2.object_id = ot.ticket_id AND ot2.object_type = 'T' JOIN osticket.ost_thread ot2 ON ot2.object_id = ot.ticket_id AND ot2.object_type = 'T'
JOIN ( JOIN (
SELECT ote.thread_id, MAX(ote.created) created, MAX(ote.updated) updated SELECT sub2.thread_id, sub2.type, sub2.updated, sub2.created
FROM osticket.ost_thread_entry ote FROM (
WHERE ote.staff_id AND ote.type = 'R' SELECT ote.thread_id, ote.created, ote.updated, ote.type
GROUP BY ote.thread_id FROM osticket.ost_thread_entry ote
WHERE ote.staff_id
ORDER BY ote.id DESC
LIMIT 10000000000000000000) sub2
GROUP BY sub2.thread_id
) sub ON sub.thread_id = ot2.id ) sub ON sub.thread_id = ot2.id
WHERE ot.isanswered WHERE ot.isanswered
AND ots.state = ? AND ots.id IN (?)
AND IF(sub.updated > sub.created, sub.updated, sub.created) < DATE_SUB(CURDATE(), INTERVAL ? DAY)`; AND sub.type = 'R'
AND IF(sub.updated > sub.created, sub.updated, sub.created) < DATE_SUB(CURDATE(), INTERVAL ? DAY);`;
const ticketsId = []; const ticketsId = [];
const statusIdToClose = config.oldStatus.split(',');
con.connect(err => { con.connect(err => {
if (err) throw err; if (err) throw err;
con.query(sql, [config.oldStatus, config.day], con.query(sql, [statusIdToClose, config.day],
(err, results) => { (err, results) => {
if (err) throw err; if (err) throw err;
for (const result of results) for (const result of results)
@ -62,15 +69,15 @@ module.exports = Self => {
const result = response.headers.get('set-cookie'); const result = response.headers.get('set-cookie');
const [firtHeader] = result.split(' '); const [firtHeader] = result.split(' ');
const firtCookie = firtHeader.substring(0, firtHeader.length - 1); const cookie = firtHeader.substring(0, firtHeader.length - 1);
const body = await response.text(); const body = await response.text();
const dom = new jsdom.JSDOM(body); const dom = new jsdom.JSDOM(body);
const token = dom.window.document.querySelector('[name="__CSRFToken__"]').value; const token = dom.window.document.querySelector('[name="__CSRFToken__"]').value;
await login(token, firtCookie); await login(token, cookie);
} }
async function login(token, firtCookie) { async function login(token, cookie) {
const data = { const data = {
__CSRFToken__: token, __CSRFToken__: token,
do: 'scplogin', do: 'scplogin',
@ -83,21 +90,18 @@ module.exports = Self => {
body: new URLSearchParams(data), body: new URLSearchParams(data),
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Cookie': firtCookie 'Cookie': cookie
} }
}; };
const response = await fetch(ostUri, params); await fetch(ostUri, params);
const result = response.headers.get('set-cookie');
const [firtHeader] = result.split(' ');
const secondCookie = firtHeader.substring(0, firtHeader.length - 1);
await close(token, secondCookie); await close(token, cookie);
} }
async function close(token, secondCookie) { async function close(token, cookie) {
for (const ticketId of ticketsId) { for (const ticketId of ticketsId) {
try { try {
const lock = await getLockCode(token, secondCookie, ticketId); const lock = await getLockCode(token, cookie, ticketId);
if (!lock.code) { if (!lock.code) {
let error = `Can't get lock code`; let error = `Can't get lock code`;
if (lock.msg) error += `: ${lock.msg}`; if (lock.msg) error += `: ${lock.msg}`;
@ -120,7 +124,7 @@ module.exports = Self => {
method: 'POST', method: 'POST',
body: form, body: form,
headers: { headers: {
'Cookie': secondCookie 'Cookie': cookie
} }
}; };
await fetch(ostUri, params); await fetch(ostUri, params);
@ -132,13 +136,13 @@ module.exports = Self => {
} }
} }
async function getLockCode(token, secondCookie, ticketId) { async function getLockCode(token, cookie, ticketId) {
const ostUri = `${config.host}/ajax.php/lock/ticket/${ticketId}`; const ostUri = `${config.host}/ajax.php/lock/ticket/${ticketId}`;
const params = { const params = {
method: 'POST', method: 'POST',
headers: { headers: {
'X-CSRFToken': token, 'X-CSRFToken': token,
'Cookie': secondCookie 'Cookie': cookie
} }
}; };
const response = await fetch(ostUri, params); const response = await fetch(ostUri, params);

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
describe('getStarredModules()', () => { describe('getStarredModules()', () => {
@ -19,13 +19,13 @@ describe('getStarredModules()', () => {
}); });
it(`should return the starred modules for a given user`, async() => { it(`should return the starred modules for a given user`, async() => {
const newStarred = await app.models.StarredModule.create({workerFk: 9, moduleFk: 'customer', position: 1}); const newStarred = await models.StarredModule.create({workerFk: 9, moduleFk: 'customer', position: 1});
const starredModules = await app.models.StarredModule.getStarredModules(ctx); const starredModules = await models.StarredModule.getStarredModules(ctx);
expect(starredModules.length).toEqual(1); expect(starredModules.length).toEqual(1);
expect(starredModules[0].moduleFk).toEqual('customer'); expect(starredModules[0].moduleFk).toEqual('customer');
// restores // restores
await app.models.StarredModule.destroyById(newStarred.id); await models.StarredModule.destroyById(newStarred.id);
}); });
}); });

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
describe('setPosition()', () => { describe('setPosition()', () => {
@ -21,7 +21,7 @@ describe('setPosition()', () => {
}); });
it('should increase the orders module position by replacing it with clients and vice versa', async() => { it('should increase the orders module position by replacing it with clients and vice versa', async() => {
const tx = await app.models.StarredModule.beginTransaction({}); const tx = await models.StarredModule.beginTransaction({});
const filter = { const filter = {
where: { where: {
@ -32,24 +32,24 @@ describe('setPosition()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
await app.models.StarredModule.toggleStarredModule(ctx, 'order', options); await models.StarredModule.toggleStarredModule(ctx, 'order', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'customer', options); await models.StarredModule.toggleStarredModule(ctx, 'customer', options);
let orders = await app.models.StarredModule.findOne(filter, options); let orders = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'customer'; filter.where.moduleFk = 'customer';
let clients = await app.models.StarredModule.findOne(filter, options); let clients = await models.StarredModule.findOne(filter, options);
expect(orders.position).toEqual(1); expect(orders.position).toEqual(1);
expect(clients.position).toEqual(2); expect(clients.position).toEqual(2);
await app.models.StarredModule.setPosition(ctx, 'customer', 'left', options); await models.StarredModule.setPosition(ctx, 'customer', 'left', options);
filter.where.moduleFk = 'customer'; filter.where.moduleFk = 'customer';
clients = await app.models.StarredModule.findOne(filter, options); clients = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'order'; filter.where.moduleFk = 'order';
orders = await app.models.StarredModule.findOne(filter, options); orders = await models.StarredModule.findOne(filter, options);
expect(clients.position).toEqual(1); expect(clients.position).toEqual(1);
expect(orders.position).toEqual(2); expect(orders.position).toEqual(2);
@ -62,7 +62,7 @@ describe('setPosition()', () => {
}); });
it('should decrease the orders module position by replacing it with clients and vice versa', async() => { it('should decrease the orders module position by replacing it with clients and vice versa', async() => {
const tx = await app.models.StarredModule.beginTransaction({}); const tx = await models.StarredModule.beginTransaction({});
const filter = { const filter = {
where: { where: {
@ -73,24 +73,24 @@ describe('setPosition()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
await app.models.StarredModule.toggleStarredModule(ctx, 'order', options); await models.StarredModule.toggleStarredModule(ctx, 'order', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'customer', options); await models.StarredModule.toggleStarredModule(ctx, 'customer', options);
let orders = await app.models.StarredModule.findOne(filter, options); let orders = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'customer'; filter.where.moduleFk = 'customer';
let clients = await app.models.StarredModule.findOne(filter, options); let clients = await models.StarredModule.findOne(filter, options);
expect(orders.position).toEqual(1); expect(orders.position).toEqual(1);
expect(clients.position).toEqual(2); expect(clients.position).toEqual(2);
await app.models.StarredModule.setPosition(ctx, 'order', 'right', options); await models.StarredModule.setPosition(ctx, 'order', 'right', options);
filter.where.moduleFk = 'order'; filter.where.moduleFk = 'order';
orders = await app.models.StarredModule.findOne(filter, options); orders = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'customer'; filter.where.moduleFk = 'customer';
clients = await app.models.StarredModule.findOne(filter, options); clients = await models.StarredModule.findOne(filter, options);
expect(orders.position).toEqual(2); expect(orders.position).toEqual(2);
expect(clients.position).toEqual(1); expect(clients.position).toEqual(1);
@ -103,7 +103,7 @@ describe('setPosition()', () => {
}); });
it('should switch two modules after adding and deleting several modules', async() => { it('should switch two modules after adding and deleting several modules', async() => {
const tx = await app.models.StarredModule.beginTransaction({}); const tx = await models.StarredModule.beginTransaction({});
const filter = { const filter = {
where: { where: {
@ -115,29 +115,29 @@ describe('setPosition()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
await app.models.StarredModule.toggleStarredModule(ctx, 'customer', options); await models.StarredModule.toggleStarredModule(ctx, 'customer', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'order', options); await models.StarredModule.toggleStarredModule(ctx, 'order', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'customer', options); await models.StarredModule.toggleStarredModule(ctx, 'customer', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'order', options); await models.StarredModule.toggleStarredModule(ctx, 'order', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'item', options); await models.StarredModule.toggleStarredModule(ctx, 'item', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'claim', options); await models.StarredModule.toggleStarredModule(ctx, 'claim', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'customer', options); await models.StarredModule.toggleStarredModule(ctx, 'customer', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'order', options); await models.StarredModule.toggleStarredModule(ctx, 'order', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'zone', options); await models.StarredModule.toggleStarredModule(ctx, 'zone', options);
const items = await app.models.StarredModule.findOne(filter, options); const items = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'claim'; filter.where.moduleFk = 'claim';
const claims = await app.models.StarredModule.findOne(filter, options); const claims = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'customer'; filter.where.moduleFk = 'customer';
let clients = await app.models.StarredModule.findOne(filter, options); let clients = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'order'; filter.where.moduleFk = 'order';
let orders = await app.models.StarredModule.findOne(filter, options); let orders = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'zone'; filter.where.moduleFk = 'zone';
const zones = await app.models.StarredModule.findOne(filter, options); const zones = await models.StarredModule.findOne(filter, options);
expect(items.position).toEqual(1); expect(items.position).toEqual(1);
expect(claims.position).toEqual(2); expect(claims.position).toEqual(2);
@ -145,13 +145,13 @@ describe('setPosition()', () => {
expect(orders.position).toEqual(4); expect(orders.position).toEqual(4);
expect(zones.position).toEqual(5); expect(zones.position).toEqual(5);
await app.models.StarredModule.setPosition(ctx, 'customer', 'right', options); await models.StarredModule.setPosition(ctx, 'customer', 'right', options);
filter.where.moduleFk = 'order'; filter.where.moduleFk = 'order';
orders = await app.models.StarredModule.findOne(filter, options); orders = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'customer'; filter.where.moduleFk = 'customer';
clients = await app.models.StarredModule.findOne(filter, options); clients = await models.StarredModule.findOne(filter, options);
expect(orders.position).toEqual(3); expect(orders.position).toEqual(3);
expect(clients.position).toEqual(4); expect(clients.position).toEqual(4);
@ -164,7 +164,7 @@ describe('setPosition()', () => {
}); });
it('should switch two modules after adding and deleting a module between them', async() => { it('should switch two modules after adding and deleting a module between them', async() => {
const tx = await app.models.StarredModule.beginTransaction({}); const tx = await models.StarredModule.beginTransaction({});
const filter = { const filter = {
where: { where: {
@ -176,25 +176,25 @@ describe('setPosition()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
await app.models.StarredModule.toggleStarredModule(ctx, 'item', options); await models.StarredModule.toggleStarredModule(ctx, 'item', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'customer', options); await models.StarredModule.toggleStarredModule(ctx, 'customer', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'claim', options); await models.StarredModule.toggleStarredModule(ctx, 'claim', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'order', options); await models.StarredModule.toggleStarredModule(ctx, 'order', options);
await app.models.StarredModule.toggleStarredModule(ctx, 'zone', options); await models.StarredModule.toggleStarredModule(ctx, 'zone', options);
const items = await app.models.StarredModule.findOne(filter, options); const items = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'customer'; filter.where.moduleFk = 'customer';
let clients = await app.models.StarredModule.findOne(filter, options); let clients = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'claim'; filter.where.moduleFk = 'claim';
const claims = await app.models.StarredModule.findOne(filter, options); const claims = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'order'; filter.where.moduleFk = 'order';
let orders = await app.models.StarredModule.findOne(filter, options); let orders = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'zone'; filter.where.moduleFk = 'zone';
const zones = await app.models.StarredModule.findOne(filter, options); const zones = await models.StarredModule.findOne(filter, options);
expect(items.position).toEqual(1); expect(items.position).toEqual(1);
expect(clients.position).toEqual(2); expect(clients.position).toEqual(2);
@ -202,14 +202,14 @@ describe('setPosition()', () => {
expect(orders.position).toEqual(4); expect(orders.position).toEqual(4);
expect(zones.position).toEqual(5); expect(zones.position).toEqual(5);
await app.models.StarredModule.toggleStarredModule(ctx, 'claim', options); await models.StarredModule.toggleStarredModule(ctx, 'claim', options);
await app.models.StarredModule.setPosition(ctx, 'customer', 'right', options); await models.StarredModule.setPosition(ctx, 'customer', 'right', options);
filter.where.moduleFk = 'customer'; filter.where.moduleFk = 'customer';
clients = await app.models.StarredModule.findOne(filter, options); clients = await models.StarredModule.findOne(filter, options);
filter.where.moduleFk = 'order'; filter.where.moduleFk = 'order';
orders = await app.models.StarredModule.findOne(filter, options); orders = await models.StarredModule.findOne(filter, options);
expect(orders.position).toEqual(2); expect(orders.position).toEqual(2);
expect(clients.position).toEqual(4); expect(clients.position).toEqual(4);

View File

@ -1,4 +1,4 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
describe('toggleStarredModule()', () => { describe('toggleStarredModule()', () => {
@ -21,16 +21,16 @@ describe('toggleStarredModule()', () => {
}); });
it('should create a new starred module and then remove it by calling the method again with same args', async() => { it('should create a new starred module and then remove it by calling the method again with same args', async() => {
const starredModule = await app.models.StarredModule.toggleStarredModule(ctx, 'order'); const starredModule = await models.StarredModule.toggleStarredModule(ctx, 'order');
let starredModules = await app.models.StarredModule.getStarredModules(ctx); let starredModules = await models.StarredModule.getStarredModules(ctx);
expect(starredModules.length).toEqual(1); expect(starredModules.length).toEqual(1);
expect(starredModule.moduleFk).toEqual('order'); expect(starredModule.moduleFk).toEqual('order');
expect(starredModule.workerFk).toEqual(activeCtx.accessToken.userId); expect(starredModule.workerFk).toEqual(activeCtx.accessToken.userId);
expect(starredModule.position).toEqual(starredModules.length); expect(starredModule.position).toEqual(starredModules.length);
await app.models.StarredModule.toggleStarredModule(ctx, 'order'); await models.StarredModule.toggleStarredModule(ctx, 'order');
starredModules = await app.models.StarredModule.getStarredModules(ctx); starredModules = await models.StarredModule.getStarredModules(ctx);
expect(starredModules.length).toEqual(0); expect(starredModules.length).toEqual(0);
}); });

View File

@ -22,14 +22,8 @@ module.exports = Self => {
let userId = ctx.req.accessToken.userId; let userId = ctx.req.accessToken.userId;
let models = Self.app.models; let models = Self.app.models;
let user = await models.Account.findById(userId, { let user = await Self.findById(userId, {
fields: ['id', 'name', 'nickname', 'email', 'lang'], fields: ['id', 'name', 'nickname', 'email', 'lang']
include: {
relation: 'userConfig',
scope: {
fields: ['darkMode']
}
}
}); });
let roles = await models.RoleMapping.find({ let roles = await models.RoleMapping.find({

View File

@ -1,9 +1,14 @@
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('privileges', { Self.remoteMethod('privileges', {
description: 'Change role and hasGrant if user has privileges', description: 'Change role and hasGrant if user has privileges',
accepts: [ accepts: [
{
arg: 'ctx',
type: 'Object',
http: {source: 'context'}
},
{ {
arg: 'id', arg: 'id',
type: 'number', type: 'number',
@ -39,9 +44,9 @@ module.exports = Self => {
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
const user = await models.Account.findById(userId, {fields: ['hasGrant']}, myOptions); const user = await Self.findById(userId, {fields: ['hasGrant']}, myOptions);
const userToUpdate = await models.Account.findById(id, { const userToUpdate = await Self.findById(id, {
fields: ['id', 'name', 'hasGrant', 'roleFk', 'password'], fields: ['id', 'name', 'hasGrant', 'roleFk', 'password'],
include: { include: {
relation: 'role', relation: 'role',
@ -54,7 +59,7 @@ module.exports = Self => {
if (!user.hasGrant) if (!user.hasGrant)
throw new UserError(`You don't have grant privilege`); throw new UserError(`You don't have grant privilege`);
const hasRoleFromUser = await models.Account.hasRole(userId, userToUpdate.role().name, myOptions); const hasRoleFromUser = await Self.hasRole(userId, userToUpdate.role().name, myOptions);
if (!hasRoleFromUser) if (!hasRoleFromUser)
throw new UserError(`You don't own the role and you can't assign it to another user`); throw new UserError(`You don't own the role and you can't assign it to another user`);
@ -64,7 +69,7 @@ module.exports = Self => {
if (roleFk) { if (roleFk) {
const role = await models.Role.findById(roleFk, {fields: ['name']}, myOptions); const role = await models.Role.findById(roleFk, {fields: ['name']}, myOptions);
const hasRole = await models.Account.hasRole(userId, role.name, myOptions); const hasRole = await Self.hasRole(userId, role.name, myOptions);
if (!hasRole) if (!hasRole)
throw new UserError(`You don't own the role and you can't assign it to another user`); throw new UserError(`You don't own the role and you can't assign it to another user`);
@ -73,6 +78,6 @@ module.exports = Self => {
} }
await userToUpdate.save(userToUpdate); await userToUpdate.save(userToUpdate);
await models.UserAccount.sync(userToUpdate.name); await models.Account.sync(userToUpdate.name);
}; };
}; };

View File

@ -3,9 +3,9 @@ module.exports = Self => {
description: 'Send email to the user', description: 'Send email to the user',
accepts: [ accepts: [
{ {
arg: 'email', arg: 'user',
type: 'string', type: 'string',
description: 'The email of user', description: 'The user name or email',
required: true required: true
} }
], ],
@ -15,11 +15,20 @@ module.exports = Self => {
} }
}); });
Self.recoverPassword = async function(email) { Self.recoverPassword = async function(user) {
const models = Self.app.models; const models = Self.app.models;
const usesEmail = user.indexOf('@') !== -1;
if (!usesEmail) {
const account = await models.VnUser.findOne({
fields: ['email'],
where: {name: user}
});
user = account.email;
}
try { try {
await models.user.resetPassword({email, emailTemplate: 'recover-password'}); await Self.resetPassword({email: user, emailTemplate: 'recover-password'});
} catch (err) { } catch (err) {
if (err.code === 'EMAIL_NOT_FOUND') if (err.code === 'EMAIL_NOT_FOUND')
return; return;

View File

@ -1,14 +1,14 @@
const md5 = require('md5');
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethod('login', { Self.remoteMethod('signIn', {
description: 'Login a user with username/email and password', description: 'Login a user with username/email and password',
accepts: [ accepts: [
{ {
arg: 'user', arg: 'user',
type: 'String', type: 'String',
description: 'The user name or email', description: 'The user name or email',
http: {source: 'form'},
required: true required: true
}, { }, {
arg: 'password', arg: 'password',
@ -21,20 +21,20 @@ module.exports = Self => {
root: true root: true
}, },
http: { http: {
path: `/login`, path: `/signIn`,
verb: 'POST' verb: 'POST'
} }
}); });
Self.login = async function(user, password) { Self.signIn = async function(user, password) {
let $ = Self.app.models; let models = Self.app.models;
let token; let token;
let usesEmail = user.indexOf('@') !== -1; let usesEmail = user.indexOf('@') !== -1;
let userInfo = usesEmail let userInfo = usesEmail
? {email: user} ? {email: user}
: {username: user}; : {username: user};
let instance = await $.User.findOne({ let instance = await Self.findOne({
fields: ['username', 'password'], fields: ['username', 'password'],
where: userInfo where: userInfo
}); });
@ -42,29 +42,27 @@ module.exports = Self => {
let where = usesEmail let where = usesEmail
? {email: user} ? {email: user}
: {name: user}; : {name: user};
let account = await Self.findOne({ let vnUser = await Self.findOne({
fields: ['active', 'password'], fields: ['active'],
where where
}); });
let validCredentials = instance && ( let validCredentials = instance
await instance.hasPassword(password) || && await instance.hasPassword(password);
account.password == md5(password || '')
);
if (validCredentials) { if (validCredentials) {
if (!account.active) if (!vnUser.active)
throw new UserError('User disabled'); throw new UserError('User disabled');
try { try {
await $.UserAccount.sync(instance.username, password); await models.Account.sync(instance.username, password);
} catch (err) { } catch (err) {
console.warn(err); console.warn(err);
} }
} }
let loginInfo = Object.assign({password}, userInfo); let loginInfo = Object.assign({password}, userInfo);
token = await $.User.login(loginInfo, 'user'); token = await Self.login(loginInfo, 'user');
return {token: token.id}; return {token: token.id};
}; };
}; };

View File

@ -1,6 +1,6 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
describe('account privileges()', () => { describe('VnUser privileges()', () => {
const employeeId = 1; const employeeId = 1;
const developerId = 9; const developerId = 9;
const sysadminId = 66; const sysadminId = 66;
@ -10,13 +10,13 @@ describe('account privileges()', () => {
it('should throw an error when user not has privileges', async() => { it('should throw an error when user not has privileges', async() => {
const ctx = {req: {accessToken: {userId: developerId}}}; const ctx = {req: {accessToken: {userId: developerId}}};
const tx = await models.Account.beginTransaction({}); const tx = await models.VnUser.beginTransaction({});
let error; let error;
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
await models.Account.privileges(ctx, employeeId, null, true, options); await models.VnUser.privileges(ctx, employeeId, null, true, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -29,13 +29,13 @@ describe('account privileges()', () => {
it('should throw an error when user has privileges but not has the role', async() => { it('should throw an error when user has privileges but not has the role', async() => {
const ctx = {req: {accessToken: {userId: sysadminId}}}; const ctx = {req: {accessToken: {userId: sysadminId}}};
const tx = await models.Account.beginTransaction({}); const tx = await models.VnUser.beginTransaction({});
let error; let error;
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
await models.Account.privileges(ctx, employeeId, rootId, null, options); await models.VnUser.privileges(ctx, employeeId, rootId, null, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -48,13 +48,13 @@ describe('account privileges()', () => {
it('should throw an error when user has privileges but not has the role from user', async() => { it('should throw an error when user has privileges but not has the role from user', async() => {
const ctx = {req: {accessToken: {userId: sysadminId}}}; const ctx = {req: {accessToken: {userId: sysadminId}}};
const tx = await models.Account.beginTransaction({}); const tx = await models.VnUser.beginTransaction({});
let error; let error;
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
await models.Account.privileges(ctx, itBossId, developerId, null, options); await models.VnUser.privileges(ctx, itBossId, developerId, null, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -67,7 +67,7 @@ describe('account privileges()', () => {
it('should change role', async() => { it('should change role', async() => {
const ctx = {req: {accessToken: {userId: sysadminId}}}; const ctx = {req: {accessToken: {userId: sysadminId}}};
const tx = await models.Account.beginTransaction({}); const tx = await models.VnUser.beginTransaction({});
const options = {transaction: tx}; const options = {transaction: tx};
const agency = await models.Role.findOne({ const agency = await models.Role.findOne({
@ -79,8 +79,8 @@ describe('account privileges()', () => {
let error; let error;
let result; let result;
try { try {
await models.Account.privileges(ctx, clarkKent, agency.id, null, options); await models.VnUser.privileges(ctx, clarkKent, agency.id, null, options);
result = await models.Account.findById(clarkKent, null, options); result = await models.VnUser.findById(clarkKent, null, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -94,14 +94,14 @@ describe('account privileges()', () => {
it('should change hasGrant', async() => { it('should change hasGrant', async() => {
const ctx = {req: {accessToken: {userId: sysadminId}}}; const ctx = {req: {accessToken: {userId: sysadminId}}};
const tx = await models.Account.beginTransaction({}); const tx = await models.VnUser.beginTransaction({});
let error; let error;
let result; let result;
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
await models.Account.privileges(ctx, clarkKent, null, true, options); await models.VnUser.privileges(ctx, clarkKent, null, true, options);
result = await models.Account.findById(clarkKent, null, options); result = await models.VnUser.findById(clarkKent, null, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -1,25 +1,25 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('account login()', () => { describe('VnUser signIn()', () => {
describe('when credentials are correct', () => { describe('when credentials are correct', () => {
it('should return the token', async() => { it('should return the token', async() => {
let login = await app.models.Account.login('salesAssistant', 'nightmare'); let login = await models.VnUser.signIn('salesAssistant', 'nightmare');
let accessToken = await app.models.AccessToken.findById(login.token); let accessToken = await models.AccessToken.findById(login.token);
let ctx = {req: {accessToken: accessToken}}; let ctx = {req: {accessToken: accessToken}};
expect(login.token).toBeDefined(); expect(login.token).toBeDefined();
await app.models.Account.logout(ctx); await models.VnUser.signOut(ctx);
}); });
it('should return the token if the user doesnt exist but the client does', async() => { it('should return the token if the user doesnt exist but the client does', async() => {
let login = await app.models.Account.login('PetterParker', 'nightmare'); let login = await models.VnUser.signIn('PetterParker', 'nightmare');
let accessToken = await app.models.AccessToken.findById(login.token); let accessToken = await models.AccessToken.findById(login.token);
let ctx = {req: {accessToken: accessToken}}; let ctx = {req: {accessToken: accessToken}};
expect(login.token).toBeDefined(); expect(login.token).toBeDefined();
await app.models.Account.logout(ctx); await models.VnUser.signOut(ctx);
}); });
}); });
@ -28,7 +28,7 @@ describe('account login()', () => {
let error; let error;
try { try {
await app.models.Account.login('IDontExist', 'TotallyWrongPassword'); await models.VnUser.signIn('IDontExist', 'TotallyWrongPassword');
} catch (e) { } catch (e) {
error = e; error = e;
} }

View File

@ -1,13 +1,13 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('account logout()', () => { describe('VnUser signOut()', () => {
it('should logout and remove token after valid login', async() => { it('should logout and remove token after valid login', async() => {
let loginResponse = await app.models.Account.login('buyer', 'nightmare'); let loginResponse = await models.VnUser.signOut('buyer', 'nightmare');
let accessToken = await app.models.AccessToken.findById(loginResponse.token); let accessToken = await models.AccessToken.findById(loginResponse.token);
let ctx = {req: {accessToken: accessToken}}; let ctx = {req: {accessToken: accessToken}};
let logoutResponse = await app.models.Account.logout(ctx); let logoutResponse = await models.VnUser.signOut(ctx);
let tokenAfterLogout = await app.models.AccessToken.findById(loginResponse.token); let tokenAfterLogout = await models.AccessToken.findById(loginResponse.token);
expect(logoutResponse).toBeTrue(); expect(logoutResponse).toBeTrue();
expect(tokenAfterLogout).toBeNull(); expect(tokenAfterLogout).toBeNull();
@ -18,7 +18,7 @@ describe('account logout()', () => {
let ctx = {req: {accessToken: {id: 'invalidToken'}}}; let ctx = {req: {accessToken: {id: 'invalidToken'}}};
try { try {
response = await app.models.Account.logout(ctx); response = await models.VnUser.signOut(ctx);
} catch (e) { } catch (e) {
error = e; error = e;
} }
@ -32,7 +32,7 @@ describe('account logout()', () => {
let ctx = {req: {accessToken: null}}; let ctx = {req: {accessToken: null}};
try { try {
response = await app.models.Account.logout(ctx); response = await models.VnUser.signOut(ctx);
} catch (e) { } catch (e) {
error = e; error = e;
} }

View File

@ -1,7 +1,4 @@
{ {
"Account": {
"dataSource": "vn"
},
"AccountingType": { "AccountingType": {
"dataSource": "vn" "dataSource": "vn"
}, },
@ -125,10 +122,10 @@
"UserConfigView": { "UserConfigView": {
"dataSource": "vn" "dataSource": "vn"
}, },
"UserLog": { "Warehouse": {
"dataSource": "vn" "dataSource": "vn"
}, },
"Warehouse": { "VnUser": {
"dataSource": "vn" "dataSource": "vn"
}, },
"OsTicket": { "OsTicket": {

View File

@ -1,139 +0,0 @@
/* eslint max-len: ["error", { "code": 150 }]*/
const md5 = require('md5');
const LoopBackContext = require('loopback-context');
const {Email} = require('vn-print');
module.exports = Self => {
require('../methods/account/login')(Self);
require('../methods/account/logout')(Self);
require('../methods/account/acl')(Self);
require('../methods/account/change-password')(Self);
require('../methods/account/set-password')(Self);
require('../methods/account/recover-password')(Self);
require('../methods/account/validate-token')(Self);
require('../methods/account/privileges')(Self);
// Validations
Self.validatesFormatOf('email', {
message: 'Invalid email',
allowNull: true,
allowBlank: true,
with: /^[\w|.|-]+@[\w|-]+(\.[\w|-]+)*(,[\w|.|-]+@[\w|-]+(\.[\w|-]+)*)*$/
});
Self.validatesUniquenessOf('name', {
message: `A client with that Web User name already exists`
});
Self.observe('before save', async function(ctx) {
if (ctx.currentInstance && ctx.currentInstance.id && ctx.data && ctx.data.password)
ctx.data.password = md5(ctx.data.password);
});
Self.afterRemote('prototype.patchAttributes', async(ctx, instance) => {
if (!ctx.args || !ctx.args.data.email) return;
const models = Self.app.models;
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 userId = ctx.instance.id;
const user = await models.user.findById(userId);
class Mailer {
async send(verifyOptions, cb) {
const params = {
url: verifyOptions.verifyHref,
recipient: verifyOptions.to,
lang: ctx.req.getLocale()
};
const email = new Email('email-verify', params);
email.send();
cb(null, verifyOptions.to);
}
}
const options = {
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 user.verify(options);
});
Self.remoteMethod('getCurrentUserData', {
description: 'Gets the current user data',
accepts: [
{
arg: 'ctx',
type: 'object',
http: {source: 'context'}
}
],
returns: {
type: 'object',
root: true
},
http: {
verb: 'GET',
path: '/getCurrentUserData'
}
});
Self.getCurrentUserData = async function(ctx) {
let userId = ctx.req.accessToken.userId;
return await Self.findById(userId, {
fields: ['id', 'name', 'nickname']
});
};
/**
* Checks if user has a role.
*
* @param {Integer} userId The user id
* @param {String} name The role name
* @param {object} options Options
* @return {Boolean} %true if user has the role, %false otherwise
*/
Self.hasRole = async function(userId, name, options) {
let roles = await Self.getRoles(userId, options);
return roles.some(role => role == name);
};
/**
* Get all user roles.
*
* @param {Integer} userId The user id
* @param {object} options Options
* @return {object} User role list
*/
Self.getRoles = async(userId, options) => {
let result = await Self.rawSql(
`SELECT r.name
FROM account.user u
JOIN account.roleRole rr ON rr.role = u.role
JOIN account.role r ON r.id = rr.inheritsFrom
WHERE u.id = ?`, [userId], options);
let roles = [];
for (role of result)
roles.push(role.name);
return roles;
};
};

View File

@ -3,4 +3,5 @@ module.exports = Self => {
require('../methods/collection/newCollection')(Self); require('../methods/collection/newCollection')(Self);
require('../methods/collection/getSectors')(Self); require('../methods/collection/getSectors')(Self);
require('../methods/collection/setSaleQuantity')(Self); require('../methods/collection/setSaleQuantity')(Self);
require('../methods/collection/previousLabel')(Self);
}; };

View File

@ -20,10 +20,9 @@
"type": "date" "type": "date"
} }
}, },
"scope": { "scope": {
"where" :{ "where" :{
"expired": null "expired": null
} }
} }
} }

View File

@ -9,17 +9,29 @@
"properties": { "properties": {
"id": { "id": {
"id": true, "id": true,
"type": "number", "type": "number"
"forceId": false
}, },
"date": { "created": {
"type": "date" "type": "date"
}, },
"m3":{ "longitude":{
"type": "number" "type": "number"
}, },
"warehouseFk":{ "latitude":{
"type": "number"
},
"dated":{
"type": "date"
},
"ticketFk":{
"type": "number" "type": "number"
} }
} },
"relations": {
"ticket": {
"type": "belongsTo",
"model": "Ticket",
"foreignKey": "ticketFk"
}
}
} }

View File

@ -54,8 +54,8 @@ module.exports = Self => {
const writeRole = dmsType.writeRole() && dmsType.writeRole().name; const writeRole = dmsType.writeRole() && dmsType.writeRole().name;
const requiredRole = readRole || writeRole; const requiredRole = readRole || writeRole;
const hasRequiredRole = await models.Account.hasRole(myUserId, requiredRole, options); const hasRequiredRole = await models.VnUser.hasRole(myUserId, requiredRole, options);
const isRoot = await models.Account.hasRole(myUserId, 'root', options); const isRoot = await models.VnUser.hasRole(myUserId, 'root', options);
if (isRoot || hasRequiredRole) if (isRoot || hasRequiredRole)
return true; return true;

View File

@ -44,4 +44,4 @@
"principalId": "$everyone", "principalId": "$everyone",
"permission": "ALLOW" "permission": "ALLOW"
}] }]
} }

View File

@ -6,7 +6,6 @@ module.exports = Self => {
require('../methods/dms/removeFile')(Self); require('../methods/dms/removeFile')(Self);
require('../methods/dms/updateFile')(Self); require('../methods/dms/updateFile')(Self);
require('../methods/dms/deleteTrashFiles')(Self); require('../methods/dms/deleteTrashFiles')(Self);
require('../methods/dms/saveSign')(Self);
Self.checkRole = async function(ctx, id) { Self.checkRole = async function(ctx, id) {
const models = Self.app.models; const models = Self.app.models;

View File

@ -20,7 +20,7 @@
"relations": { "relations": {
"user": { "user": {
"type": "belongsTo", "type": "belongsTo",
"model": "Account", "model": "VnUser",
"foreignKey": "userFk" "foreignKey": "userFk"
} }
}, },

View File

@ -9,10 +9,11 @@ module.exports = Self => {
* @return {boolean} True for user with read privileges * @return {boolean} True for user with read privileges
*/ */
Self.hasReadRole = async(ctx, name, options) => { Self.hasReadRole = async(ctx, name, options) => {
const collection = await Self.findOne({where: {name}}, { const collection = await Self.findOne({
include: { include: {
relation: 'readRole' relation: 'readRole'
} },
where: {name}
}, options); }, options);
return await hasRole(ctx, collection, options); return await hasRole(ctx, collection, options);
@ -53,8 +54,8 @@ module.exports = Self => {
const writeRole = collection.writeRole() && collection.writeRole().name; const writeRole = collection.writeRole() && collection.writeRole().name;
const requiredRole = readRole || writeRole; const requiredRole = readRole || writeRole;
const hasRequiredRole = await models.Account.hasRole(myUserId, requiredRole, options); const hasRequiredRole = await models.VnUser.hasRole(myUserId, requiredRole, options);
const isRoot = await models.Account.hasRole(myUserId, 'root', options); const isRoot = await models.VnUser.hasRole(myUserId, 'root', options);
if (isRoot || hasRequiredRole) if (isRoot || hasRequiredRole)
return true; return true;

View File

@ -1,161 +1,112 @@
const fs = require('fs-extra'); const fs = require('fs-extra');
const sharp = require('sharp');
const path = require('path'); const path = require('path');
const readChunk = require('read-chunk'); const gm = require('gm');
const imageType = require('image-type');
const bmp = require('bmp-js');
module.exports = Self => { module.exports = Self => {
require('../methods/image/download')(Self); require('../methods/image/download')(Self);
require('../methods/image/upload')(Self); require('../methods/image/upload')(Self);
require('../methods/image/scrub')(Self);
// Function extracted from jimp package (utils) Self.resize = async function({collectionName, srcFile, fileName, entityId}) {
function scan(image, x, y, w, h, f) {
// round input
x = Math.round(x);
y = Math.round(y);
w = Math.round(w);
h = Math.round(h);
for (let _y = y; _y < y + h; _y++) {
for (let _x = x; _x < x + w; _x++) {
const idx = (image.bitmap.width * _y + _x) << 2;
f.call(image, _x, _y, idx);
}
}
return image;
}
// Function extracted from jimp package (type-bmp)
function fromAGBR(bitmap) {
return scan({bitmap}, 0, 0, bitmap.width, bitmap.height, function(
x,
y,
index
) {
const alpha = this.bitmap.data[index + 0];
const blue = this.bitmap.data[index + 1];
const green = this.bitmap.data[index + 2];
const red = this.bitmap.data[index + 3];
this.bitmap.data[index + 0] = red;
this.bitmap.data[index + 1] = green;
this.bitmap.data[index + 2] = blue;
this.bitmap.data[index + 3] = bitmap.is_with_alpha ? alpha : 0xff;
}).bitmap;
}
Self.registerImage = async(collectionName, srcFilePath, fileName, entityId) => {
const models = Self.app.models; const models = Self.app.models;
const tx = await Self.beginTransaction({});
const myOptions = {transaction: tx};
try { const collection = await models.ImageCollection.findOne(
const collection = await models.ImageCollection.findOne({ {
fields: [ fields: [
'id', 'id',
'name',
'maxWidth', 'maxWidth',
'maxHeight', 'maxHeight',
'model', 'model',
'property' 'property',
], ],
where: {name: collectionName}, where: {name: collectionName},
include: { include: {
relation: 'sizes', relation: 'sizes',
scope: { scope: {
fields: ['width', 'height', 'crop'] fields: ['width', 'height', 'crop'],
} },
} },
}, myOptions); }
);
const data = { // Insert image row
name: fileName, const imageName = path.parse(fileName).name;
await models.Image.upsertWithWhere(
{
name: imageName,
collectionFk: collectionName collectionFk: collectionName
}; },
const newImage = await Self.upsertWithWhere(data, { {
name: fileName, name: imageName,
collectionFk: collectionName, collectionFk: collectionName,
updated: (new Date).getTime() updated: Date.vnNow() / 1000,
}, myOptions);
// Resizes and saves the image
const container = await models.ImageContainer.container(collectionName);
const rootPath = container.client.root;
const collectionDir = path.join(rootPath, collectionName);
const file = `${fileName}.png`;
const dstDir = path.join(collectionDir, 'full');
const dstFile = path.join(dstDir, file);
const buffer = readChunk.sync(srcFilePath, 0, 12);
const type = imageType(buffer);
let sharpOptions;
let imgSrc = srcFilePath;
if (type.mime == 'image/bmp') {
const bmpBuffer = fs.readFileSync(srcFilePath);
const bmpData = fromAGBR(bmp.decode(bmpBuffer));
imgSrc = bmpData.data;
sharpOptions = {
raw: {
width: bmpData.width,
height: bmpData.height,
channels: 4
}
};
} }
);
const resizeOpts = { // Update entity image file name
withoutEnlargement: true, const model = models[collection.model];
fit: 'inside' if (!model) throw new Error('No matching model found');
};
await fs.mkdir(dstDir, {recursive: true}); const entity = await model.findById(entityId);
await sharp(imgSrc, sharpOptions) if (entity) {
.resize(collection.maxWidth, collection.maxHeight, resizeOpts) await entity.updateAttribute(
.png() collection.property,
.toFile(dstFile); imageName
);
}
const sizes = collection.sizes(); // Resize
for (let size of sizes) { const container = await models.ImageContainer.container(
const dstDir = path.join(collectionDir, `${size.width}x${size.height}`); collectionName
const dstFile = path.join(dstDir, file); );
const resizeOpts = { const rootPath = container.client.root;
withoutEnlargement: true, const collectionDir = path.join(rootPath, collectionName);
fit: size.crop ? 'cover' : 'inside'
};
await fs.mkdir(dstDir, {recursive: true}); // To max size
await sharp(imgSrc, sharpOptions) const {maxWidth, maxHeight} = collection;
.resize(size.width, size.height, resizeOpts) const fullSizePath = path.join(collectionDir, 'full');
.png() const toFullSizePath = `${fullSizePath}/${fileName}`;
.toFile(dstFile);
}
const model = models[collection.model]; await fs.mkdir(fullSizePath, {recursive: true});
await new Promise((resolve, reject) => {
gm(srcFile)
.resize(maxWidth, maxHeight, '>')
.setFormat('png')
.quality(100)
.write(toFullSizePath, function(err) {
if (err) reject(err);
if (!err) resolve();
});
});
if (!model) // To collection sizes
throw new Error('Matching model not found'); for (const size of collection.sizes()) {
const {width, height} = size;
const item = await model.findById(entityId, null, myOptions); const sizePath = path.join(collectionDir, `${width}x${height}`);
if (item) { const toSizePath = `${sizePath}/${fileName}`;
await item.updateAttribute(
collection.property,
fileName,
myOptions
);
}
if (fs.existsSync(srcFilePath)) await fs.mkdir(sizePath, {recursive: true});
await fs.unlink(srcFilePath); await new Promise((resolve, reject) => {
const gmInstance = gm(srcFile);
await tx.commit(); if (size.crop) {
gmInstance
.resize(width, height, '^')
.gravity('Center')
.crop(width, height);
}
return newImage; if (!size.crop) gmInstance.resize(width, height, '>');
} catch (e) {
await tx.rollback(); gmInstance
throw e; .setFormat('png')
.quality(100)
.write(toSizePath, function(err) {
if (err) reject(err);
if (!err) resolve();
});
});
} }
}; };
}; };

View File

@ -6,6 +6,16 @@
"table": "util.notificationAcl" "table": "util.notificationAcl"
} }
}, },
"properties":{
"notificationFk": {
"id": true,
"type": "number"
},
"roleFk":{
"id": true,
"type": "number"
}
},
"relations": { "relations": {
"notification": { "notification": {
"type": "belongsTo", "type": "belongsTo",

View File

@ -31,7 +31,7 @@
}, },
"author": { "author": {
"type": "belongsTo", "type": "belongsTo",
"model": "Account", "model": "VnUser",
"foreignKey": "authorFk" "foreignKey": "authorFk"
} }
} }

View File

@ -0,0 +1,62 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.observe('before save', async function(ctx) {
const models = Self.app.models;
const userId = ctx.options.accessToken.userId;
const user = await ctx.instance.userFk;
const modifiedUser = await getUserToModify(null, user, models);
if (userId != modifiedUser.id && userId != modifiedUser.bossFk)
throw new UserError('You dont have permission to modify this user');
});
Self.remoteMethod('deleteNotification', {
description: 'Deletes a notification subscription',
accepts: [
{
arg: 'ctx',
type: 'object',
http: {source: 'context'}
},
{
arg: 'notificationId',
type: 'number',
required: true
},
],
returns: {
type: 'object',
root: true
},
http: {
verb: 'POST',
path: '/deleteNotification'
}
});
Self.deleteNotification = async function(ctx, notificationId) {
const models = Self.app.models;
const user = ctx.req.accessToken.userId;
const modifiedUser = await getUserToModify(notificationId, null, models);
if (user != modifiedUser.id && user != modifiedUser.bossFk)
throw new UserError('You dont have permission to modify this user');
await models.NotificationSubscription.destroyById(notificationId);
};
async function getUserToModify(notificationId, userFk, models) {
let userToModify = userFk;
if (notificationId) {
const subscription = await models.NotificationSubscription.findById(notificationId);
userToModify = subscription.userFk;
}
return await models.Worker.findOne({
fields: ['id', 'bossFk'],
where: {
id: userToModify
}
});
}
};

View File

@ -7,15 +7,18 @@
} }
}, },
"properties": { "properties": {
"notificationFk": { "id": {
"type": "number", "type": "number",
"id": true, "id": true,
"description": "Identifier" "description": "Primary key"
},
"notificationFk": {
"type": "number",
"description": "Foreign key to Notification"
}, },
"userFk": { "userFk": {
"type": "number", "type": "number",
"id": true, "description": "Foreign key to Account"
"description": "Identifier"
} }
}, },
"relations": { "relations": {
@ -26,7 +29,7 @@
}, },
"user": { "user": {
"type": "belongsTo", "type": "belongsTo",
"model": "Account", "model": "VnUser",
"foreignKey": "userFk" "foreignKey": "userFk"
} }
} }

View File

@ -1,8 +1,8 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('loopback model Company', () => { describe('loopback model Company', () => {
it('should check that the company FTH doesnt exists', async() => { it('should check that the company FTH doesnt exists', async() => {
let result = await app.models.Company.findOne({where: {code: 'FTH'}}); let result = await models.Company.findOne({where: {code: 'FTH'}});
expect(result).toBeFalsy(); expect(result).toBeFalsy();
}); });

View File

@ -1,6 +1,6 @@
const app = require('vn-loopback/server/server'); const {models} = require('vn-loopback/server/server');
describe('Dms', () => { describe('Dms', () => {
const Dms = app.models.Dms; const Dms = models.Dms;
describe('getFile()', () => { describe('getFile()', () => {
it('should return a response with text content-type', async() => { it('should return a response with text content-type', async() => {
@ -23,7 +23,7 @@ describe('Dms', () => {
it('should return an error for a record does not exists', async() => { it('should return an error for a record does not exists', async() => {
let error = {}; let error = {};
try { try {
await app.models.Dms.getFile('NotExistentId'); await models.Dms.getFile('NotExistentId');
} catch (e) { } catch (e) {
error = e; error = e;
} }

View File

@ -0,0 +1,74 @@
const models = require('vn-loopback/server/server').models;
describe('loopback model NotificationSubscription', () => {
it('Should fail to delete a notification if the user is not editing itself or a subordinate', async() => {
const tx = await models.NotificationSubscription.beginTransaction({});
try {
const options = {transaction: tx};
const user = 9;
const notificationSubscriptionId = 2;
const ctx = {req: {accessToken: {userId: user}}};
const notification = await models.NotificationSubscription.findById(notificationSubscriptionId);
let error;
try {
await models.NotificationSubscription.deleteNotification(ctx, notification.id, options);
} catch (e) {
error = e;
}
expect(error.message).toContain('You dont have permission to modify this user');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('Should delete a notification if the user is editing itself', async() => {
const tx = await models.NotificationSubscription.beginTransaction({});
try {
const options = {transaction: tx};
const user = 9;
const notificationSubscriptionId = 4;
const ctx = {req: {accessToken: {userId: user}}};
const notification = await models.NotificationSubscription.findById(notificationSubscriptionId);
await models.NotificationSubscription.deleteNotification(ctx, notification.id, options);
const deletedNotification = await models.NotificationSubscription.findById(notificationSubscriptionId);
expect(deletedNotification).toBeNull();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('Should delete a notification if the user is editing a subordinate', async() => {
const tx = await models.NotificationSubscription.beginTransaction({});
try {
const options = {transaction: tx};
const user = 9;
const notificationSubscriptionId = 5;
const ctx = {req: {accessToken: {userId: user}}};
const notification = await models.NotificationSubscription.findById(notificationSubscriptionId);
await models.NotificationSubscription.deleteNotification(ctx, notification.id, options);
const deletedNotification = await models.NotificationSubscription.findById(notificationSubscriptionId);
expect(deletedNotification).toBeNull();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -1,7 +1,7 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
describe('account recoverPassword()', () => { describe('VnUser recoverPassword()', () => {
const userId = 1107; const userId = 1107;
const activeCtx = { const activeCtx = {
@ -21,9 +21,9 @@ describe('account recoverPassword()', () => {
it('should send email with token', async() => { it('should send email with token', async() => {
const userId = 1107; const userId = 1107;
const user = await models.Account.findById(userId); const user = await models.VnUser.findById(userId);
await models.Account.recoverPassword(user.email); await models.VnUser.recoverPassword(user.email);
const result = await models.AccessToken.findOne({where: {userId: userId}}); const result = await models.AccessToken.findOne({where: {userId: userId}});

View File

@ -1,14 +1,14 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
describe('loopback model Account', () => { 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() => {
let result = await models.Account.hasRole(1, 'employee'); let result = await models.VnUser.hasRole(1, 'employee');
expect(result).toBeTruthy(); expect(result).toBeTruthy();
}); });
it('should return false if the user doesnt have the given role', async() => { it('should return false if the user doesnt have the given role', async() => {
let result = await models.Account.hasRole(1, 'administrator'); let result = await models.VnUser.hasRole(1, 'administrator');
expect(result).toBeFalsy(); expect(result).toBeFalsy();
}); });

View File

@ -26,7 +26,7 @@
"relations": { "relations": {
"user": { "user": {
"type": "belongsTo", "type": "belongsTo",
"model": "Account", "model": "VnUser",
"foreignKey": "userFk" "foreignKey": "userFk"
} }
} }

View File

@ -39,9 +39,9 @@
"model": "Company", "model": "Company",
"foreignKey": "companyFk" "foreignKey": "companyFk"
}, },
"account": { "VnUser": {
"type": "belongsTo", "type": "belongsTo",
"model": "Account", "model": "VnUser",
"foreignKey": "userFk" "foreignKey": "userFk"
} }
} }

View File

@ -1,27 +0,0 @@
const LoopBackContext = require('loopback-context');
const {Email} = require('vn-print');
module.exports = function(Self) {
Self.on('resetPasswordRequest', async function(info) {
const loopBackContext = LoopBackContext.getCurrentContext();
const httpCtx = {req: loopBackContext.active};
const httpRequest = httpCtx.req.http.req;
const headers = httpRequest.headers;
const origin = headers.origin;
const user = await Self.app.models.Account.findById(info.user.id);
const params = {
recipient: info.email,
lang: user.lang,
url: `${origin}/#!/reset-password?access_token=${info.accessToken.id}`
};
const options = Object.assign({}, info.options);
for (const param in options)
params[param] = options[param];
const email = new Email(options.emailTemplate, params);
return email.send();
});
};

View File

@ -1,19 +0,0 @@
{
"name": "user",
"base": "User",
"options": {
"mysql": {
"table": "salix.User"
}
},
"properties": {
"id": {
"id": true,
"type": "number",
"forceId": false
},
"username":{
"type": "string"
}
}
}

110
back/models/vn-user.js Normal file
View File

@ -0,0 +1,110 @@
const vnModel = require('vn-loopback/common/models/vn-model');
const LoopBackContext = require('loopback-context');
const {Email} = require('vn-print');
module.exports = function(Self) {
vnModel(Self);
require('../methods/vn-user/signIn')(Self);
require('../methods/vn-user/acl')(Self);
require('../methods/vn-user/recover-password')(Self);
require('../methods/vn-user/validate-token')(Self);
require('../methods/vn-user/privileges')(Self);
// Validations
Self.validatesFormatOf('email', {
message: 'Invalid email',
allowNull: true,
allowBlank: true,
with: /^[\w|.|-]+@[\w|-]+(\.[\w|-]+)*(,[\w|.|-]+@[\w|-]+(\.[\w|-]+)*)*$/
});
Self.validatesUniquenessOf('name', {
message: `A client with that Web User name already exists`
});
Self.remoteMethod('getCurrentUserData', {
description: 'Gets the current user data',
accepts: [
{
arg: 'ctx',
type: 'Object',
http: {source: 'context'}
}
],
returns: {
type: 'Object',
root: true
},
http: {
verb: 'GET',
path: '/getCurrentUserData'
}
});
Self.getCurrentUserData = async function(ctx) {
let userId = ctx.req.accessToken.userId;
return await Self.findById(userId, {
fields: ['id', 'name', 'nickname']
});
};
/**
* Checks if user has a role.
*
* @param {Integer} userId The user id
* @param {String} name The role name
* @param {Object} options Options
* @return {Boolean} %true if user has the role, %false otherwise
*/
Self.hasRole = async function(userId, name, options) {
const roles = await Self.getRoles(userId, options);
return roles.some(role => role == name);
};
/**
* Get all user roles.
*
* @param {Integer} userId The user id
* @param {Object} options Options
* @return {Object} User role list
*/
Self.getRoles = async(userId, options) => {
const result = await Self.rawSql(
`SELECT r.name
FROM account.user u
JOIN account.roleRole rr ON rr.role = u.role
JOIN account.role r ON r.id = rr.inheritsFrom
WHERE u.id = ?`, [userId], options);
const roles = [];
for (const role of result)
roles.push(role.name);
return roles;
};
Self.on('resetPasswordRequest', async function(info) {
const loopBackContext = LoopBackContext.getCurrentContext();
const httpCtx = {req: loopBackContext.active};
const httpRequest = httpCtx.req.http.req;
const headers = httpRequest.headers;
const origin = headers.origin;
const user = await Self.app.models.VnUser.findById(info.user.id);
const params = {
recipient: info.email,
lang: user.lang,
url: `${origin}/#!/reset-password?access_token=${info.accessToken.id}`
};
const options = Object.assign({}, info.options);
for (const param in options)
params[param] = options[param];
const email = new Email(options.emailTemplate, params);
return email.send();
});
};

View File

@ -1,11 +1,13 @@
{ {
"name": "Account", "name": "VnUser",
"base": "VnModel", "base": "User",
"validateUpsert": true,
"options": { "options": {
"mysql": { "mysql": {
"table": "account.user" "table": "account.user"
} }
}, },
"resetPasswordTokenTTL": "604800",
"properties": { "properties": {
"id": { "id": {
"type": "number", "type": "number",
@ -15,6 +17,19 @@
"type": "string", "type": "string",
"required": true "required": true
}, },
"username": {
"type": "string",
"mysql": {
"columnName": "name"
}
},
"password": {
"type": "string",
"required": true,
"mysql": {
"columnName": "bcryptPassword"
}
},
"roleFk": { "roleFk": {
"type": "number", "type": "number",
"mysql": { "mysql": {
@ -27,10 +42,6 @@
"lang": { "lang": {
"type": "string" "type": "string"
}, },
"password": {
"type": "string",
"required": true
},
"bcryptPassword": { "bcryptPassword": {
"type": "string" "type": "string"
}, },
@ -40,9 +51,6 @@
"email": { "email": {
"type": "string" "type": "string"
}, },
"emailVerified": {
"type": "boolean"
},
"created": { "created": {
"type": "date" "type": "date"
}, },
@ -86,7 +94,7 @@
}, },
"acls": [ "acls": [
{ {
"property": "login", "property": "signIn",
"accessType": "EXECUTE", "accessType": "EXECUTE",
"principalType": "ROLE", "principalType": "ROLE",
"principalId": "$everyone", "principalId": "$everyone",
@ -100,13 +108,6 @@
"permission": "ALLOW" "permission": "ALLOW"
}, },
{ {
"property": "logout",
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
},
{
"property": "validateToken", "property": "validateToken",
"accessType": "EXECUTE", "accessType": "EXECUTE",
"principalType": "ROLE", "principalType": "ROLE",

View File

@ -30,7 +30,12 @@ async function test() {
const bootOptions = {dataSources}; const bootOptions = {dataSources};
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
app.boot(bootOptions); await new Promise((resolve, reject) => {
app.boot(bootOptions,
err => err ? reject(err) : resolve());
});
// FIXME: Workaround to wait for loopback to be ready
await app.models.Application.status();
const Jasmine = require('jasmine'); const Jasmine = require('jasmine');
const jasmine = new Jasmine(); const jasmine = new Jasmine();
@ -50,7 +55,7 @@ async function test() {
const JunitReporter = require('jasmine-reporters'); const JunitReporter = require('jasmine-reporters');
jasmine.addReporter(new JunitReporter.JUnitXmlReporter()); jasmine.addReporter(new JunitReporter.JUnitXmlReporter());
jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000; jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 90000;
jasmine.exitOnCompletion = true; jasmine.exitOnCompletion = true;
} }

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