Merge branch 'dev' into 3946-permisos
gitea/salix/pipeline/head This commit looks good
Details
gitea/salix/pipeline/head This commit looks good
Details
This commit is contained in:
commit
6d6ea802d0
|
@ -2,10 +2,11 @@ coverage
|
|||
node_modules
|
||||
dist
|
||||
storage
|
||||
.idea
|
||||
npm-debug.log
|
||||
.eslintcache
|
||||
datasources.*.json
|
||||
print.*.json
|
||||
db.json
|
||||
junit.xml
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
// Coloque su configuración en este archivo para sobrescribir la configuración predeterminada y de usuario.
|
||||
{
|
||||
// Carácter predeterminado de final de línea.
|
||||
"files.eol": "\n",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
}
|
||||
}
|
||||
// Coloque su configuración en este archivo para sobrescribir la configuración predeterminada y de usuario.
|
||||
{
|
||||
// Carácter predeterminado de final de línea.
|
||||
"files.eol": "\n",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"search.useIgnoreFiles": false,
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
|
||||
"eslint.format.enable": true,
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
# Changelog
|
||||
|
||||
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/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [2314.01] - 2023-04-20
|
||||
|
||||
### Added
|
||||
-
|
||||
|
||||
### Changed
|
||||
-
|
||||
|
||||
### Fixed
|
||||
-
|
||||
|
||||
## [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
|
||||
- (Tickets -> Control clientes) Eliminada sección
|
40
Dockerfile
40
Dockerfile
|
@ -1,32 +1,44 @@
|
|||
FROM debian:stretch-slim
|
||||
FROM debian:bullseye-slim
|
||||
ENV TZ Europe/Madrid
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# NodeJs
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
ca-certificates \
|
||||
gnupg2 \
|
||||
libfontconfig lftp \
|
||||
&& apt-get -y install xvfb gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 \
|
||||
libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 \
|
||||
libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 \
|
||||
libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 \
|
||||
libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget \
|
||||
&& curl -sL https://deb.nodesource.com/setup_12.x | bash - \
|
||||
graphicsmagick \
|
||||
&& curl -fsSL https://deb.nodesource.com/setup_14.x | bash - \
|
||||
&& apt-get install -y --no-install-recommends nodejs \
|
||||
&& npm install -g npm@8.19.2
|
||||
|
||||
# Puppeteer
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
nodejs \
|
||||
&& apt-get purge -y --auto-remove \
|
||||
gnupg2 \
|
||||
libfontconfig lftp xvfb gconf-service libasound2 libatk1.0-0 libc6 \
|
||||
libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 \
|
||||
libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 \
|
||||
libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 \
|
||||
libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 \
|
||||
libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \
|
||||
fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& npm -g install pm2
|
||||
|
||||
# Salix
|
||||
|
||||
WORKDIR /salix
|
||||
|
||||
COPY print/package.json print/package-lock.json print/
|
||||
RUN npm --prefix ./print install --omit=dev ./print
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
COPY loopback/package.json loopback/
|
||||
COPY print/package.json print/
|
||||
RUN npm install --only=prod
|
||||
RUN npm --prefix ./print install --only=prod ./print
|
||||
RUN npm install --omit=dev
|
||||
|
||||
COPY loopback loopback
|
||||
COPY back back
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env groovy
|
||||
|
||||
pipeline {
|
||||
agent any
|
||||
options {
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('privileges', {
|
||||
description: 'Change role and hasGrant if user has privileges',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'number',
|
||||
required: true,
|
||||
description: 'The user id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
{
|
||||
arg: 'roleFk',
|
||||
type: 'number',
|
||||
description: 'The new role for user',
|
||||
},
|
||||
{
|
||||
arg: 'hasGrant',
|
||||
type: 'boolean',
|
||||
description: 'Whether to has grant'
|
||||
}
|
||||
],
|
||||
http: {
|
||||
path: `/:id/privileges`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.privileges = async function(ctx, id, roleFk, hasGrant, options) {
|
||||
if (!(hasGrant != null || roleFk)) return;
|
||||
|
||||
const models = Self.app.models;
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
|
||||
const myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const user = await models.Account.findById(userId, {fields: ['hasGrant']}, myOptions);
|
||||
|
||||
const userToUpdate = await models.Account.findById(id, {
|
||||
fields: ['id', 'name', 'hasGrant', 'roleFk', 'password'],
|
||||
include: {
|
||||
relation: 'role',
|
||||
scope: {
|
||||
fields: ['name']
|
||||
}
|
||||
}
|
||||
}, myOptions);
|
||||
|
||||
if (!user.hasGrant)
|
||||
throw new UserError(`You don't have grant privilege`);
|
||||
|
||||
const hasRoleFromUser = await models.Account.hasRole(userId, userToUpdate.role().name, myOptions);
|
||||
|
||||
if (!hasRoleFromUser)
|
||||
throw new UserError(`You don't own the role and you can't assign it to another user`);
|
||||
|
||||
if (hasGrant != null)
|
||||
userToUpdate.hasGrant = hasGrant;
|
||||
|
||||
if (roleFk) {
|
||||
const role = await models.Role.findById(roleFk, {fields: ['name']}, myOptions);
|
||||
const hasRole = await models.Account.hasRole(userId, role.name, myOptions);
|
||||
|
||||
if (!hasRole)
|
||||
throw new UserError(`You don't own the role and you can't assign it to another user`);
|
||||
|
||||
userToUpdate.roleFk = roleFk;
|
||||
}
|
||||
|
||||
await userToUpdate.save(userToUpdate);
|
||||
await models.UserAccount.sync(userToUpdate.name);
|
||||
};
|
||||
};
|
|
@ -0,0 +1,39 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethod('recoverPassword', {
|
||||
description: 'Send email to the user',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'user',
|
||||
type: 'string',
|
||||
description: 'The user name or email',
|
||||
required: true
|
||||
}
|
||||
],
|
||||
http: {
|
||||
path: `/recoverPassword`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.recoverPassword = async function(user) {
|
||||
const models = Self.app.models;
|
||||
|
||||
const usesEmail = user.indexOf('@') !== -1;
|
||||
if (!usesEmail) {
|
||||
const account = await models.Account.findOne({
|
||||
fields: ['email'],
|
||||
where: {name: user}
|
||||
});
|
||||
user = account.email;
|
||||
}
|
||||
|
||||
try {
|
||||
await models.user.resetPassword({email: user, emailTemplate: 'recover-password'});
|
||||
} catch (err) {
|
||||
if (err.code === 'EMAIL_NOT_FOUND')
|
||||
return;
|
||||
else
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -0,0 +1,115 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
|
||||
describe('account privileges()', () => {
|
||||
const employeeId = 1;
|
||||
const developerId = 9;
|
||||
const sysadminId = 66;
|
||||
const itBossId = 104;
|
||||
const rootId = 100;
|
||||
const clarkKent = 1103;
|
||||
|
||||
it('should throw an error when user not has privileges', async() => {
|
||||
const ctx = {req: {accessToken: {userId: developerId}}};
|
||||
const tx = await models.Account.beginTransaction({});
|
||||
|
||||
let error;
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
await models.Account.privileges(ctx, employeeId, null, true, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
await tx.rollback();
|
||||
}
|
||||
|
||||
expect(error.message).toContain(`You don't have grant privilege`);
|
||||
});
|
||||
|
||||
it('should throw an error when user has privileges but not has the role', async() => {
|
||||
const ctx = {req: {accessToken: {userId: sysadminId}}};
|
||||
const tx = await models.Account.beginTransaction({});
|
||||
|
||||
let error;
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
await models.Account.privileges(ctx, employeeId, rootId, null, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
await tx.rollback();
|
||||
}
|
||||
|
||||
expect(error.message).toContain(`You don't own the role and you can't assign it to another user`);
|
||||
});
|
||||
|
||||
it('should throw an error when user has privileges but not has the role from user', async() => {
|
||||
const ctx = {req: {accessToken: {userId: sysadminId}}};
|
||||
const tx = await models.Account.beginTransaction({});
|
||||
|
||||
let error;
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
await models.Account.privileges(ctx, itBossId, developerId, null, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
await tx.rollback();
|
||||
}
|
||||
|
||||
expect(error.message).toContain(`You don't own the role and you can't assign it to another user`);
|
||||
});
|
||||
|
||||
it('should change role', async() => {
|
||||
const ctx = {req: {accessToken: {userId: sysadminId}}};
|
||||
const tx = await models.Account.beginTransaction({});
|
||||
|
||||
const options = {transaction: tx};
|
||||
const agency = await models.Role.findOne({
|
||||
where: {
|
||||
name: 'agency'
|
||||
}
|
||||
}, options);
|
||||
|
||||
let error;
|
||||
let result;
|
||||
try {
|
||||
await models.Account.privileges(ctx, clarkKent, agency.id, null, options);
|
||||
result = await models.Account.findById(clarkKent, null, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
await tx.rollback();
|
||||
}
|
||||
|
||||
expect(error).not.toBeDefined();
|
||||
expect(result.roleFk).toEqual(agency.id);
|
||||
});
|
||||
|
||||
it('should change hasGrant', async() => {
|
||||
const ctx = {req: {accessToken: {userId: sysadminId}}};
|
||||
const tx = await models.Account.beginTransaction({});
|
||||
|
||||
let error;
|
||||
let result;
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
await models.Account.privileges(ctx, clarkKent, null, true, options);
|
||||
result = await models.Account.findById(clarkKent, null, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
await tx.rollback();
|
||||
}
|
||||
|
||||
expect(error).not.toBeDefined();
|
||||
expect(result.hasGrant).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('account changePassword()', () => {
|
||||
describe('account setPassword()', () => {
|
||||
it('should throw an error when password does not meet requirements', async() => {
|
||||
let req = app.models.Account.setPassword(1, 'insecurePass');
|
||||
|
||||
|
|
|
@ -22,15 +22,19 @@ module.exports = Self => {
|
|||
|
||||
Self.latest = async filter => {
|
||||
const conn = Self.dataSource.connector;
|
||||
const minDate = new Date();
|
||||
const minDate = Date.vnNew();
|
||||
minDate.setFullYear(minDate.getFullYear() - 1);
|
||||
|
||||
const where = {dated: {gte: minDate}};
|
||||
filter = mergeFilters(filter, {where});
|
||||
|
||||
const stmt = new ParameterizedSQL(
|
||||
`SELECT * FROM campaign`);
|
||||
`SELECT * FROM (`);
|
||||
stmt.merge('SELECT * FROM campaign');
|
||||
stmt.merge(conn.makeWhere(filter.where));
|
||||
stmt.merge('ORDER BY dated ASC');
|
||||
stmt.merge('LIMIT 10000000000000000000');
|
||||
stmt.merge(') sub');
|
||||
stmt.merge('GROUP BY code');
|
||||
stmt.merge(conn.makePagination(filter));
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ const app = require('vn-loopback/server/server');
|
|||
|
||||
describe('campaign latest()', () => {
|
||||
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 randomIndex = Math.floor(Math.random() * result.length);
|
||||
const campaignDated = result[randomIndex].dated;
|
||||
|
@ -12,7 +12,7 @@ describe('campaign latest()', () => {
|
|||
});
|
||||
|
||||
it('should return the campaigns from the current year', async() => {
|
||||
const now = new Date();
|
||||
const now = Date.vnNew();
|
||||
const currentYear = now.getFullYear();
|
||||
const result = await app.models.Campaign.latest({
|
||||
where: {dated: {like: `%${currentYear}%`}}
|
||||
|
|
|
@ -4,7 +4,7 @@ describe('campaign upcoming()', () => {
|
|||
it('should return the upcoming campaign but from the last year', async() => {
|
||||
const response = await app.models.Campaign.upcoming();
|
||||
const campaignDated = response.dated;
|
||||
const now = new Date();
|
||||
const now = Date.vnNew();
|
||||
|
||||
expect(campaignDated).toEqual(jasmine.any(Date));
|
||||
expect(campaignDated).toBeLessThanOrEqual(now);
|
||||
|
|
|
@ -14,7 +14,7 @@ module.exports = Self => {
|
|||
});
|
||||
|
||||
Self.upcoming = async() => {
|
||||
const minDate = new Date();
|
||||
const minDate = Date.vnNew();
|
||||
minDate.setFullYear(minDate.getFullYear() - 1);
|
||||
|
||||
return Self.findOne({
|
||||
|
|
|
@ -21,7 +21,7 @@ module.exports = Self => {
|
|||
|
||||
if (!this.login) return;
|
||||
|
||||
if (Date.now() > this.login.expires)
|
||||
if (Date.vnNow() > this.login.expires)
|
||||
this.login = await requestToken();
|
||||
|
||||
return this.login;
|
||||
|
@ -48,7 +48,7 @@ module.exports = Self => {
|
|||
userId: requestData.userId,
|
||||
token: requestData.authToken
|
||||
},
|
||||
expires: Date.now() + (1000 * 60 * tokenLifespan)
|
||||
expires: Date.vnNow() + (1000 * 60 * tokenLifespan)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ module.exports = Self => {
|
|||
},
|
||||
http: {
|
||||
path: `/notifyIssues`,
|
||||
verb: 'GET'
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -32,7 +32,7 @@ module.exports = Self => {
|
|||
let message = $t(`There's a new urgent ticket:`);
|
||||
const ostUri = 'https://cau.verdnatura.es/scp/tickets.php?id=';
|
||||
tickets.forEach(ticket => {
|
||||
message += `\r\n[ID: *${ticket.number}* - ${ticket.subject} (@${ticket.username})](${ostUri + ticket.id})`;
|
||||
message += `\r\n[ID: ${ticket.number} - ${ticket.subject} @${ticket.username}](${ostUri + ticket.id})`;
|
||||
});
|
||||
|
||||
const department = await models.Department.findOne({
|
||||
|
@ -42,7 +42,5 @@ module.exports = Self => {
|
|||
|
||||
if (channelName)
|
||||
return Self.send(ctx, `#${channelName}`, `@all ➔ ${message}`);
|
||||
|
||||
return;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -30,16 +30,23 @@ module.exports = Self => {
|
|||
const recipient = to.replace('@', '');
|
||||
|
||||
if (sender.name != recipient) {
|
||||
await models.Chat.create({
|
||||
const chat = await models.Chat.create({
|
||||
senderFk: sender.id,
|
||||
recipient: to,
|
||||
dated: new Date(),
|
||||
dated: Date.vnNew(),
|
||||
checkUserStatus: 0,
|
||||
message: message,
|
||||
status: 0,
|
||||
status: 'sending',
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,18 +24,13 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.sendCheckingPresence = async(ctx, recipientId, message, options) => {
|
||||
Self.sendCheckingPresence = async(ctx, recipientId, message) => {
|
||||
if (!recipientId) return false;
|
||||
|
||||
const myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const models = Self.app.models;
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
const sender = await models.Account.findById(userId);
|
||||
const recipient = await models.Account.findById(recipientId, null, myOptions);
|
||||
const sender = await models.Account.findById(userId, {fields: ['id']});
|
||||
const recipient = await models.Account.findById(recipientId, null);
|
||||
|
||||
// Prevent sending messages to yourself
|
||||
if (recipientId == userId) return false;
|
||||
|
@ -43,16 +38,26 @@ module.exports = Self => {
|
|||
if (!recipient)
|
||||
throw new Error(`Could not send message "${message}" to worker id ${recipientId} from user ${userId}`);
|
||||
|
||||
await models.Chat.create({
|
||||
if (process.env.NODE_ENV == 'test')
|
||||
message = `[Test:Environment to user ${userId}] ` + message;
|
||||
|
||||
const chat = await models.Chat.create({
|
||||
senderFk: sender.id,
|
||||
recipient: `@${recipient.name}`,
|
||||
dated: new Date(),
|
||||
dated: Date.vnNew(),
|
||||
checkUserStatus: 1,
|
||||
message: message,
|
||||
status: 0,
|
||||
status: 'sending',
|
||||
attempts: 0
|
||||
});
|
||||
|
||||
try {
|
||||
await Self.sendCheckingUserStatus(chat);
|
||||
await Self.updateChat(chat, 'sent');
|
||||
} catch (error) {
|
||||
await Self.updateChat(chat, 'error', error);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -3,7 +3,6 @@ module.exports = Self => {
|
|||
Self.remoteMethodCtx('sendQueued', {
|
||||
description: 'Send a RocketChat message',
|
||||
accessType: 'WRITE',
|
||||
accepts: [],
|
||||
returns: {
|
||||
type: 'object',
|
||||
root: true
|
||||
|
@ -16,14 +15,17 @@ module.exports = Self => {
|
|||
|
||||
Self.sendQueued = async() => {
|
||||
const models = Self.app.models;
|
||||
const maxAttempts = 3;
|
||||
const sentStatus = 1;
|
||||
const errorStatus = 2;
|
||||
|
||||
const chats = await models.Chat.find({
|
||||
where: {
|
||||
status: {neq: sentStatus},
|
||||
attempts: {lt: maxAttempts}
|
||||
status: {
|
||||
nin: [
|
||||
'sent',
|
||||
'sending'
|
||||
]
|
||||
|
||||
},
|
||||
attempts: {lt: 3}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -31,16 +33,16 @@ module.exports = Self => {
|
|||
if (chat.checkUserStatus) {
|
||||
try {
|
||||
await Self.sendCheckingUserStatus(chat);
|
||||
await updateChat(chat, sentStatus);
|
||||
await Self.updateChat(chat, 'sent');
|
||||
} catch (error) {
|
||||
await updateChat(chat, errorStatus);
|
||||
await Self.updateChat(chat, 'error', error);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await Self.sendMessage(chat.senderFk, chat.recipient, chat.message);
|
||||
await updateChat(chat, sentStatus);
|
||||
await Self.updateChat(chat, 'sent');
|
||||
} catch (error) {
|
||||
await updateChat(chat, errorStatus);
|
||||
await Self.updateChat(chat, 'error', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +77,7 @@ module.exports = Self => {
|
|||
const channelName = department && department.chatName;
|
||||
|
||||
if (channelName)
|
||||
return Self.sendMessage(chat.senderFk, `#${channelName}`, `@${recipient.name} ➔ ${message}`);
|
||||
return Self.sendMessage(chat.senderFk, `#${channelName}`, `@${recipient.name} ➔ ${chat.message}`);
|
||||
else
|
||||
return Self.sendMessage(chat.senderFk, `@${recipient.name}`, chat.message);
|
||||
} else
|
||||
|
@ -127,14 +129,18 @@ module.exports = Self => {
|
|||
*
|
||||
* @param {object} chat - The chat
|
||||
* @param {string} status - The new status
|
||||
* @param {string} error - The error
|
||||
* @param {object} options - Query options
|
||||
* @return {Promise} - The request promise
|
||||
*/
|
||||
async function updateChat(chat, status) {
|
||||
*/
|
||||
|
||||
Self.updateChat = async(chat, status, error) => {
|
||||
return chat.updateAttributes({
|
||||
status: status,
|
||||
attempts: ++chat.attempts
|
||||
attempts: ++chat.attempts,
|
||||
error: error
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the current user status on Rocketchat
|
||||
|
|
|
@ -27,7 +27,7 @@ describe('Chat notifyIssue()', () => {
|
|||
subject: 'Issue title'}
|
||||
]);
|
||||
// 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);
|
||||
let orgChatName = department.chatName;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
|
||||
describe('Chat sendCheckingPresence()', () => {
|
||||
const today = new Date();
|
||||
const today = Date.vnNew();
|
||||
today.setHours(6, 0);
|
||||
const chatModel = models.Chat;
|
||||
|
||||
|
@ -10,7 +10,7 @@ describe('Chat sendCheckingPresence()', () => {
|
|||
|
||||
const chat = {
|
||||
checkUserStatus: 1,
|
||||
status: 0,
|
||||
status: 'pending',
|
||||
attempts: 0
|
||||
};
|
||||
|
||||
|
@ -27,7 +27,7 @@ describe('Chat sendCheckingPresence()', () => {
|
|||
|
||||
const chat = {
|
||||
checkUserStatus: 0,
|
||||
status: 0,
|
||||
status: 'pending',
|
||||
attempts: 0
|
||||
};
|
||||
|
||||
|
|
|
@ -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');
|
||||
};
|
|
@ -24,13 +24,32 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.setSaleQuantity = async(saleId, quantity) => {
|
||||
Self.setSaleQuantity = async(saleId, quantity, options) => {
|
||||
const models = Self.app.models;
|
||||
const myOptions = {};
|
||||
let tx;
|
||||
|
||||
const sale = await models.Sale.findById(saleId);
|
||||
return await sale.updateAttributes({
|
||||
originalQuantity: sale.quantity,
|
||||
quantity: quantity
|
||||
});
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
if (!myOptions.transaction) {
|
||||
tx = await Self.beginTransaction({});
|
||||
myOptions.transaction = tx;
|
||||
}
|
||||
|
||||
try {
|
||||
const sale = await models.Sale.findById(saleId, null, myOptions);
|
||||
const saleUpdated = await sale.updateAttributes({
|
||||
originalQuantity: sale.quantity,
|
||||
quantity: quantity
|
||||
}, myOptions);
|
||||
|
||||
if (tx) await tx.commit();
|
||||
|
||||
return saleUpdated;
|
||||
} catch (e) {
|
||||
if (tx) await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -2,10 +2,10 @@ const models = require('vn-loopback/server/server').models;
|
|||
|
||||
describe('ticket getCollection()', () => {
|
||||
it('should return a list of collections', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 1106}}};
|
||||
let ctx = {req: {accessToken: {userId: 1107}}};
|
||||
let response = await models.Collection.getCollection(ctx);
|
||||
|
||||
expect(response.length).toBeGreaterThan(0);
|
||||
expect(response[0].collectionFk).toEqual(1);
|
||||
expect(response[0].collectionFk).toEqual(3);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,16 +1,42 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
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() => {
|
||||
const saleId = 30;
|
||||
const newQuantity = 10;
|
||||
const tx = await models.Ticket.beginTransaction({});
|
||||
|
||||
const originalSale = await models.Sale.findById(saleId);
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
await models.Collection.setSaleQuantity(saleId, newQuantity);
|
||||
const updateSale = await models.Sale.findById(saleId);
|
||||
const saleId = 30;
|
||||
const newQuantity = 10;
|
||||
|
||||
expect(updateSale.originalQuantity).toEqual(originalSale.quantity);
|
||||
expect(updateSale.quantity).toEqual(newQuantity);
|
||||
const originalSale = await models.Sale.findById(saleId, null, options);
|
||||
|
||||
await models.Collection.setSaleQuantity(saleId, newQuantity, options);
|
||||
const updateSale = await models.Sale.findById(saleId, null, options);
|
||||
|
||||
expect(updateSale.originalQuantity).toEqual(originalSale.quantity);
|
||||
expect(updateSale.quantity).toEqual(newQuantity);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
|
||||
|
@ -15,43 +16,53 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.deleteTrashFiles = async(options) => {
|
||||
const tx = await Self.beginTransaction({});
|
||||
Self.deleteTrashFiles = async options => {
|
||||
const myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
if (!myOptions.transaction)
|
||||
myOptions.transaction = tx;
|
||||
if (process.env.NODE_ENV == 'test')
|
||||
throw new UserError(`Action not allowed on the test environment`);
|
||||
|
||||
try {
|
||||
const models = Self.app.models;
|
||||
const DmsContainer = models.DmsContainer;
|
||||
const models = Self.app.models;
|
||||
const DmsContainer = models.DmsContainer;
|
||||
|
||||
const trashDmsType = await models.DmsType.findOne({
|
||||
where: {code: 'trash'}
|
||||
}, myOptions);
|
||||
const trashDmsType = await models.DmsType.findOne({
|
||||
where: {code: 'trash'}
|
||||
}, myOptions);
|
||||
|
||||
const dmsToDelete = await models.Dms.find({
|
||||
where: {
|
||||
dmsTypeFk: trashDmsType.id
|
||||
}
|
||||
}, myOptions);
|
||||
const date = Date.vnNew();
|
||||
date.setMonth(date.getMonth() - 4);
|
||||
|
||||
for (let dms of dmsToDelete) {
|
||||
const pathHash = DmsContainer.getHash(dms.id);
|
||||
const dmsContainer = await DmsContainer.container(pathHash);
|
||||
const dmsToDelete = await models.Dms.find({
|
||||
where: {
|
||||
and: [
|
||||
{dmsTypeFk: trashDmsType.id},
|
||||
{created: {lt: date}}
|
||||
]
|
||||
}
|
||||
}, myOptions);
|
||||
|
||||
for (let dms of dmsToDelete) {
|
||||
const pathHash = DmsContainer.getHash(dms.id);
|
||||
const dmsContainer = await DmsContainer.container(pathHash);
|
||||
try {
|
||||
const dstFile = path.join(dmsContainer.client.root, pathHash, dms.file);
|
||||
await fs.unlink(dstFile);
|
||||
await dms.destroy(myOptions);
|
||||
} catch (err) {
|
||||
if (err.code != 'ENOENT' && dms.file)
|
||||
throw err;
|
||||
}
|
||||
if (tx) await tx.commit();
|
||||
|
||||
} catch (e) {
|
||||
if (tx) await tx.rollback();
|
||||
|
||||
throw e;
|
||||
await dms.destroy(myOptions);
|
||||
|
||||
const dstFolder = path.join(dmsContainer.client.root, pathHash);
|
||||
try {
|
||||
await fs.rmdir(dstFolder);
|
||||
} catch (err) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const got = require('got');
|
||||
const axios = require('axios');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('checkFile', {
|
||||
|
@ -8,7 +8,7 @@ module.exports = Self => {
|
|||
{
|
||||
arg: 'id',
|
||||
type: 'number',
|
||||
description: 'The id',
|
||||
description: 'The id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
{
|
||||
|
@ -18,14 +18,14 @@ module.exports = Self => {
|
|||
description: 'The fileCabinet name'
|
||||
},
|
||||
{
|
||||
arg: 'dialog',
|
||||
type: 'string',
|
||||
arg: 'signed',
|
||||
type: 'boolean',
|
||||
required: true,
|
||||
description: 'The dialog name'
|
||||
description: 'If pdf is necessary to be signed'
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
type: 'boolean',
|
||||
type: 'object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
|
@ -34,58 +34,51 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.checkFile = async function(ctx, id, fileCabinet, dialog) {
|
||||
const myUserId = ctx.req.accessToken.userId;
|
||||
if (!myUserId)
|
||||
return false;
|
||||
|
||||
Self.checkFile = async function(ctx, id, fileCabinet, signed) {
|
||||
const models = Self.app.models;
|
||||
const docuwareConfig = await models.DocuwareConfig.findOne();
|
||||
const action = 'find';
|
||||
|
||||
const docuwareInfo = await models.Docuware.findOne({
|
||||
where: {
|
||||
code: fileCabinet,
|
||||
dialogName: dialog
|
||||
action: action
|
||||
}
|
||||
});
|
||||
|
||||
const docuwareUrl = docuwareConfig.url;
|
||||
const cookie = docuwareConfig.token;
|
||||
const fileCabinetName = docuwareInfo.fileCabinetName;
|
||||
const find = docuwareInfo.find;
|
||||
const options = {
|
||||
'headers': {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'Cookie': cookie
|
||||
}
|
||||
};
|
||||
const searchFilter = {
|
||||
condition: [
|
||||
{
|
||||
DBName: find,
|
||||
DBName: docuwareInfo.findById,
|
||||
|
||||
Value: [id]
|
||||
}
|
||||
],
|
||||
sortOrder: [
|
||||
{
|
||||
Field: 'FILENAME',
|
||||
Direction: 'Desc'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
try {
|
||||
// get fileCabinetId
|
||||
const fileCabinetResponse = await got.get(`${docuwareUrl}/FileCabinets`, options);
|
||||
const fileCabinetJson = JSON.parse(fileCabinetResponse.body).FileCabinet;
|
||||
const fileCabinetId = fileCabinetJson.find(dialogs => dialogs.Name === fileCabinetName).Id;
|
||||
const options = await Self.getOptions();
|
||||
|
||||
// get dialog
|
||||
const dialogResponse = await got.get(`${docuwareUrl}/FileCabinets/${fileCabinetId}/dialogs`, options);
|
||||
const dialogJson = JSON.parse(dialogResponse.body).Dialog;
|
||||
const dialogId = dialogJson.find(dialogs => dialogs.DisplayName === 'find').Id;
|
||||
const fileCabinetId = await Self.getFileCabinet(fileCabinet);
|
||||
const dialogId = await Self.getDialog(fileCabinet, action, fileCabinetId);
|
||||
|
||||
// get docuwareID
|
||||
Object.assign(options, {'body': JSON.stringify(searchFilter)});
|
||||
const response = await got.post(
|
||||
`${docuwareUrl}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`, options);
|
||||
JSON.parse(response.body).Items[0].Id;
|
||||
const response = await axios.post(
|
||||
`${options.url}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`,
|
||||
searchFilter,
|
||||
options.headers
|
||||
);
|
||||
const [documents] = response.data.Items;
|
||||
if (!documents) return false;
|
||||
|
||||
return true;
|
||||
const state = documents.Fields.find(field => field.FieldName == 'ESTADO');
|
||||
if (signed && state.Item != 'Firmado') return false;
|
||||
|
||||
return {id: documents.Id};
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
const axios = require('axios');
|
||||
|
||||
module.exports = Self => {
|
||||
/**
|
||||
* Returns the dialog id
|
||||
*
|
||||
* @param {string} code - The fileCabinet name
|
||||
* @param {string} action - The fileCabinet name
|
||||
* @param {string} fileCabinetId - Optional The fileCabinet name
|
||||
* @return {number} - The fileCabinet id
|
||||
*/
|
||||
Self.getDialog = async(code, action, fileCabinetId) => {
|
||||
const docuwareInfo = await Self.app.models.Docuware.findOne({
|
||||
where: {
|
||||
code: code,
|
||||
action: action
|
||||
}
|
||||
});
|
||||
if (!fileCabinetId) fileCabinetId = await Self.getFileCabinet(code);
|
||||
|
||||
const options = await Self.getOptions();
|
||||
|
||||
if (!process.env.NODE_ENV)
|
||||
return Math.round();
|
||||
|
||||
const response = await axios.get(`${options.url}/FileCabinets/${fileCabinetId}/dialogs`, options.headers);
|
||||
const dialogs = response.data.Dialog;
|
||||
const dialogId = dialogs.find(dialogs => dialogs.DisplayName === docuwareInfo.dialogName).Id;
|
||||
|
||||
return dialogId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the fileCabinetId
|
||||
*
|
||||
* @param {string} code - The fileCabinet code
|
||||
* @return {number} - The fileCabinet id
|
||||
*/
|
||||
Self.getFileCabinet = async code => {
|
||||
const options = await Self.getOptions();
|
||||
const docuwareInfo = await Self.app.models.Docuware.findOne({
|
||||
where: {
|
||||
code: code
|
||||
}
|
||||
});
|
||||
|
||||
if (!process.env.NODE_ENV)
|
||||
return Math.round();
|
||||
|
||||
const fileCabinetResponse = await axios.get(`${options.url}/FileCabinets`, options.headers);
|
||||
const fileCabinets = fileCabinetResponse.data.FileCabinet;
|
||||
const fileCabinetId = fileCabinets.find(fileCabinet => fileCabinet.Name === docuwareInfo.fileCabinetName).Id;
|
||||
|
||||
return fileCabinetId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns basic headers
|
||||
*
|
||||
* @param {string} cookie - The docuware cookie
|
||||
* @return {object} - The headers
|
||||
*/
|
||||
Self.getOptions = async() => {
|
||||
const docuwareConfig = await Self.app.models.DocuwareConfig.findOne();
|
||||
const headers = {
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'Cookie': docuwareConfig.cookie
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
url: docuwareConfig.url,
|
||||
headers
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,72 @@
|
|||
const {Email} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('deliveryNoteEmail', {
|
||||
description: 'Sends the delivery note email with an docuware attached PDF',
|
||||
accessType: 'WRITE',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'string',
|
||||
required: true,
|
||||
description: 'The ticket id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
{
|
||||
arg: 'recipient',
|
||||
type: 'string',
|
||||
description: 'The recipient email',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
arg: 'recipientId',
|
||||
type: 'number',
|
||||
description: 'The client id',
|
||||
required: false
|
||||
}
|
||||
],
|
||||
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/delivery-note-email',
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.deliveryNoteEmail = async(ctx, id) => {
|
||||
const args = Object.assign({}, ctx.args);
|
||||
const params = {
|
||||
recipient: args.recipient,
|
||||
lang: ctx.req.getLocale()
|
||||
};
|
||||
|
||||
delete args.ctx;
|
||||
for (const param in args)
|
||||
params[param] = args[param];
|
||||
|
||||
const email = new Email('delivery-note', params);
|
||||
|
||||
const docuwareFile = await Self.app.models.Docuware.download(ctx, id, 'deliveryNote');
|
||||
|
||||
return email.send({
|
||||
overrideAttachments: true,
|
||||
attachments: [{
|
||||
filename: `${id}.pdf`,
|
||||
content: docuwareFile[0]
|
||||
}]
|
||||
});
|
||||
};
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint max-len: ["error", { "code": 180 }]*/
|
||||
const got = require('got');
|
||||
const axios = require('axios');
|
||||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
|
@ -10,19 +10,13 @@ module.exports = Self => {
|
|||
{
|
||||
arg: 'id',
|
||||
type: 'number',
|
||||
description: 'The id',
|
||||
description: 'The ticket id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
{
|
||||
arg: 'fileCabinet',
|
||||
type: 'string',
|
||||
description: 'The id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
{
|
||||
arg: 'dialog',
|
||||
type: 'string',
|
||||
description: 'The id',
|
||||
description: 'The file cabinet',
|
||||
http: {source: 'path'}
|
||||
}
|
||||
],
|
||||
|
@ -42,79 +36,26 @@ module.exports = Self => {
|
|||
}
|
||||
],
|
||||
http: {
|
||||
path: `/:id/download/:fileCabinet/:dialog`,
|
||||
path: `/:id/download/:fileCabinet`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.download = async function(ctx, id, fileCabinet, dialog) {
|
||||
const myUserId = ctx.req.accessToken.userId;
|
||||
if (!myUserId)
|
||||
throw new UserError(`You don't have enough privileges`);
|
||||
|
||||
Self.download = async function(ctx, id, fileCabinet) {
|
||||
const models = Self.app.models;
|
||||
const docuwareConfig = await models.DocuwareConfig.findOne();
|
||||
const docuwareInfo = await models.Docuware.findOne({
|
||||
where: {
|
||||
code: fileCabinet,
|
||||
dialogName: dialog
|
||||
}
|
||||
});
|
||||
const docuwareFile = await models.Docuware.checkFile(ctx, id, fileCabinet, true);
|
||||
if (!docuwareFile) throw new UserError('The DOCUWARE PDF document does not exists');
|
||||
|
||||
const docuwareUrl = docuwareConfig.url;
|
||||
const cookie = docuwareConfig.token;
|
||||
const fileCabinetName = docuwareInfo.fileCabinetName;
|
||||
const find = docuwareInfo.find;
|
||||
const options = {
|
||||
'headers': {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
'Cookie': cookie
|
||||
}
|
||||
};
|
||||
const searchFilter = {
|
||||
condition: [
|
||||
{
|
||||
DBName: find,
|
||||
Value: [id]
|
||||
}
|
||||
]
|
||||
};
|
||||
const fileCabinetId = await Self.getFileCabinet(fileCabinet);
|
||||
const options = await Self.getOptions();
|
||||
options.headers.responseType = 'stream';
|
||||
|
||||
try {
|
||||
// get fileCabinetId
|
||||
const fileCabinetResponse = await got.get(`${docuwareUrl}/FileCabinets`, options);
|
||||
const fileCabinetJson = JSON.parse(fileCabinetResponse.body).FileCabinet;
|
||||
const fileCabinetId = fileCabinetJson.find(dialogs => dialogs.Name === fileCabinetName).Id;
|
||||
const fileName = `filename="${id}.pdf"`;
|
||||
const contentType = 'application/pdf';
|
||||
const downloadUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents/${docuwareFile.id}/FileDownload?targetFileType=Auto&keepAnnotations=false`;
|
||||
|
||||
// get dialog
|
||||
const dialogResponse = await got.get(`${docuwareUrl}/FileCabinets/${fileCabinetId}/dialogs`, options);
|
||||
const dialogJson = JSON.parse(dialogResponse.body).Dialog;
|
||||
const dialogId = dialogJson.find(dialogs => dialogs.DisplayName === 'find').Id;
|
||||
const stream = await axios.get(downloadUri, options.headers);
|
||||
|
||||
// get docuwareID
|
||||
Object.assign(options, {'body': JSON.stringify(searchFilter)});
|
||||
const response = await got.post(`${docuwareUrl}/FileCabinets/${fileCabinetId}/Query/DialogExpression?dialogId=${dialogId}`, options);
|
||||
const docuwareId = JSON.parse(response.body).Items[0].Id;
|
||||
|
||||
// download & save file
|
||||
const fileName = `filename="${id}.pdf"`;
|
||||
const contentType = 'application/pdf';
|
||||
const downloadUri = `${docuwareUrl}/FileCabinets/${fileCabinetId}/Documents/${docuwareId}/FileDownload?targetFileType=Auto&keepAnnotations=false`;
|
||||
const downloadOptions = {
|
||||
'headers': {
|
||||
'Cookie': cookie
|
||||
}
|
||||
};
|
||||
|
||||
const stream = got.stream(downloadUri, downloadOptions);
|
||||
|
||||
return [stream, contentType, fileName];
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT')
|
||||
throw new UserError('The DOCUWARE PDF document does not exists');
|
||||
|
||||
throw error;
|
||||
}
|
||||
return [stream.data, contentType, fileName];
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const got = require('got');
|
||||
const axios = require('axios');
|
||||
|
||||
describe('docuware download()', () => {
|
||||
const ticketId = 1;
|
||||
|
@ -12,53 +12,71 @@ describe('docuware download()', () => {
|
|||
}
|
||||
};
|
||||
|
||||
const fileCabinetName = 'deliveryClient';
|
||||
const dialogDisplayName = 'find';
|
||||
const dialogName = 'findTicket';
|
||||
const docuwareModel = models.Docuware;
|
||||
const fileCabinetName = 'deliveryNote';
|
||||
|
||||
const gotGetResponse = {
|
||||
body: JSON.stringify(
|
||||
{
|
||||
FileCabinet: [
|
||||
{Id: 12, Name: fileCabinetName}
|
||||
],
|
||||
Dialog: [
|
||||
{Id: 34, DisplayName: dialogDisplayName}
|
||||
]
|
||||
})
|
||||
};
|
||||
|
||||
it('should return exist file in docuware', async() => {
|
||||
const gotPostResponse = {
|
||||
body: JSON.stringify(
|
||||
{
|
||||
Items: [
|
||||
{Id: 56}
|
||||
],
|
||||
})
|
||||
};
|
||||
|
||||
spyOn(got, 'get').and.returnValue(new Promise(resolve => resolve(gotGetResponse)));
|
||||
spyOn(got, 'post').and.returnValue(new Promise(resolve => resolve(gotPostResponse)));
|
||||
|
||||
const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, dialogName);
|
||||
|
||||
expect(result).toEqual(true);
|
||||
beforeAll(() => {
|
||||
spyOn(docuwareModel, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
|
||||
spyOn(docuwareModel, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
|
||||
});
|
||||
|
||||
it('should return not exist file in docuware', async() => {
|
||||
const gotPostResponse = {
|
||||
body: JSON.stringify(
|
||||
{
|
||||
Items: [],
|
||||
})
|
||||
it('should return false if there are no documents', async() => {
|
||||
const response = {
|
||||
data: {
|
||||
Items: []
|
||||
}
|
||||
};
|
||||
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response)));
|
||||
|
||||
spyOn(got, 'get').and.returnValue(new Promise(resolve => resolve(gotGetResponse)));
|
||||
spyOn(got, 'post').and.returnValue(new Promise(resolve => resolve(gotPostResponse)));
|
||||
|
||||
const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, dialogName);
|
||||
const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return false if the document is unsigned', async() => {
|
||||
const response = {
|
||||
data: {
|
||||
Items: [
|
||||
{
|
||||
Id: 1,
|
||||
Fields: [
|
||||
{
|
||||
FieldName: 'ESTADO',
|
||||
Item: 'Unsigned'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response)));
|
||||
|
||||
const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true);
|
||||
|
||||
expect(result).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return the document data', async() => {
|
||||
const docuwareId = 1;
|
||||
const response = {
|
||||
data: {
|
||||
Items: [
|
||||
{
|
||||
Id: docuwareId,
|
||||
Fields: [
|
||||
{
|
||||
FieldName: 'ESTADO',
|
||||
Item: 'Firmado'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(response)));
|
||||
|
||||
const result = await models.Docuware.checkFile(ctx, ticketId, fileCabinetName, true);
|
||||
|
||||
expect(result.id).toEqual(docuwareId);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const got = require('got');
|
||||
const axios = require('axios');
|
||||
const stream = require('stream');
|
||||
|
||||
describe('docuware download()', () => {
|
||||
|
@ -13,36 +13,33 @@ describe('docuware download()', () => {
|
|||
}
|
||||
};
|
||||
|
||||
it('should return the downloaded file name', async() => {
|
||||
const fileCabinetName = 'deliveryClient';
|
||||
const dialogDisplayName = 'find';
|
||||
const dialogName = 'findTicket';
|
||||
const gotGetResponse = {
|
||||
body: JSON.stringify(
|
||||
{
|
||||
FileCabinet: [
|
||||
{Id: 12, Name: fileCabinetName}
|
||||
],
|
||||
Dialog: [
|
||||
{Id: 34, DisplayName: dialogDisplayName}
|
||||
]
|
||||
})
|
||||
};
|
||||
const docuwareModel = models.Docuware;
|
||||
const fileCabinetName = 'deliveryNote';
|
||||
|
||||
const gotPostResponse = {
|
||||
body: JSON.stringify(
|
||||
{
|
||||
Items: [
|
||||
{Id: 56}
|
||||
],
|
||||
})
|
||||
};
|
||||
beforeAll(() => {
|
||||
spyOn(docuwareModel, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
|
||||
spyOn(docuwareModel, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
|
||||
});
|
||||
|
||||
spyOn(got, 'get').and.returnValue(new Promise(resolve => resolve(gotGetResponse)));
|
||||
spyOn(got, 'post').and.returnValue(new Promise(resolve => resolve(gotPostResponse)));
|
||||
spyOn(got, 'stream').and.returnValue(new stream.PassThrough({objectMode: true}));
|
||||
it('should return error if file not exist', async() => {
|
||||
spyOn(docuwareModel, 'checkFile').and.returnValue(false);
|
||||
spyOn(axios, 'get').and.returnValue(new stream.PassThrough({objectMode: true}));
|
||||
|
||||
const result = await models.Docuware.download(ctx, ticketId, fileCabinetName, dialogName);
|
||||
let error;
|
||||
try {
|
||||
await models.Docuware.download(ctx, ticketId, fileCabinetName);
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
}
|
||||
|
||||
expect(error).toEqual('The DOCUWARE PDF document does not exists');
|
||||
});
|
||||
|
||||
it('should return the downloaded file if exist file ', async() => {
|
||||
spyOn(docuwareModel, 'checkFile').and.returnValue({});
|
||||
spyOn(axios, 'get').and.returnValue(new stream.PassThrough({objectMode: true}));
|
||||
|
||||
const result = await models.Docuware.download(ctx, ticketId, fileCabinetName);
|
||||
|
||||
expect(result[1]).toEqual('application/pdf');
|
||||
expect(result[2]).toEqual(`filename="${ticketId}.pdf"`);
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
|
||||
describe('docuware upload()', () => {
|
||||
const userId = 9;
|
||||
const ticketId = 10;
|
||||
const ctx = {
|
||||
req: {
|
||||
getLocale: () => {
|
||||
return 'en';
|
||||
},
|
||||
accessToken: {userId: userId},
|
||||
headers: {origin: 'http://localhost:5000'},
|
||||
}
|
||||
};
|
||||
|
||||
const docuwareModel = models.Docuware;
|
||||
const ticketModel = models.Ticket;
|
||||
const fileCabinetName = 'deliveryNote';
|
||||
|
||||
beforeAll(() => {
|
||||
spyOn(docuwareModel, 'getFileCabinet').and.returnValue(new Promise(resolve => resolve(Math.random())));
|
||||
spyOn(docuwareModel, 'getDialog').and.returnValue(new Promise(resolve => resolve(Math.random())));
|
||||
});
|
||||
|
||||
it('should try upload file', async() => {
|
||||
spyOn(ticketModel, 'deliveryNotePdf').and.returnValue(new Promise(resolve => resolve({})));
|
||||
|
||||
let error;
|
||||
try {
|
||||
await models.Docuware.upload(ctx, ticketId, fileCabinetName);
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
}
|
||||
|
||||
expect(error).toEqual('Action not allowed on the test environment');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,141 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
const axios = require('axios');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('upload', {
|
||||
description: 'Upload an docuware PDF',
|
||||
accessType: 'WRITE',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'number',
|
||||
description: 'The ticket id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
{
|
||||
arg: 'fileCabinet',
|
||||
type: 'string',
|
||||
description: 'The file cabinet'
|
||||
},
|
||||
{
|
||||
arg: 'dialog',
|
||||
type: 'string',
|
||||
description: 'The dialog'
|
||||
}
|
||||
],
|
||||
returns: [],
|
||||
http: {
|
||||
path: `/:id/upload`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.upload = async function(ctx, id, fileCabinet) {
|
||||
const models = Self.app.models;
|
||||
const action = 'store';
|
||||
|
||||
const options = await Self.getOptions();
|
||||
const fileCabinetId = await Self.getFileCabinet(fileCabinet);
|
||||
const dialogId = await Self.getDialog(fileCabinet, action, fileCabinetId);
|
||||
|
||||
// get delivery note
|
||||
const deliveryNote = await models.Ticket.deliveryNotePdf(ctx, {
|
||||
id,
|
||||
type: 'deliveryNote'
|
||||
});
|
||||
|
||||
// get ticket data
|
||||
const ticket = await models.Ticket.findById(id, {
|
||||
include: [{
|
||||
relation: 'client',
|
||||
scope: {
|
||||
fields: ['id', 'socialName', 'fi']
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
// upload file
|
||||
const templateJson = {
|
||||
'Fields': [
|
||||
{
|
||||
'FieldName': 'N__ALBAR_N',
|
||||
'ItemElementName': 'string',
|
||||
'Item': id,
|
||||
},
|
||||
{
|
||||
'FieldName': 'CIF_PROVEEDOR',
|
||||
'ItemElementName': 'string',
|
||||
'Item': ticket.client().fi,
|
||||
},
|
||||
{
|
||||
'FieldName': 'CODIGO_PROVEEDOR',
|
||||
'ItemElementName': 'string',
|
||||
'Item': ticket.client().id,
|
||||
},
|
||||
{
|
||||
'FieldName': 'NOMBRE_PROVEEDOR',
|
||||
'ItemElementName': 'string',
|
||||
'Item': ticket.client().socialName,
|
||||
},
|
||||
{
|
||||
'FieldName': 'FECHA_FACTURA',
|
||||
'ItemElementName': 'date',
|
||||
'Item': ticket.shipped,
|
||||
},
|
||||
{
|
||||
'FieldName': 'TOTAL_FACTURA',
|
||||
'ItemElementName': 'Decimal',
|
||||
'Item': ticket.totalWithVat,
|
||||
},
|
||||
{
|
||||
'FieldName': 'ESTADO',
|
||||
'ItemElementName': 'string',
|
||||
'Item': 'Pendiente procesar',
|
||||
},
|
||||
{
|
||||
'FieldName': 'FIRMA_',
|
||||
'ItemElementName': 'string',
|
||||
'Item': 'Si',
|
||||
},
|
||||
{
|
||||
'FieldName': 'FILTRO_TABLET',
|
||||
'ItemElementName': 'string',
|
||||
'Item': 'Tablet1',
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV != 'production')
|
||||
throw new UserError('Action not allowed on the test environment');
|
||||
|
||||
// delete old
|
||||
const docuwareFile = await models.Docuware.checkFile(ctx, id, fileCabinet, false);
|
||||
if (docuwareFile) {
|
||||
const deleteJson = {
|
||||
'Field': [{'FieldName': 'ESTADO', 'Item': 'Pendiente eliminar', 'ItemElementName': 'String'}]
|
||||
};
|
||||
const deleteUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents/${docuwareFile.id}/Fields`;
|
||||
await axios.put(deleteUri, deleteJson, options.headers);
|
||||
}
|
||||
|
||||
const uploadUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents?StoreDialogId=${dialogId}`;
|
||||
const FormData = require('form-data');
|
||||
const data = new FormData();
|
||||
|
||||
data.append('document', JSON.stringify(templateJson), 'schema.json');
|
||||
data.append('file[]', deliveryNote[0], 'file.pdf');
|
||||
const uploadOptions = {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
'X-File-ModifiedDate': Date.vnNew(),
|
||||
'Cookie': options.headers.headers.Cookie,
|
||||
...data.getHeaders()
|
||||
},
|
||||
};
|
||||
|
||||
return await axios.post(uploadUri, data, uploadOptions)
|
||||
.catch(() => {
|
||||
throw new UserError('Failed to upload file');
|
||||
});
|
||||
};
|
||||
};
|
|
@ -12,7 +12,7 @@ module.exports = Self => {
|
|||
},
|
||||
http: {
|
||||
path: `/updateData`,
|
||||
verb: 'GET'
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -20,83 +20,93 @@ module.exports = Self => {
|
|||
const models = Self.app.models;
|
||||
|
||||
// Get files checksum
|
||||
const files = await Self.rawSql('SELECT name, checksum, keyValue FROM edi.fileConfig');
|
||||
const tx = await Self.beginTransaction({});
|
||||
|
||||
const updatableFiles = [];
|
||||
for (const file of files) {
|
||||
const fileChecksum = await getChecksum(file);
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
const files = await Self.rawSql('SELECT name, checksum, keyValue FROM edi.fileConfig', null, options);
|
||||
|
||||
if (file.checksum != fileChecksum) {
|
||||
updatableFiles.push({
|
||||
name: file.name,
|
||||
checksum: fileChecksum
|
||||
});
|
||||
} else
|
||||
console.debug(`File already updated, skipping...`);
|
||||
}
|
||||
const updatableFiles = [];
|
||||
for (const file of files) {
|
||||
const fileChecksum = await getChecksum(file);
|
||||
|
||||
if (updatableFiles.length === 0)
|
||||
return false;
|
||||
|
||||
// Download files
|
||||
const container = await models.TempContainer.container('edi');
|
||||
const tempPath = path.join(container.client.root, container.name);
|
||||
|
||||
let remoteFile;
|
||||
let tempDir;
|
||||
let tempFile;
|
||||
|
||||
const fileNames = updatableFiles.map(file => file.name);
|
||||
|
||||
const tables = await Self.rawSql(`
|
||||
SELECT fileName, toTable, file
|
||||
FROM edi.tableConfig
|
||||
WHERE file IN (?)`, [fileNames]);
|
||||
|
||||
for (const table of tables) {
|
||||
const fileName = table.file;
|
||||
|
||||
console.debug(`Downloading file ${fileName}...`);
|
||||
|
||||
remoteFile = `codes/${fileName}.ZIP`;
|
||||
tempDir = `${tempPath}/${fileName}`;
|
||||
tempFile = `${tempPath}/${fileName}.zip`;
|
||||
|
||||
try {
|
||||
await fs.readFile(tempFile);
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
const downloadOutput = await downloadFile(remoteFile, tempFile);
|
||||
if (downloadOutput.error)
|
||||
continue;
|
||||
}
|
||||
if (file.checksum != fileChecksum) {
|
||||
updatableFiles.push({
|
||||
name: file.name,
|
||||
checksum: fileChecksum
|
||||
});
|
||||
} else
|
||||
console.debug(`File already updated, skipping...`);
|
||||
}
|
||||
|
||||
console.debug(`Extracting file ${fileName}...`);
|
||||
await extractFile(tempFile, tempDir);
|
||||
if (updatableFiles.length === 0)
|
||||
return false;
|
||||
|
||||
console.debug(`Updating table ${table.toTable}...`);
|
||||
await dumpData(tempDir, table);
|
||||
}
|
||||
// Download files
|
||||
const container = await models.TempContainer.container('edi');
|
||||
const tempPath = path.join(container.client.root, container.name);
|
||||
|
||||
// Update files checksum
|
||||
for (const file of updatableFiles) {
|
||||
await Self.rawSql(`
|
||||
UPDATE edi.fileConfig
|
||||
SET checksum = ?
|
||||
WHERE name = ?`,
|
||||
[file.checksum, file.name]);
|
||||
}
|
||||
let remoteFile;
|
||||
let tempDir;
|
||||
let tempFile;
|
||||
|
||||
// Clean files
|
||||
try {
|
||||
await fs.remove(tempPath);
|
||||
const fileNames = updatableFiles.map(file => file.name);
|
||||
|
||||
const tables = await Self.rawSql(`
|
||||
SELECT fileName, toTable, file
|
||||
FROM edi.tableConfig
|
||||
WHERE file IN (?)`, [fileNames], options);
|
||||
|
||||
for (const table of tables) {
|
||||
const fileName = table.file;
|
||||
|
||||
remoteFile = `codes/${fileName}.ZIP`;
|
||||
tempDir = `${tempPath}/${fileName}`;
|
||||
tempFile = `${tempPath}/${fileName}.zip`;
|
||||
|
||||
try {
|
||||
await fs.readFile(tempFile);
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
console.debug(`Downloading file ${fileName}...`);
|
||||
const downloadOutput = await downloadFile(remoteFile, tempFile);
|
||||
if (downloadOutput.error)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
await extractFile(fileName, tempFile, tempDir);
|
||||
|
||||
console.debug(`Updating table ${table.toTable}...`);
|
||||
await dumpData(tempDir, table, options);
|
||||
}
|
||||
|
||||
// Update files checksum
|
||||
for (const file of updatableFiles) {
|
||||
console.log(`Updating file ${file.name} checksum...`);
|
||||
await Self.rawSql(`
|
||||
UPDATE edi.fileConfig
|
||||
SET checksum = ?
|
||||
WHERE name = ?`,
|
||||
[file.checksum, file.name], options);
|
||||
}
|
||||
|
||||
await tx.commit();
|
||||
|
||||
// Clean files
|
||||
try {
|
||||
console.debug(`Cleaning files...`);
|
||||
await fs.remove(tempPath);
|
||||
} catch (error) {
|
||||
if (error.code !== 'ENOENT')
|
||||
throw e;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (error.code !== 'ENOENT')
|
||||
throw e;
|
||||
await tx.rollback();
|
||||
throw error;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
let ftpClient;
|
||||
|
@ -126,9 +136,9 @@ module.exports = Self => {
|
|||
|
||||
const response = await new Promise((resolve, reject) => {
|
||||
ftpClient.exec((err, response) => {
|
||||
if (response.error) {
|
||||
if (err || response.error) {
|
||||
console.debug(`Error downloading checksum file... ${response.error}`);
|
||||
reject(err);
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(response);
|
||||
|
@ -159,9 +169,9 @@ module.exports = Self => {
|
|||
|
||||
return new Promise((resolve, reject) => {
|
||||
ftpClient.exec((err, response) => {
|
||||
if (response.error) {
|
||||
if (err || response.error) {
|
||||
console.debug(`Error downloading file... ${response.error}`);
|
||||
reject(err);
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(response);
|
||||
|
@ -169,11 +179,12 @@ module.exports = Self => {
|
|||
});
|
||||
}
|
||||
|
||||
async function extractFile(tempFile, tempDir) {
|
||||
async function extractFile(fileName, tempFile, tempDir) {
|
||||
const JSZip = require('jszip');
|
||||
|
||||
try {
|
||||
await fs.mkdir(tempDir);
|
||||
console.debug(`Extracting file ${fileName}...`);
|
||||
} catch (error) {
|
||||
if (error.code !== 'EEXIST')
|
||||
throw e;
|
||||
|
@ -196,41 +207,32 @@ module.exports = Self => {
|
|||
}
|
||||
}
|
||||
|
||||
async function dumpData(tempDir, table) {
|
||||
async function dumpData(tempDir, table, options) {
|
||||
const toTable = table.toTable;
|
||||
const baseName = table.fileName;
|
||||
|
||||
const tx = await Self.beginTransaction({});
|
||||
console.log(`Emptying table ${toTable}...`);
|
||||
const tableName = `edi.${toTable}`;
|
||||
await Self.rawSql(`DELETE FROM ??`, [tableName]);
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
const dirFiles = await fs.readdir(tempDir);
|
||||
const files = dirFiles.filter(file => file.startsWith(baseName));
|
||||
|
||||
const tableName = `edi.${toTable}`;
|
||||
await Self.rawSql(`DELETE FROM ??`, [tableName], options);
|
||||
for (const file of files) {
|
||||
console.log(`Dumping data from file ${file}...`);
|
||||
|
||||
const dirFiles = await fs.readdir(tempDir);
|
||||
const files = dirFiles.filter(file => file.startsWith(baseName));
|
||||
const templatePath = path.join(__dirname, `./sql/${toTable}.sql`);
|
||||
const sqlTemplate = await fs.readFile(templatePath, 'utf8');
|
||||
const filePath = path.join(tempDir, file);
|
||||
|
||||
for (const file of files) {
|
||||
console.log(`Dumping data from file ${file}...`);
|
||||
|
||||
const templatePath = path.join(__dirname, `./sql/${toTable}.sql`);
|
||||
const sqlTemplate = await fs.readFile(templatePath, 'utf8');
|
||||
const filePath = path.join(tempDir, file);
|
||||
|
||||
await Self.rawSql(sqlTemplate, [filePath], options);
|
||||
await Self.rawSql(`
|
||||
await Self.rawSql(sqlTemplate, [filePath], options);
|
||||
await Self.rawSql(`
|
||||
UPDATE edi.tableConfig
|
||||
SET updated = ?
|
||||
WHERE fileName = ?
|
||||
`, [new Date(), baseName], options);
|
||||
}
|
||||
|
||||
tx.commit();
|
||||
} catch (error) {
|
||||
tx.rollback();
|
||||
throw error;
|
||||
`, [Date.vnNew(), baseName], options);
|
||||
}
|
||||
|
||||
console.log(`Updated table ${toTable}\n`);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -74,10 +74,13 @@ module.exports = Self => {
|
|||
const container = await models.ImageContainer.getContainer(collection);
|
||||
const rootPath = container.client.root;
|
||||
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 = {
|
||||
path: `${fileSrc}/${image.name}.png`,
|
||||
path: `${fileSrc}/${fileName}`,
|
||||
contentType: 'image/png',
|
||||
name: `${image.name}.png`
|
||||
name: image.name
|
||||
};
|
||||
|
||||
if (!fs.existsSync(file.path)) return [];
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const uuid = require('uuid');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('upload', {
|
||||
|
@ -18,12 +19,6 @@ module.exports = Self => {
|
|||
type: 'string',
|
||||
description: 'The collection name',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'fileName',
|
||||
type: 'string',
|
||||
description: 'The file name',
|
||||
required: true
|
||||
}],
|
||||
returns: {
|
||||
type: 'Object',
|
||||
|
@ -56,10 +51,12 @@ module.exports = Self => {
|
|||
const [uploadedFile] = Object.values(uploaded.files).map(file => {
|
||||
return file[0];
|
||||
});
|
||||
|
||||
const file = await TempContainer.getFile(tempContainer.name, uploadedFile.name);
|
||||
srcFile = path.join(file.client.root, file.container, file.name);
|
||||
|
||||
await models.Image.registerImage(args.collection, srcFile, args.fileName, args.id);
|
||||
const fileName = `${uuid.v4()}.png`;
|
||||
await models.Image.registerImage(args.collection, srcFile, fileName, args.id);
|
||||
} catch (e) {
|
||||
if (fs.existsSync(srcFile))
|
||||
await fs.unlink(srcFile);
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethod('clean', {
|
||||
description: 'clean notifications from queue',
|
||||
accessType: 'WRITE',
|
||||
returns: {
|
||||
type: 'object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/clean`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.clean = async options => {
|
||||
const models = Self.app.models;
|
||||
const status = ['sent', 'error'];
|
||||
|
||||
const myOptions = {};
|
||||
let tx;
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
if (!myOptions.transaction) {
|
||||
tx = await Self.beginTransaction({});
|
||||
myOptions.transaction = tx;
|
||||
}
|
||||
|
||||
try {
|
||||
const config = await models.NotificationConfig.findOne({}, myOptions);
|
||||
|
||||
if (!config.cleanDays) return;
|
||||
|
||||
const cleanDate = Date.vnNew();
|
||||
cleanDate.setDate(cleanDate.getDate() - config.cleanDays);
|
||||
|
||||
await models.NotificationQueue.destroyAll({
|
||||
where: {status: {inq: status}},
|
||||
created: {lt: cleanDate}
|
||||
}, myOptions);
|
||||
} catch (e) {
|
||||
if (tx) await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (tx) await tx.commit();
|
||||
};
|
||||
};
|
|
@ -0,0 +1,83 @@
|
|||
const {Email} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('send', {
|
||||
description: 'Send notifications from queue',
|
||||
accessType: 'WRITE',
|
||||
returns: {
|
||||
type: 'object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/send`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.send = async options => {
|
||||
const models = Self.app.models;
|
||||
const findStatus = 'pending';
|
||||
|
||||
const myOptions = {};
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
const notificationQueue = await models.NotificationQueue.find({
|
||||
where: {status: findStatus},
|
||||
include: [
|
||||
{
|
||||
relation: 'notification',
|
||||
scope: {
|
||||
include: {
|
||||
relation: 'subscription',
|
||||
scope: {
|
||||
include: {
|
||||
relation: 'user',
|
||||
scope: {
|
||||
fields: ['name', 'lang'],
|
||||
include: {
|
||||
relation: 'emailUser'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
]
|
||||
}, myOptions);
|
||||
|
||||
const statusSent = 'sent';
|
||||
const statusError = 'error';
|
||||
|
||||
for (const queue of notificationQueue) {
|
||||
const queueName = queue.notification().name;
|
||||
const queueParams = JSON.parse(queue.params);
|
||||
|
||||
for (const notificationUser of queue.notification().subscription()) {
|
||||
try {
|
||||
const sendParams = {
|
||||
recipient: notificationUser.user().emailUser().email,
|
||||
lang: notificationUser.user().lang
|
||||
};
|
||||
|
||||
if (notificationUser.userFk == queue.authorFk) {
|
||||
await queue.updateAttribute('status', statusSent);
|
||||
continue;
|
||||
}
|
||||
|
||||
const newParams = Object.assign({}, queueParams, sendParams);
|
||||
const email = new Email(queueName, newParams);
|
||||
|
||||
if (process.env.NODE_ENV != 'test')
|
||||
await email.send();
|
||||
|
||||
await queue.updateAttribute('status', statusSent);
|
||||
} catch (error) {
|
||||
await queue.updateAttribute('status', statusError);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
|
||||
describe('Notification Clean()', () => {
|
||||
it('should delete old rows with error', async() => {
|
||||
const userId = 9;
|
||||
const status = 'error';
|
||||
const tx = await models.NotificationQueue.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
const notification = await models.Notification.findOne({}, options);
|
||||
const notificationConfig = await models.NotificationConfig.findOne({});
|
||||
|
||||
const cleanDate = Date.vnNew();
|
||||
cleanDate.setDate(cleanDate.getDate() - (notificationConfig.cleanDays + 1));
|
||||
|
||||
let before;
|
||||
let after;
|
||||
|
||||
try {
|
||||
const notificationDelete = await models.NotificationQueue.create({
|
||||
notificationFk: notification.name,
|
||||
authorFk: userId,
|
||||
status: status,
|
||||
created: cleanDate
|
||||
}, options);
|
||||
|
||||
before = await models.NotificationQueue.findById(notificationDelete.id, null, options);
|
||||
|
||||
await models.Notification.clean(options);
|
||||
|
||||
after = await models.NotificationQueue.findById(notificationDelete.id, null, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
|
||||
expect(before.notificationFk).toEqual(notification.name);
|
||||
expect(after).toBe(null);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
|
||||
describe('Notification Send()', () => {
|
||||
it('should send notification', async() => {
|
||||
const statusPending = 'pending';
|
||||
const tx = await models.NotificationQueue.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
const filter = {
|
||||
where: {
|
||||
status: statusPending
|
||||
}
|
||||
};
|
||||
|
||||
let before;
|
||||
let after;
|
||||
|
||||
try {
|
||||
before = await models.NotificationQueue.find(filter, options);
|
||||
|
||||
await models.Notification.send(options);
|
||||
|
||||
after = await models.NotificationQueue.find(filter, options);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
|
||||
expect(before.length).toEqual(3);
|
||||
expect(after.length).toEqual(0);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,155 @@
|
|||
const jsdom = require('jsdom');
|
||||
const mysql = require('mysql');
|
||||
const FormData = require('form-data');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('closeTicket', {
|
||||
description: 'Close tickets without response from the user',
|
||||
accessType: 'READ',
|
||||
returns: {
|
||||
type: 'object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/closeTicket`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.closeTicket = async ctx => {
|
||||
const models = Self.app.models;
|
||||
const config = await models.OsTicketConfig.findOne();
|
||||
const ostUri = `${config.host}/login.php`;
|
||||
|
||||
if (!config.user || !config.password || !config.userDb || !config.passwordDb)
|
||||
return false;
|
||||
|
||||
const con = mysql.createConnection({
|
||||
host: config.hostDb,
|
||||
user: config.userDb,
|
||||
password: config.passwordDb,
|
||||
port: config.portDb
|
||||
});
|
||||
|
||||
const sql = `SELECT ot.ticket_id, ot.number
|
||||
FROM osticket.ost_ticket ot
|
||||
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 (
|
||||
SELECT sub2.thread_id, sub2.type, sub2.updated, sub2.created
|
||||
FROM (
|
||||
SELECT ote.thread_id, ote.created, ote.updated, ote.type
|
||||
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
|
||||
WHERE ot.isanswered
|
||||
AND ots.id IN (?)
|
||||
AND sub.type = 'R'
|
||||
AND IF(sub.updated > sub.created, sub.updated, sub.created) < DATE_SUB(CURDATE(), INTERVAL ? DAY);`;
|
||||
|
||||
const ticketsId = [];
|
||||
const statusIdToClose = config.oldStatus.split(',');
|
||||
|
||||
con.connect(err => {
|
||||
if (err) throw err;
|
||||
con.query(sql, [statusIdToClose, config.day],
|
||||
(err, results) => {
|
||||
if (err) throw err;
|
||||
for (const result of results)
|
||||
ticketsId.push(result.ticket_id);
|
||||
});
|
||||
});
|
||||
await getRequestToken();
|
||||
|
||||
async function getRequestToken() {
|
||||
const response = await fetch(ostUri);
|
||||
|
||||
const result = response.headers.get('set-cookie');
|
||||
const [firtHeader] = result.split(' ');
|
||||
const cookie = firtHeader.substring(0, firtHeader.length - 1);
|
||||
const body = await response.text();
|
||||
const dom = new jsdom.JSDOM(body);
|
||||
const token = dom.window.document.querySelector('[name="__CSRFToken__"]').value;
|
||||
|
||||
await login(token, cookie);
|
||||
}
|
||||
|
||||
async function login(token, cookie) {
|
||||
const data = {
|
||||
__CSRFToken__: token,
|
||||
do: 'scplogin',
|
||||
userid: config.user,
|
||||
passwd: config.password,
|
||||
ajax: 1
|
||||
};
|
||||
const params = {
|
||||
method: 'POST',
|
||||
body: new URLSearchParams(data),
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'Cookie': cookie
|
||||
}
|
||||
};
|
||||
await fetch(ostUri, params);
|
||||
|
||||
await close(token, cookie);
|
||||
}
|
||||
|
||||
async function close(token, cookie) {
|
||||
for (const ticketId of ticketsId) {
|
||||
try {
|
||||
const lock = await getLockCode(token, cookie, ticketId);
|
||||
if (!lock.code) {
|
||||
let error = `Can't get lock code`;
|
||||
if (lock.msg) error += `: ${lock.msg}`;
|
||||
throw new Error(error);
|
||||
}
|
||||
let form = new FormData();
|
||||
form.append('__CSRFToken__', token);
|
||||
form.append('id', ticketId);
|
||||
form.append('a', config.responseType);
|
||||
form.append('lockCode', lock.code);
|
||||
form.append('from_email_id', config.fromEmailId);
|
||||
form.append('reply-to', config.replyTo);
|
||||
form.append('cannedResp', 0);
|
||||
form.append('response', config.comment);
|
||||
form.append('signature', 'none');
|
||||
form.append('reply_status_id', config.newStatusId);
|
||||
|
||||
const ostUri = `${config.host}/tickets.php?id=${ticketId}`;
|
||||
const params = {
|
||||
method: 'POST',
|
||||
body: form,
|
||||
headers: {
|
||||
'Cookie': cookie
|
||||
}
|
||||
};
|
||||
await fetch(ostUri, params);
|
||||
} catch (e) {
|
||||
const err = new Error(`${ticketId} Ticket close failed: ${e.message}`);
|
||||
err.stack += e.stack;
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getLockCode(token, cookie, ticketId) {
|
||||
const ostUri = `${config.host}/ajax.php/lock/ticket/${ticketId}`;
|
||||
const params = {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-CSRFToken': token,
|
||||
'Cookie': cookie
|
||||
}
|
||||
};
|
||||
const response = await fetch(ostUri, params);
|
||||
const body = await response.text();
|
||||
const json = JSON.parse(body);
|
||||
|
||||
return json;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
const {Email} = require('vn-print');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('osTicketReportEmail', {
|
||||
description: 'Sends the buyer waste email',
|
||||
accessType: 'WRITE',
|
||||
accepts: [],
|
||||
returns: {
|
||||
type: ['object'],
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: '/osticket-report-email',
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.osTicketReportEmail = async ctx => {
|
||||
const models = Self.app.models;
|
||||
const printConfig = await models.PrintConfig.findOne();
|
||||
|
||||
const email = new Email('osticket-report', {
|
||||
recipient: printConfig.itRecipient,
|
||||
lang: ctx.req.getLocale()
|
||||
});
|
||||
|
||||
return email.send();
|
||||
};
|
||||
};
|
|
@ -19,11 +19,11 @@ describe('getStarredModules()', () => {
|
|||
});
|
||||
|
||||
it(`should return the starred modules for a given user`, async() => {
|
||||
const newStarred = await app.models.StarredModule.create({workerFk: 9, moduleFk: 'Clients', position: 1});
|
||||
const newStarred = await app.models.StarredModule.create({workerFk: 9, moduleFk: 'customer', position: 1});
|
||||
const starredModules = await app.models.StarredModule.getStarredModules(ctx);
|
||||
|
||||
expect(starredModules.length).toEqual(1);
|
||||
expect(starredModules[0].moduleFk).toEqual('Clients');
|
||||
expect(starredModules[0].moduleFk).toEqual('customer');
|
||||
|
||||
// restores
|
||||
await app.models.StarredModule.destroyById(newStarred.id);
|
||||
|
|
|
@ -26,29 +26,29 @@ describe('setPosition()', () => {
|
|||
const filter = {
|
||||
where: {
|
||||
workerFk: ctx.req.accessToken.userId,
|
||||
moduleFk: 'Orders'
|
||||
moduleFk: 'order'
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'order', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'customer', options);
|
||||
|
||||
let orders = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Clients';
|
||||
filter.where.moduleFk = 'customer';
|
||||
let clients = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
expect(orders.position).toEqual(1);
|
||||
expect(clients.position).toEqual(2);
|
||||
|
||||
await app.models.StarredModule.setPosition(ctx, 'Clients', 'left', options);
|
||||
await app.models.StarredModule.setPosition(ctx, 'customer', 'left', options);
|
||||
|
||||
filter.where.moduleFk = 'Clients';
|
||||
filter.where.moduleFk = 'customer';
|
||||
clients = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Orders';
|
||||
filter.where.moduleFk = 'order';
|
||||
orders = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
expect(clients.position).toEqual(1);
|
||||
|
@ -67,29 +67,29 @@ describe('setPosition()', () => {
|
|||
const filter = {
|
||||
where: {
|
||||
workerFk: ctx.req.accessToken.userId,
|
||||
moduleFk: 'Orders'
|
||||
moduleFk: 'order'
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'order', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'customer', options);
|
||||
|
||||
let orders = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Clients';
|
||||
filter.where.moduleFk = 'customer';
|
||||
let clients = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
expect(orders.position).toEqual(1);
|
||||
expect(clients.position).toEqual(2);
|
||||
|
||||
await app.models.StarredModule.setPosition(ctx, 'Orders', 'right', options);
|
||||
await app.models.StarredModule.setPosition(ctx, 'order', 'right', options);
|
||||
|
||||
filter.where.moduleFk = 'Orders';
|
||||
filter.where.moduleFk = 'order';
|
||||
orders = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Clients';
|
||||
filter.where.moduleFk = 'customer';
|
||||
clients = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
expect(orders.position).toEqual(2);
|
||||
|
@ -108,35 +108,35 @@ describe('setPosition()', () => {
|
|||
const filter = {
|
||||
where: {
|
||||
workerFk: ctx.req.accessToken.userId,
|
||||
moduleFk: 'Items'
|
||||
moduleFk: 'item'
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Items', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Claims', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Zones', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'customer', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'order', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'customer', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'order', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'item', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'claim', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'customer', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'order', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'zone', options);
|
||||
|
||||
const items = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Claims';
|
||||
filter.where.moduleFk = 'claim';
|
||||
const claims = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Clients';
|
||||
filter.where.moduleFk = 'customer';
|
||||
let clients = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Orders';
|
||||
filter.where.moduleFk = 'order';
|
||||
let orders = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Zones';
|
||||
filter.where.moduleFk = 'zone';
|
||||
const zones = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
expect(items.position).toEqual(1);
|
||||
|
@ -145,12 +145,12 @@ describe('setPosition()', () => {
|
|||
expect(orders.position).toEqual(4);
|
||||
expect(zones.position).toEqual(5);
|
||||
|
||||
await app.models.StarredModule.setPosition(ctx, 'Clients', 'right', options);
|
||||
await app.models.StarredModule.setPosition(ctx, 'customer', 'right', options);
|
||||
|
||||
filter.where.moduleFk = 'Orders';
|
||||
filter.where.moduleFk = 'order';
|
||||
orders = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Clients';
|
||||
filter.where.moduleFk = 'customer';
|
||||
clients = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
expect(orders.position).toEqual(3);
|
||||
|
@ -169,31 +169,31 @@ describe('setPosition()', () => {
|
|||
const filter = {
|
||||
where: {
|
||||
workerFk: ctx.req.accessToken.userId,
|
||||
moduleFk: 'Items'
|
||||
moduleFk: 'item'
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Items', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Clients', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Claims', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Orders', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Zones', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'item', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'customer', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'claim', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'order', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'zone', options);
|
||||
|
||||
const items = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Clients';
|
||||
filter.where.moduleFk = 'customer';
|
||||
let clients = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Claims';
|
||||
filter.where.moduleFk = 'claim';
|
||||
const claims = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Orders';
|
||||
filter.where.moduleFk = 'order';
|
||||
let orders = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Zones';
|
||||
filter.where.moduleFk = 'zone';
|
||||
const zones = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
expect(items.position).toEqual(1);
|
||||
|
@ -202,13 +202,13 @@ describe('setPosition()', () => {
|
|||
expect(orders.position).toEqual(4);
|
||||
expect(zones.position).toEqual(5);
|
||||
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Claims', options);
|
||||
await app.models.StarredModule.setPosition(ctx, 'Clients', 'right', options);
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'claim', options);
|
||||
await app.models.StarredModule.setPosition(ctx, 'customer', 'right', options);
|
||||
|
||||
filter.where.moduleFk = 'Clients';
|
||||
filter.where.moduleFk = 'customer';
|
||||
clients = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
filter.where.moduleFk = 'Orders';
|
||||
filter.where.moduleFk = 'order';
|
||||
orders = await app.models.StarredModule.findOne(filter, options);
|
||||
|
||||
expect(orders.position).toEqual(2);
|
||||
|
|
|
@ -21,15 +21,15 @@ describe('toggleStarredModule()', () => {
|
|||
});
|
||||
|
||||
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, 'Orders');
|
||||
const starredModule = await app.models.StarredModule.toggleStarredModule(ctx, 'order');
|
||||
let starredModules = await app.models.StarredModule.getStarredModules(ctx);
|
||||
|
||||
expect(starredModules.length).toEqual(1);
|
||||
expect(starredModule.moduleFk).toEqual('Orders');
|
||||
expect(starredModule.moduleFk).toEqual('order');
|
||||
expect(starredModule.workerFk).toEqual(activeCtx.accessToken.userId);
|
||||
expect(starredModule.position).toEqual(starredModules.length);
|
||||
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'Orders');
|
||||
await app.models.StarredModule.toggleStarredModule(ctx, 'order');
|
||||
starredModules = await app.models.StarredModule.getStarredModules(ctx);
|
||||
|
||||
expect(starredModules.length).toEqual(0);
|
||||
|
|
|
@ -77,6 +77,21 @@
|
|||
"Module": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Notification": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"NotificationAcl": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"NotificationConfig": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"NotificationQueue": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"NotificationSubscription": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Province": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
|
@ -101,6 +116,9 @@
|
|||
"Town": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Url": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"UserConfig": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
|
@ -116,8 +134,14 @@
|
|||
"OsTicket": {
|
||||
"dataSource": "osticket"
|
||||
},
|
||||
"OsTicketConfig": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Edi": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"PrintConfig": {
|
||||
"dataSource": "vn"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
/* 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);
|
||||
|
@ -6,7 +9,9 @@ module.exports = 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
|
||||
|
||||
|
@ -26,17 +31,62 @@ module.exports = Self => {
|
|||
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',
|
||||
type: 'object',
|
||||
http: {source: 'context'}
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
type: 'Object',
|
||||
type: 'object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
|
@ -57,7 +107,7 @@ module.exports = Self => {
|
|||
*
|
||||
* @param {Integer} userId The user id
|
||||
* @param {String} name The role name
|
||||
* @param {Object} options Options
|
||||
* @param {object} options Options
|
||||
* @return {Boolean} %true if user has the role, %false otherwise
|
||||
*/
|
||||
Self.hasRole = async function(userId, name, options) {
|
||||
|
@ -69,19 +119,19 @@ module.exports = Self => {
|
|||
* Get all user roles.
|
||||
*
|
||||
* @param {Integer} userId The user id
|
||||
* @param {Object} options Options
|
||||
* @return {Object} User role list
|
||||
* @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
|
||||
JOIN account.role r ON r.id = rr.inheritsFrom
|
||||
WHERE u.id = ?`, [userId], options);
|
||||
|
||||
let roles = [];
|
||||
for (role of result)
|
||||
for (const role of result)
|
||||
roles.push(role.name);
|
||||
|
||||
return roles;
|
||||
|
|
|
@ -40,6 +40,9 @@
|
|||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"emailVerified": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"created": {
|
||||
"type": "date"
|
||||
},
|
||||
|
@ -48,6 +51,9 @@
|
|||
},
|
||||
"image": {
|
||||
"type": "string"
|
||||
},
|
||||
"hasGrant": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
|
@ -85,20 +91,34 @@
|
|||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
},
|
||||
},
|
||||
{
|
||||
"property": "recoverPassword",
|
||||
"accessType": "EXECUTE",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
},
|
||||
{
|
||||
"property": "logout",
|
||||
"property": "logout",
|
||||
"accessType": "EXECUTE",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$authenticated",
|
||||
"permission": "ALLOW"
|
||||
},
|
||||
{
|
||||
"property": "validateToken",
|
||||
"property": "validateToken",
|
||||
"accessType": "EXECUTE",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$authenticated",
|
||||
"permission": "ALLOW"
|
||||
},
|
||||
{
|
||||
"property": "privileges",
|
||||
"accessType": "*",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$authenticated",
|
||||
"permission": "ALLOW"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -4,4 +4,23 @@ module.exports = Self => {
|
|||
require('../methods/chat/sendCheckingPresence')(Self);
|
||||
require('../methods/chat/notifyIssues')(Self);
|
||||
require('../methods/chat/sendQueued')(Self);
|
||||
|
||||
Self.observe('before save', async function(ctx) {
|
||||
if (!ctx.isNewInstance) return;
|
||||
|
||||
let {message} = ctx.instance;
|
||||
if (!message) return;
|
||||
|
||||
const parts = message.match(/(?<=\[)[a-zA-Z0-9_\-+!@#$%^&*()={};':"\\|,.<>/?\s]*(?=])/g);
|
||||
if (!parts) return;
|
||||
|
||||
const replacedParts = parts.map(part => {
|
||||
return part.replace(/[!$%^&*()={};':"\\,.<>/?]/g, '');
|
||||
});
|
||||
|
||||
for (const [index, part] of parts.entries())
|
||||
message = message.replace(part, replacedParts[index]);
|
||||
|
||||
ctx.instance.message = message;
|
||||
});
|
||||
};
|
||||
|
|
|
@ -32,6 +32,9 @@
|
|||
},
|
||||
"attempts": {
|
||||
"type": "number"
|
||||
},
|
||||
"error": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"acls": [{
|
||||
|
|
|
@ -3,4 +3,5 @@ module.exports = Self => {
|
|||
require('../methods/collection/newCollection')(Self);
|
||||
require('../methods/collection/getSectors')(Self);
|
||||
require('../methods/collection/setSaleQuantity')(Self);
|
||||
require('../methods/collection/previousLabel')(Self);
|
||||
};
|
||||
|
|
|
@ -20,10 +20,9 @@
|
|||
"type": "date"
|
||||
}
|
||||
},
|
||||
|
||||
"scope": {
|
||||
"where" :{
|
||||
"expired": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,17 +9,29 @@
|
|||
"properties": {
|
||||
"id": {
|
||||
"id": true,
|
||||
"type": "number",
|
||||
"forceId": false
|
||||
"type": "number"
|
||||
},
|
||||
"date": {
|
||||
"created": {
|
||||
"type": "date"
|
||||
},
|
||||
"m3":{
|
||||
"longitude":{
|
||||
"type": "number"
|
||||
},
|
||||
"warehouseFk":{
|
||||
"latitude":{
|
||||
"type": "number"
|
||||
},
|
||||
"dated":{
|
||||
"type": "date"
|
||||
},
|
||||
"ticketFk":{
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"ticket": {
|
||||
"type": "belongsTo",
|
||||
"model": "Ticket",
|
||||
"foreignKey": "ticketFk"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
"url": {
|
||||
"type": "string"
|
||||
},
|
||||
"token": {
|
||||
"cookie": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
@ -29,4 +29,4 @@
|
|||
"permission": "ALLOW"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/docuware/download')(Self);
|
||||
require('../methods/docuware/upload')(Self);
|
||||
require('../methods/docuware/checkFile')(Self);
|
||||
require('../methods/docuware/deliveryNoteEmail')(Self);
|
||||
require('../methods/docuware/core')(Self);
|
||||
};
|
||||
|
|
|
@ -19,20 +19,14 @@
|
|||
"fileCabinetName": {
|
||||
"type": "string"
|
||||
},
|
||||
"action": {
|
||||
"type": "string"
|
||||
},
|
||||
"dialogName": {
|
||||
"type": "string"
|
||||
},
|
||||
"find": {
|
||||
"findById": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"acls": [
|
||||
{
|
||||
"property": "*",
|
||||
"accessType": "*",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,16 +77,15 @@ module.exports = Self => {
|
|||
const newImage = await Self.upsertWithWhere(data, {
|
||||
name: fileName,
|
||||
collectionFk: collectionName,
|
||||
updated: (new Date).getTime()
|
||||
updated: Date.vnNow()
|
||||
}, 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 dstFile = path.join(dstDir, fileName);
|
||||
|
||||
const buffer = readChunk.sync(srcFilePath, 0, 12);
|
||||
const type = imageType(buffer);
|
||||
|
@ -102,7 +101,8 @@ module.exports = Self => {
|
|||
width: bmpData.width,
|
||||
height: bmpData.height,
|
||||
channels: 4
|
||||
}
|
||||
},
|
||||
failOn: 'none'
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -120,7 +120,7 @@ module.exports = Self => {
|
|||
const sizes = collection.sizes();
|
||||
for (let size of sizes) {
|
||||
const dstDir = path.join(collectionDir, `${size.width}x${size.height}`);
|
||||
const dstFile = path.join(dstDir, file);
|
||||
const dstFile = path.join(dstDir, fileName);
|
||||
const resizeOpts = {
|
||||
withoutEnlargement: true,
|
||||
fit: size.crop ? 'cover' : 'inside'
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/notification/send')(Self);
|
||||
require('../methods/notification/clean')(Self);
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "Notification",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "util.notification"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number",
|
||||
"id": true,
|
||||
"description": "Identifier"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"subscription": {
|
||||
"type": "hasMany",
|
||||
"model": "NotificationSubscription",
|
||||
"foreignKey": "notificationFk"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"name": "NotificationAcl",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "util.notificationAcl"
|
||||
}
|
||||
},
|
||||
"properties":{
|
||||
"notificationFk": {
|
||||
"id": true,
|
||||
"type": "number"
|
||||
},
|
||||
"roleFk":{
|
||||
"id": true,
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"notification": {
|
||||
"type": "belongsTo",
|
||||
"model": "Notification",
|
||||
"foreignKey": "notificationFk"
|
||||
},
|
||||
"role": {
|
||||
"type": "belongsTo",
|
||||
"model": "Role",
|
||||
"foreignKey": "roleFk"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,20 +1,19 @@
|
|||
{
|
||||
"name": "Sector",
|
||||
"name": "NotificationConfig",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "sector"
|
||||
"table": "util.notificationConfig"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number",
|
||||
"id": true,
|
||||
"description": "Identifier"
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
"cleanDays": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"name": "NotificationQueue",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "util.notificationQueue"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number",
|
||||
"id": true,
|
||||
"description": "Identifier"
|
||||
},
|
||||
"params": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"created": {
|
||||
"type": "date"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"notification": {
|
||||
"type": "belongsTo",
|
||||
"model": "Notification",
|
||||
"foreignKey": "notificationFk",
|
||||
"primaryKey": "name"
|
||||
},
|
||||
"author": {
|
||||
"type": "belongsTo",
|
||||
"model": "Account",
|
||||
"foreignKey": "authorFk"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "NotificationSubscription",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "util.notificationSubscription"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number",
|
||||
"id": true,
|
||||
"description": "Primary key"
|
||||
},
|
||||
"notificationFk": {
|
||||
"type": "number",
|
||||
"description": "Foreign key to Notification"
|
||||
},
|
||||
"userFk": {
|
||||
"type": "number",
|
||||
"description": "Foreign key to Account"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"notification": {
|
||||
"type": "belongsTo",
|
||||
"model": "Notification",
|
||||
"foreignKey": "notificationFk"
|
||||
},
|
||||
"user": {
|
||||
"type": "belongsTo",
|
||||
"model": "Account",
|
||||
"foreignKey": "userFk"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"name": "OsTicketConfig",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "osTicketConfig"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number",
|
||||
"id": true,
|
||||
"description": "Identifier"
|
||||
},
|
||||
"host": {
|
||||
"type": "string"
|
||||
},
|
||||
"user": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"oldStatus": {
|
||||
"type": "string"
|
||||
},
|
||||
"newStatusId": {
|
||||
"type": "number"
|
||||
},
|
||||
"day": {
|
||||
"type": "number"
|
||||
},
|
||||
"comment": {
|
||||
"type": "string"
|
||||
},
|
||||
"hostDb": {
|
||||
"type": "string"
|
||||
},
|
||||
"userDb": {
|
||||
"type": "string"
|
||||
},
|
||||
"passwordDb": {
|
||||
"type": "string"
|
||||
},
|
||||
"portDb": {
|
||||
"type": "number"
|
||||
},
|
||||
"responseType": {
|
||||
"type": "string"
|
||||
},
|
||||
"fromEmailId": {
|
||||
"type": "number"
|
||||
},
|
||||
"replyTo": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/osticket/osTicketReportEmail')(Self);
|
||||
require('../methods/osticket/closeTicket')(Self);
|
||||
};
|
|
@ -1,12 +1,5 @@
|
|||
{
|
||||
"name": "OsTicket",
|
||||
"base": "VnModel",
|
||||
"acls": [{
|
||||
"property": "validations",
|
||||
"accessType": "EXECUTE",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
}]
|
||||
"base": "VnModel"
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "PrintConfig",
|
||||
"description": "Print config",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "salix.printConfig"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"id": true,
|
||||
"type": "number",
|
||||
"description": "Identifier"
|
||||
},
|
||||
"itRecipient": {
|
||||
"type": "string"
|
||||
},
|
||||
"incidencesEmail": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"acls": [{
|
||||
"accessType": "READ",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
}]
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
const models = require('vn-loopback/server/server').models;
|
||||
|
||||
describe('loopback model Account', () => {
|
||||
it('should return true if the user has the given role', async() => {
|
||||
let result = await app.models.Account.hasRole(1, 'employee');
|
||||
let result = await models.Account.hasRole(1, 'employee');
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return false if the user doesnt have the given role', async() => {
|
||||
let result = await app.models.Account.hasRole(1, 'administrator');
|
||||
let result = await models.Account.hasRole(1, 'administrator');
|
||||
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('account recoverPassword()', () => {
|
||||
const userId = 1107;
|
||||
|
||||
const activeCtx = {
|
||||
accessToken: {userId: userId},
|
||||
http: {
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
it('should send email with token', async() => {
|
||||
const userId = 1107;
|
||||
const user = await models.Account.findById(userId);
|
||||
|
||||
await models.Account.recoverPassword(user.email);
|
||||
|
||||
const result = await models.AccessToken.findOne({where: {userId: userId}});
|
||||
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "Url",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "salix.url"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"appName": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"id": 1
|
||||
},
|
||||
"environment": {
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"id": 2
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
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();
|
||||
});
|
||||
};
|
|
@ -24,9 +24,6 @@
|
|||
},
|
||||
"isManaged":{
|
||||
"type": "boolean"
|
||||
},
|
||||
"hasStowaway":{
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"acls": [
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"modules/supplier/front/**/*",
|
||||
"modules/ticket/front/**/*",
|
||||
"modules/travel/front/**/*",
|
||||
"modules/shelving/front/**/*",
|
||||
"modules/worker/front/**/*",
|
||||
"modules/zone/front/**/*"
|
||||
]
|
||||
|
|
|
@ -7,13 +7,18 @@ process.on('warning', warning => {
|
|||
console.log(warning.stack);
|
||||
});
|
||||
|
||||
process.on('exit', async function() {
|
||||
if (container) await container.rm();
|
||||
});
|
||||
|
||||
let container;
|
||||
async function test() {
|
||||
let isCI = false;
|
||||
|
||||
if (process.argv[2] === 'ci')
|
||||
isCI = true;
|
||||
|
||||
const container = new Docker();
|
||||
container = new Docker();
|
||||
|
||||
await container.run(isCI);
|
||||
dataSources = JSON.parse(JSON.stringify(dataSources));
|
||||
|
@ -25,7 +30,10 @@ async function test() {
|
|||
|
||||
const bootOptions = {dataSources};
|
||||
const app = require('vn-loopback/server/server');
|
||||
app.boot(bootOptions);
|
||||
await new Promise((resolve, reject) => {
|
||||
app.boot(bootOptions,
|
||||
err => err ? reject(err) : resolve());
|
||||
});
|
||||
|
||||
const Jasmine = require('jasmine');
|
||||
const jasmine = new Jasmine();
|
||||
|
@ -46,6 +54,7 @@ async function test() {
|
|||
jasmine.addReporter(new JunitReporter.JUnitXmlReporter());
|
||||
|
||||
jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
|
||||
jasmine.exitOnCompletion = true;
|
||||
}
|
||||
|
||||
const backSpecs = [
|
||||
|
@ -60,11 +69,10 @@ async function test() {
|
|||
helpers: [],
|
||||
});
|
||||
|
||||
jasmine.exitOnCompletion = false;
|
||||
await jasmine.execute();
|
||||
if (app) await app.disconnect();
|
||||
if (container) await container.rm();
|
||||
console.log('app disconnected & container removed');
|
||||
console.log('App disconnected & container removed');
|
||||
}
|
||||
|
||||
test();
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||
VALUES
|
||||
('ClientConsumptionQueue', '*', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Ticket', 'deliveryNotePdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Ticket', 'deliveryNoteEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Ticket', 'deliveryNoteCsvPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Ticket', 'deliveryNoteCsvEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Client', 'campaignMetricsPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Client', 'campaignMetricsEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Client', 'clientWelcomeHtml', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Client', 'clientWelcomeEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Client', 'creditRequestPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Client', 'creditRequestHtml', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Client', 'creditRequestEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Client', 'printerSetupHtml', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Client', 'printerSetupEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Client', 'sepaCoreEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Client', 'letterDebtorPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Client', 'letterDebtorStHtml', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Client', 'letterDebtorStEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Client', 'letterDebtorNdHtml', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Client', 'letterDebtorNdEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Client', 'clientDebtStatementPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Client', 'clientDebtStatementHtml', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Client', 'clientDebtStatementEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Client', 'incotermsAuthorizationPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Client', 'incotermsAuthorizationHtml', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Client', 'incotermsAuthorizationEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Client', 'consumptionSendQueued', 'WRITE', 'ALLOW', 'ROLE', 'system'),
|
||||
('InvoiceOut', 'invoiceEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
|
||||
('InvoiceOut', 'exportationPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('InvoiceOut', 'sendQueued', 'WRITE', 'ALLOW', 'ROLE', 'system'),
|
||||
('Ticket', 'invoiceCsvPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Ticket', 'invoiceCsvEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Supplier', 'campaignMetricsPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Supplier', 'campaignMetricsEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Travel', 'extraCommunityPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Travel', 'extraCommunityEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Entry', 'entryOrderPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('OsTicket', 'osTicketReportEmail', 'WRITE', 'ALLOW', 'ROLE', 'system'),
|
||||
('Item', 'buyerWasteEmail', 'WRITE', 'ALLOW', 'ROLE', 'system'),
|
||||
('Claim', 'claimPickupPdf', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Claim', 'claimPickupEmail', 'WRITE', 'ALLOW', 'ROLE', 'claimManager'),
|
||||
('Item', 'labelPdf', 'READ', 'ALLOW', 'ROLE', 'employee');
|
||||
|
||||
INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalType,principalId)
|
||||
VALUES ('Sector','*','READ','ALLOW','ROLE','employee');
|
||||
INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalType,principalId)
|
||||
VALUES ('Sector','*','WRITE','ALLOW','ROLE','employee');
|
|
@ -1,3 +1,3 @@
|
|||
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
|
||||
VALUES
|
||||
('SupplierAgencyTerm', '*', '*', 'ALLOW', 'ROLE', 'administrative');
|
||||
('Receipt', 'receiptPdf', '*', 'ALLOW', 'ROLE', 'salesAssistant');
|
|
@ -0,0 +1,9 @@
|
|||
create table `vn`.`clientConsumptionQueue`
|
||||
(
|
||||
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
params json not null,
|
||||
queued datetime default current_timestamp() not null,
|
||||
printed datetime null,
|
||||
status varchar(50) default '' null
|
||||
)
|
||||
comment 'Queue for client consumption PDF mailing';
|
|
@ -0,0 +1 @@
|
|||
rename table `vn`.`invoiceOut_queue` to `vn`.`invoiceOutQueue`;
|
|
@ -0,0 +1,5 @@
|
|||
ALTER TABLE `vn`.`itemConfig`
|
||||
ADD id int null PRIMARY KEY first;
|
||||
|
||||
ALTER TABLE `vn`.`itemConfig`
|
||||
ADD wasteRecipients VARCHAR(50) NOT NULL comment 'Weekly waste report schedule recipients';
|
|
@ -0,0 +1,10 @@
|
|||
create table `salix`.`printConfig`
|
||||
(
|
||||
id int auto_increment,
|
||||
itRecipient varchar(50) null comment 'IT recipients for report mailing',
|
||||
incidencesEmail varchar(50) null comment 'CAU destinatary email',
|
||||
constraint printConfig_pk
|
||||
primary key (id)
|
||||
)
|
||||
comment 'Print service config';
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
DROP TRIGGER IF EXISTS vn.sale_afterUpdate;
|
||||
USE vn;
|
||||
|
||||
DELIMITER $$
|
||||
$$
|
||||
CREATE DEFINER=`root`@`localhost` TRIGGER `vn`.`sale_afterUpdate`
|
||||
AFTER UPDATE ON `sale`
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
DECLARE vIsToSendMail BOOL;
|
||||
DECLARE vPickedLines INT;
|
||||
DECLARE vCollectionFk INT;
|
||||
DECLARE vUserRole VARCHAR(255);
|
||||
|
||||
IF !(NEW.id <=> OLD.id)
|
||||
OR !(NEW.ticketFk <=> OLD.ticketFk)
|
||||
OR !(NEW.itemFk <=> OLD.itemFk)
|
||||
OR !(NEW.quantity <=> OLD.quantity)
|
||||
OR !(NEW.created <=> OLD.created)
|
||||
OR !(NEW.isPicked <=> OLD.isPicked) THEN
|
||||
CALL stock.log_add('sale', NEW.id, OLD.id);
|
||||
END IF;
|
||||
|
||||
IF !(NEW.price <=> OLD.price)
|
||||
OR !(NEW.ticketFk <=> OLD.ticketFk)
|
||||
OR !(NEW.itemFk <=> OLD.itemFk)
|
||||
OR !(NEW.quantity <=> OLD.quantity)
|
||||
OR !(NEW.discount <=> OLD.discount) THEN
|
||||
CALL ticket_requestRecalc(NEW.ticketFk);
|
||||
CALL ticket_requestRecalc(OLD.ticketFk);
|
||||
END IF;
|
||||
|
||||
IF !(OLD.ticketFk <=> NEW.ticketFk) THEN
|
||||
UPDATE ticketRequest SET ticketFk = NEW.ticketFk
|
||||
WHERE saleFk = NEW.id;
|
||||
END IF;
|
||||
|
||||
SELECT account.myUser_getName() INTO vUserRole;
|
||||
SELECT account.user_getMysqlRole(vUserRole) INTO vUserRole;
|
||||
|
||||
IF !(OLD.quantity <=> NEW.quantity) THEN
|
||||
SELECT COUNT(*) INTO vIsToSendMail
|
||||
FROM vncontrol.inter i
|
||||
JOIN vn.state s ON s.id = i.state_id
|
||||
WHERE s.code='PACKED'
|
||||
AND i.Id_Ticket = OLD.ticketFk
|
||||
AND vUserRole IN ('salesPerson', 'salesTeamBoss')
|
||||
LIMIT 1;
|
||||
|
||||
IF vIsToSendMail THEN
|
||||
CALL vn.mail_insert('jefesventas@verdnatura.es',
|
||||
'noreply@verdnatura.es',
|
||||
CONCAT('Ticket ', OLD.ticketFk ,' modificada cantidad tras encajado'),
|
||||
CONCAT('Ticket <a href="https://salix.verdnatura.es/#!/ticket/', OLD.ticketFk ,'/log">', OLD.ticketFk ,'</a>. <br>',
|
||||
'Modificada la catidad de ', OLD.quantity, ' a ' , NEW.quantity,
|
||||
' del artículo ', OLD.itemFk, ' tras estado encajado del ticket. <br>',
|
||||
'Este email se ha generado automáticamente' )
|
||||
);
|
||||
END IF;
|
||||
IF (OLD.quantity > NEW.quantity) THEN
|
||||
INSERT INTO saleComponent(saleFk, componentFk, value)
|
||||
SELECT NEW.id, cm.id, sc.value
|
||||
FROM saleComponent sc
|
||||
JOIN component cd ON cd.id = sc.componentFk
|
||||
JOIN component cm ON cm.code = 'mana'
|
||||
WHERE saleFk = NEW.id AND cd.code = 'lastUnitsDiscount'
|
||||
ON DUPLICATE KEY UPDATE value = sc.value + VALUES(value);
|
||||
|
||||
DELETE sc.*
|
||||
FROM vn.saleComponent sc
|
||||
JOIN component c ON c.id = sc.componentFk
|
||||
WHERE saleFk = NEW.id AND c.code = 'lastUnitsDiscount';
|
||||
END IF;
|
||||
INSERT IGNORE INTO `vn`.`routeRecalc` (`routeFk`)
|
||||
SELECT r.id
|
||||
FROM vn.sale s
|
||||
JOIN vn.ticket t ON t.id = s.ticketFk
|
||||
JOIN vn.route r ON r.id = t.routeFk
|
||||
WHERE r.isOk = FALSE
|
||||
AND s.id = NEW.id
|
||||
AND r.created >= CURDATE()
|
||||
GROUP BY r.id;
|
||||
END IF;
|
||||
|
||||
IF !(ABS(NEW.isPicked) <=> ABS(OLD.isPicked)) AND NEW.quantity > 0 THEN
|
||||
|
||||
UPDATE vn.collection c
|
||||
JOIN vn.ticketCollection tc ON tc.collectionFk = c.id AND tc.ticketFk = NEW.ticketFk
|
||||
SET c.salePickedCount = c.salePickedCount + IF(NEW.isPicked != 0, 1, -1);
|
||||
|
||||
END IF;
|
||||
|
||||
IF !(NEW.quantity <=> OLD.quantity) AND (NEW.quantity = 0 OR OLD.quantity = 0) THEN
|
||||
|
||||
UPDATE vn.collection c
|
||||
JOIN vn.ticketCollection tc ON tc.collectionFk = c.id AND tc.ticketFk = NEW.ticketFk
|
||||
SET c.saleTotalCount = c.saleTotalCount + IF(OLD.quantity = 0, 1, -1);
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
|
@ -0,0 +1,6 @@
|
|||
alter table `vn`.`sample`
|
||||
add model VARCHAR(25) null comment 'Model name in plural';
|
||||
|
||||
UPDATE vn.sample t
|
||||
SET t.model = 'Clients'
|
||||
WHERE t.id IN(12, 13, 14, 15, 16, 18, 19, 20);
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE `account`.`user` ADD hasGrant TINYINT(1) NOT NULL;
|
|
@ -0,0 +1,2 @@
|
|||
INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalId)
|
||||
VALUES ('WorkerDisableExcluded','*','*','ALLOW','hr');
|
|
@ -1,2 +1,3 @@
|
|||
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
|
||||
VALUES('EducationLevel', '*', '*', 'ALLOW', 'ROLE', 'employee');
|
||||
VALUES
|
||||
('Business', '*', '*', 'ALLOW', 'ROLE', 'hr');
|
|
@ -0,0 +1,3 @@
|
|||
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
|
||||
VALUES
|
||||
('Sale', 'usesMana', '*', 'ALLOW', 'ROLE', 'employee');
|
|
@ -0,0 +1,2 @@
|
|||
INSERT INTO `vn`.`payDem` (id,payDem)
|
||||
VALUES (7,'0');
|
|
@ -0,0 +1,4 @@
|
|||
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
|
||||
VALUES
|
||||
('InvoiceIn', 'invoiceInPdf', 'READ', 'ALLOW', 'ROLE', 'administrative'),
|
||||
('InvoiceIn', 'invoiceInEmail', 'WRITE', 'ALLOW', 'ROLE', 'administrative');
|
|
@ -1,2 +1,2 @@
|
|||
INSERT INTO `salix`.`ACL` (model,property,accessType,principalId)
|
||||
VALUES ('AgencyTerm','*','*','administrative');
|
||||
VALUES ('Supplier','newSupplier','WRITE','administrative');
|
|
@ -0,0 +1,23 @@
|
|||
DROP FUNCTION IF EXISTS `util`.`notification_send`;
|
||||
DELIMITER $$
|
||||
CREATE DEFINER=`root`@`localhost` FUNCTION `util`.`notification_send`(vNotificationName VARCHAR(255), vParams TEXT, vAuthorFk INT)
|
||||
RETURNS INT
|
||||
MODIFIES SQL DATA
|
||||
BEGIN
|
||||
/**
|
||||
* Sends a notification.
|
||||
*
|
||||
* @param vNotificationName The notification name
|
||||
* @param vParams The notification parameters formatted as JSON
|
||||
* @param vAuthorFk The notification author or %NULL if there is no author
|
||||
* @return The notification id
|
||||
*/
|
||||
|
||||
INSERT INTO notificationQueue
|
||||
SET notificationFk = vNotificationName,
|
||||
params = vParams,
|
||||
authorFk = vAuthorFk;
|
||||
|
||||
RETURN LAST_INSERT_ID();
|
||||
END$$
|
||||
DELIMITER ;
|
|
@ -0,0 +1,63 @@
|
|||
USE util;
|
||||
|
||||
CREATE TABLE notification(
|
||||
id INT PRIMARY KEY,
|
||||
`name` VARCHAR(255) UNIQUE,
|
||||
`description` VARCHAR(255)
|
||||
);
|
||||
|
||||
CREATE TABLE notificationAcl(
|
||||
notificationFk INT,
|
||||
roleFk INT(10) unsigned,
|
||||
PRIMARY KEY(notificationFk, roleFk)
|
||||
);
|
||||
|
||||
ALTER TABLE `util`.`notificationAcl` ADD CONSTRAINT `notificationAcl_ibfk_1` FOREIGN KEY (`notificationFk`) REFERENCES `util`.`notification` (`id`)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE `util`.`notificationAcl` ADD CONSTRAINT `notificationAcl_ibfk_2` FOREIGN KEY (`roleFk`) REFERENCES `account`.`role`(`id`)
|
||||
ON DELETE RESTRICT
|
||||
ON UPDATE CASCADE;
|
||||
|
||||
CREATE TABLE notificationSubscription(
|
||||
notificationFk INT,
|
||||
userFk INT(10) unsigned,
|
||||
PRIMARY KEY(notificationFk, userFk)
|
||||
);
|
||||
|
||||
ALTER TABLE `util`.`notificationSubscription` ADD CONSTRAINT `notificationSubscription_ibfk_1` FOREIGN KEY (`notificationFk`) REFERENCES `util`.`notification` (`id`)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE `util`.`notificationSubscription` ADD CONSTRAINT `notificationSubscription_ibfk_2` FOREIGN KEY (`userFk`) REFERENCES `account`.`user`(`id`)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE;
|
||||
|
||||
CREATE TABLE notificationQueue(
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
notificationFk VARCHAR(255),
|
||||
params JSON,
|
||||
authorFk INT(10) unsigned NULL,
|
||||
`status` ENUM('pending', 'sent', 'error') NOT NULL DEFAULT 'pending',
|
||||
created DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX(notificationFk),
|
||||
INDEX(authorFk),
|
||||
INDEX(status)
|
||||
);
|
||||
|
||||
ALTER TABLE `util`.`notificationQueue` ADD CONSTRAINT `nnotificationQueue_ibfk_1` FOREIGN KEY (`notificationFk`) REFERENCES `util`.`notification` (`name`)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE;
|
||||
|
||||
ALTER TABLE `util`.`notificationQueue` ADD CONSTRAINT `notificationQueue_ibfk_2` FOREIGN KEY (`authorFk`) REFERENCES `account`.`user`(`id`)
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE CASCADE;
|
||||
|
||||
CREATE TABLE notificationConfig(
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
cleanDays MEDIUMINT
|
||||
);
|
||||
|
||||
INSERT INTO notificationConfig
|
||||
SET cleanDays = 90;
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE `vn`.`supplier` MODIFY COLUMN payMethodFk tinyint(3) unsigned NULL;
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE `vn`.`supplier` MODIFY COLUMN supplierActivityFk varchar(45) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL NULL;
|
|
@ -0,0 +1,35 @@
|
|||
drop procedure `vn`.`ticket_closeByTicket`;
|
||||
|
||||
DELIMITER $$
|
||||
$$
|
||||
create
|
||||
definer = `root`@`localhost` procedure `vn`.`ticket_closeByTicket`(IN vTicketFk int)
|
||||
BEGIN
|
||||
|
||||
/**
|
||||
* Inserta el ticket en la tabla temporal
|
||||
* para ser cerrado.
|
||||
*
|
||||
* @param vTicketFk Id del ticket
|
||||
*/
|
||||
|
||||
DROP TEMPORARY TABLE IF EXISTS tmp.ticket_close;
|
||||
CREATE TEMPORARY TABLE tmp.ticket_close ENGINE = MEMORY (
|
||||
SELECT
|
||||
t.id AS ticketFk
|
||||
FROM ticket t
|
||||
JOIN agencyMode am ON am.id = t.agencyModeFk
|
||||
LEFT JOIN ticketState ts ON ts.ticketFk = t.id
|
||||
JOIN alertLevel al ON al.id = ts.alertLevel
|
||||
WHERE al.code = 'PACKED' OR (am.code = 'refund' AND al.code != 'delivered')
|
||||
AND t.id = vTicketFk
|
||||
AND t.refFk IS NULL
|
||||
GROUP BY t.id);
|
||||
|
||||
CALL ticket_close();
|
||||
|
||||
DROP TEMPORARY TABLE tmp.ticket_close;
|
||||
END$$
|
||||
DELIMITER ;
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
CREATE TABLE `vn`.`zipConfig` (
|
||||
`id` double(10,2) NOT NULL,
|
||||
`maxSize` int(11) DEFAULT NULL COMMENT 'in MegaBytes',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||
|
||||
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
|
||||
VALUES
|
||||
('ZipConfig', '*', '*', 'ALLOW', 'ROLE', 'employee');
|
|
@ -0,0 +1,5 @@
|
|||
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||
VALUES
|
||||
('ClaimRma', '*', 'READ', 'ALLOW', 'ROLE', 'claimManager'),
|
||||
('ClaimRma', '*', 'WRITE', 'ALLOW', 'ROLE', 'claimManager');
|
||||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE `vn`.`claim` ADD rma varchar(100) NULL ;
|
|
@ -0,0 +1,7 @@
|
|||
CREATE TABLE `vn`.`claimRma` (
|
||||
id INT UNSIGNED auto_increment NOT NULL PRIMARY KEY,
|
||||
code varchar(100) NOT NULL,
|
||||
created timestamp DEFAULT current_timestamp() NOT NULL,
|
||||
workerFk INTEGER UNSIGNED NOT NULL
|
||||
)
|
||||
ENGINE=InnoDB;
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue