Merge branch 'master' into 4609-Refactor-www.verdnatura.es-and-shop.verdnatura.es
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Pau 2022-11-15 06:06:20 +00:00
commit 6eaee8faca
424 changed files with 51616 additions and 3479 deletions

View File

@ -1,32 +1,43 @@
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 - \
&& 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

14
Jenkinsfile vendored
View File

@ -62,13 +62,13 @@ pipeline {
}
}
}
stage('Backend') {
steps {
nodejs('node-v14') {
sh 'npm run test:back:ci'
}
}
}
// stage('Backend') {
// steps {
// nodejs('node-v14') {
// sh 'npm run test:back:ci'
// }
// }
// }
}
}
stage('Build') {

View File

@ -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);
};
};

View File

@ -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();
});
});

View File

@ -17,56 +17,52 @@ module.exports = Self => {
});
Self.deleteTrashFiles = async options => {
const tx = await Self.beginTransaction({});
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 {
if (process.env.NODE_ENV == 'test')
throw new UserError(`Action not allowed on the test environment`);
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 date = new Date();
date.setMonth(date.getMonth() - 4);
const date = new Date();
date.setMonth(date.getMonth() - 4);
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);
const dstFile = path.join(dmsContainer.client.root, pathHash, dms.file);
const dstFolder = path.join(dmsContainer.client.root, pathHash);
await fs.unlink(dstFile);
try {
await fs.rmdir(dstFolder);
await dms.destroy(myOptions);
} catch (err) {
await dms.destroy(myOptions);
}
const dmsToDelete = await models.Dms.find({
where: {
and: [
{dmsTypeFk: trashDmsType.id},
{created: {lt: date}}
]
}
if (tx) await tx.commit();
} catch (e) {
if (tx) await tx.rollback();
}, myOptions);
throw e;
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);
} catch (err) {
if (err.code != 'ENOENT')
throw err;
}
await dms.destroy(myOptions);
const dstFolder = path.join(dmsContainer.client.root, pathHash);
try {
await fs.rmdir(dstFolder);
} catch (err) {
continue;
}
}
};
};

View File

@ -0,0 +1,46 @@
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);
const cleanDate = new Date();
cleanDate.setDate(cleanDate.getDate() - config.cleanDays);
await models.NotificationQueue.destroyAll({
where: {status: {inq: status}},
created: {lt: cleanDate}
}, myOptions);
if (tx) await tx.commit();
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -0,0 +1,81 @@
const {Email} = require('vn-print');
const UserError = require('vn-loopback/util/user-error');
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', 'email', 'lang']
}
}
}
}
}
}
]
}, 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().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);
}
}
}
};
};

View File

@ -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 = new Date();
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);
});
});

View File

@ -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);
});
});

View File

@ -0,0 +1,118 @@
const jsdom = require('jsdom');
const mysql = require('mysql');
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 ote.thread_id, MAX(ote.created) created, MAX(ote.updated) updated
FROM osticket.ost_thread_entry ote
WHERE ote.staff_id != 0 AND ote.type = 'R'
GROUP BY ote.thread_id
) sub ON sub.thread_id = ot2.id
WHERE ot.isanswered = 1
AND ots.state = '${config.oldStatus}'
AND IF(sub.updated > sub.created, sub.updated, sub.created) < DATE_SUB(CURDATE(), INTERVAL ${config.day} DAY)`;
let ticketsId = [];
con.connect(err => {
if (err) throw err;
con.query(sql, (err, results) => {
if (err) throw err;
for (const result of results)
ticketsId.push(result.ticket_id);
});
});
await requestToken();
async function requestToken() {
const response = await fetch(ostUri);
const result = response.headers.get('set-cookie');
const [firtHeader] = result.split(' ');
const firtCookie = 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, firtCookie);
}
async function login(token, firtCookie) {
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': firtCookie
}
};
const response = await fetch(ostUri, params);
const result = response.headers.get('set-cookie');
const [firtHeader] = result.split(' ');
const secondCookie = firtHeader.substring(0, firtHeader.length - 1);
await close(token, secondCookie);
}
async function close(token, secondCookie) {
for (const ticketId of ticketsId) {
const ostUri = `${config.host}/ajax.php/tickets/${ticketId}/status`;
const data = {
status_id: config.newStatusId,
comments: config.comment,
undefined: config.action
};
const params = {
method: 'POST',
body: new URLSearchParams(data),
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-CSRFToken': token,
'Cookie': secondCookie
}
};
return fetch(ostUri, params);
}
}
};
};

View File

@ -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();
};
};

View File

@ -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"
}
}

View File

@ -7,6 +7,7 @@ module.exports = Self => {
require('../methods/account/change-password')(Self);
require('../methods/account/set-password')(Self);
require('../methods/account/validate-token')(Self);
require('../methods/account/privileges')(Self);
// Validations
@ -77,7 +78,7 @@ module.exports = Self => {
`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 = [];

View File

@ -48,6 +48,9 @@
},
"image": {
"type": "string"
},
"hasGrant": {
"type": "boolean"
}
},
"relations": {
@ -99,6 +102,13 @@
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
},
{
"property": "privileges",
"accessType": "*",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
}
]
}

View File

@ -0,0 +1,4 @@
module.exports = Self => {
require('../methods/notification/send')(Self);
require('../methods/notification/clean')(Self);
};

View File

@ -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"
}
}
}

View File

@ -0,0 +1,21 @@
{
"name": "NotificationAcl",
"base": "VnModel",
"options": {
"mysql": {
"table": "util.notificationAcl"
}
},
"relations": {
"notification": {
"type": "belongsTo",
"model": "Notification",
"foreignKey": "notificationFk"
},
"role": {
"type": "belongsTo",
"model": "Role",
"foreignKey": "roleFk"
}
}
}

View File

@ -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"
}
}
}

View File

@ -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"
}
}
}

View File

@ -0,0 +1,33 @@
{
"name": "NotificationSubscription",
"base": "VnModel",
"options": {
"mysql": {
"table": "util.notificationSubscription"
}
},
"properties": {
"notificationFk": {
"type": "number",
"id": true,
"description": "Identifier"
},
"userFk": {
"type": "number",
"id": true,
"description": "Identifier"
}
},
"relations": {
"notification": {
"type": "belongsTo",
"model": "Notification",
"foreignKey": "notificationFk"
},
"user": {
"type": "belongsTo",
"model": "Account",
"foreignKey": "userFk"
}
}
}

View File

@ -0,0 +1,52 @@
{
"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"
},
"action": {
"type": "string"
},
"day": {
"type": "number"
},
"comment": {
"type": "string"
},
"hostDb": {
"type": "string"
},
"userDb": {
"type": "string"
},
"passwordDb": {
"type": "string"
},
"portDb": {
"type": "number"
}
}
}

4
back/models/osticket.js Normal file
View File

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

View File

@ -1,12 +1,5 @@
{
"name": "OsTicket",
"base": "VnModel",
"acls": [{
"property": "validations",
"accessType": "EXECUTE",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}]
"base": "VnModel"
}

View File

@ -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"
}]
}

25
back/models/url.json Normal file
View File

@ -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
}
}
}

View File

@ -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));
@ -46,6 +51,7 @@ async function test() {
jasmine.addReporter(new JunitReporter.JUnitXmlReporter());
jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
jasmine.exitOnCompletion = true;
}
const backSpecs = [
@ -60,11 +66,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();

View File

@ -0,0 +1,5 @@
ALTER TABLE `vn`.`itemType` CHANGE `transaction` transaction__ tinyint(4) DEFAULT 0 NOT NULL;
ALTER TABLE `vn`.`itemType` CHANGE location location__ varchar(10) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL NULL;
ALTER TABLE `vn`.`itemType` CHANGE hasComponents hasComponents__ tinyint(1) DEFAULT 1 NOT NULL;
ALTER TABLE `vn`.`itemType` CHANGE warehouseFk warehouseFk__ smallint(6) unsigned DEFAULT 60 NOT NULL;
ALTER TABLE `vn`.`itemType` CHANGE compression compression__ decimal(5,2) DEFAULT 1.00 NULL;

View File

@ -0,0 +1,3 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('OsTicket', '*', '*', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1,3 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('OsTicketConfig', '*', '*', 'ALLOW', 'ROLE', 'it');

View File

@ -0,0 +1,20 @@
CREATE TABLE `vn`.`osTicketConfig` (
`id` int(11) NOT NULL,
`host` varchar(100) COLLATE utf8mb3_unicode_ci DEFAULT NULL,
`user` varchar(100) COLLATE utf8mb3_unicode_ci DEFAULT NULL,
`password` varchar(100) COLLATE utf8mb3_unicode_ci DEFAULT NULL,
`oldStatus` varchar(100) COLLATE utf8mb3_unicode_ci DEFAULT NULL,
`newStatusId` int(11) DEFAULT NULL,
`action` varchar(100) COLLATE utf8mb3_unicode_ci DEFAULT NULL,
`day` int(11) DEFAULT NULL,
`comment` varchar(100) COLLATE utf8mb3_unicode_ci DEFAULT NULL,
`hostDb` varchar(100) COLLATE utf8mb3_unicode_ci DEFAULT NULL,
`userDb` varchar(100) COLLATE utf8mb3_unicode_ci DEFAULT NULL,
`passwordDb` varchar(100) COLLATE utf8mb3_unicode_ci DEFAULT NULL,
`portDb` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
INSERT INTO `vn`.`osTicketConfig`(`id`, `host`, `user`, `password`, `oldStatus`, `newStatusId`, `action`, `day`, `comment`, `hostDb`, `userDb`, `passwordDb`, `portDb`)
VALUES
(0, 'https://cau.verdnatura.es/scp', NULL, NULL, 'open', 3, 'Cerrar', 60, 'Este CAU se ha cerrado automáticamente', NULL, NULL, NULL, NULL);

View File

@ -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');

View File

@ -0,0 +1,3 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Receipt', 'receiptPdf', '*', 'ALLOW', 'ROLE', 'salesAssistant');

View File

@ -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';

View File

@ -0,0 +1 @@
rename table `vn`.`invoiceOut_queue` to `vn`.`invoiceOutQueue`;

View File

@ -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';

View File

@ -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';

View File

@ -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 ;

View File

@ -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);

View File

@ -0,0 +1 @@
ALTER TABLE `account`.`user` ADD hasGrant TINYINT(1) NOT NULL;

View File

@ -0,0 +1,2 @@
INSERT INTO `vn`.`payDem` (id,payDem)
VALUES (7,'0');

View File

@ -0,0 +1,2 @@
INSERT INTO `salix`.`ACL` (model,property,accessType,principalId)
VALUES ('Supplier','newSupplier','WRITE','administrative');

View File

@ -0,0 +1,28 @@
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
*/
DECLARE vNotificationFk INT;
SELECT id INTO vNotificationFk
FROM `notification`
WHERE `name` = vNotificationName;
INSERT INTO notificationQueue
SET notificationFk = vNotificationFk,
params = vParams,
authorFk = vAuthorFk;
RETURN LAST_INSERT_ID();
END$$
DELIMITER ;

View File

@ -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;

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`supplier` MODIFY COLUMN payMethodFk tinyint(3) unsigned NULL;

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`supplier` MODIFY COLUMN supplierActivityFk varchar(45) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL NULL;

View File

@ -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 ;

View File

@ -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');

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`claim` ADD rma varchar(100) NULL ;

View File

@ -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;

View File

@ -0,0 +1,3 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Notification', '*', 'WRITE', 'ALLOW', 'ROLE', 'developer');

View File

@ -0,0 +1,12 @@
CREATE TABLE `vn`.`packingSiteConfig` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`shinobiUrl` varchar(255) NOT NULL,
`shinobiToken` varchar(255) NOT NULL,
`shinobiGroupKey` varchar(255) NOT NULL,
`avgBoxingTime` INT(3) NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Boxing', '*', '*', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1,56 @@
ALTER TABLE `vn`.`packingSite` ADD monitorId varchar(255) NULL;
UPDATE `vn`.`packingSite`
SET monitorId = 'VbiUcajdaT'
WHERE code = 'h1';
UPDATE `vn`.`packingSite`
SET monitorId = 'qKMPn9aaVe'
WHERE code = 'h2';
UPDATE `vn`.`packingSite`
SET monitorId = '3CtdIAGPAv'
WHERE code = 'h3';
UPDATE `vn`.`packingSite`
SET monitorId = 'Xme2hiqz1f'
WHERE code = 'h4';
UPDATE `vn`.`packingSite`
SET monitorId = 'aulxefgfJU'
WHERE code = 'h5';
UPDATE `vn`.`packingSite`
SET monitorId = '6Ou0D1bhBw'
WHERE code = 'h6';
UPDATE `vn`.`packingSite`
SET monitorId = 'eVUvnE6pNw'
WHERE code = 'h7';
UPDATE `vn`.`packingSite`
SET monitorId = '0wsmSvqmrs'
WHERE code = 'h8';
UPDATE `vn`.`packingSite`
SET monitorId = 'r2l2RyyF4I'
WHERE code = 'h9';
UPDATE `vn`.`packingSite`
SET monitorId = 'EdjHLIiDVD'
WHERE code = 'h10';
UPDATE `vn`.`packingSite`
SET monitorId = 'czC45kmwqI'
WHERE code = 'h11';
UPDATE `vn`.`packingSite`
SET monitorId = 'PNsmxPaCwQ'
WHERE code = 'h12';
UPDATE `vn`.`packingSite`
SET monitorId = 'agVssO0FDC'
WHERE code = 'h13';
UPDATE `vn`.`packingSite`
SET monitorId = 'f2SPNENHPo'
WHERE code = 'h14';
UPDATE `vn`.`packingSite`
SET monitorId = '6UR7gUZxks'
WHERE code = 'h15';
UPDATE `vn`.`packingSite`
SET monitorId = 'bOB0f8WZ2V'
WHERE code = 'h16';
UPDATE `vn`.`packingSite`
SET monitorId = 'MIR1nXaL0n'
WHERE code = 'h17';
UPDATE `vn`.`packingSite`
SET monitorId = '0Oj9SgGTXR'
WHERE code = 'h18';

View File

@ -0,0 +1,33 @@
CREATE TABLE `salix`.`url` (
`appName` varchar(100) NOT NULL,
`environment` varchar(100) NOT NULL,
`url` varchar(255) NOT NULL,
PRIMARY KEY (`appName`,`environment`)
);
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
VALUES
('salix', 'production', 'https://salix.verdnatura.es/#!/');
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
VALUES
('salix', 'test', 'https://test-salix.verdnatura.es/#!/');
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
VALUES
('salix', 'dev', 'http://localhost:5000/#!/');
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
VALUES
('lilium', 'production', 'https://lilium.verdnatura.es/#/');
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
VALUES
('lilium', 'test', 'https://test-lilium.verdnatura.es/#/');
INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
VALUES
('lilium', 'dev', 'http://localhost:8080/#/');
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Url', '*', 'READ', 'ALLOW', 'ROLE', 'employee');
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Url', '*', 'WRITE', 'ALLOW', 'ROLE', 'it');

View File

@ -19,8 +19,9 @@ module.exports = class Docker {
* to avoid a bug with OverlayFS driver on MacOS.
*
* @param {Boolean} ci continuous integration environment argument
* @param {String} networkName Name of the container network
*/
async run(ci) {
async run(ci, networkName = 'jenkins') {
let d = new Date();
let pad = v => v < 10 ? '0' + v : v;
let stamp = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
@ -42,8 +43,16 @@ module.exports = class Docker {
let runChown = process.platform != 'linux';
let network = '';
if (ci) network = `--network="${networkName}"`;
log('Starting container...');
const container = await this.execP(`docker run --env RUN_CHOWN=${runChown} -d ${dockerArgs} salix-db`);
const container = await this.execP(`
docker run \
${network} \
--env RUN_CHOWN=${runChown} \
-d ${dockerArgs} salix-db
`);
this.id = container.stdout.trim();
try {
@ -51,10 +60,11 @@ module.exports = class Docker {
let inspect = await this.execP(`docker inspect -f "{{json .NetworkSettings}}" ${this.id}`);
let netSettings = JSON.parse(inspect.stdout);
if (ci)
this.dbConf.host = netSettings.Gateway;
this.dbConf.port = netSettings.Ports['3306/tcp'][0]['HostPort'];
if (ci) {
this.dbConf.host = netSettings.Networks[networkName].IPAddress;
this.dbConf.port = 3306;
} else
this.dbConf.port = netSettings.Ports['3306/tcp'][0]['HostPort'];
}
await this.wait();

View File

@ -13,6 +13,10 @@ INSERT INTO `salix`.`AccessToken` (`id`, `ttl`, `created`, `userId`)
VALUES
('DEFAULT_TOKEN', '1209600', util.VN_CURDATE(), 66);
INSERT INTO `salix`.`printConfig` (`id`, `itRecipient`, `incidencesEmail`)
VALUES
(1, 'it@gotamcity.com', 'incidences@gotamcity.com');
INSERT INTO `vn`.`ticketConfig` (`id`, `scopeDays`)
VALUES
('1', '6');
@ -41,8 +45,8 @@ INSERT INTO `account`.`roleConfig`(`id`, `mysqlPassword`, `rolePrefix`, `userPre
CALL `account`.`role_sync`;
INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `password`,`role`,`active`,`email`, `lang`, `image`)
SELECT id, name, CONCAT(name, 'Nick'),MD5('nightmare'), id, 1, CONCAT(name, '@mydomain.com'), 'en', '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd'
INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `password`,`role`,`active`,`email`, `lang`, `image`, `bcryptPassword`)
SELECT id, name, CONCAT(name, 'Nick'),MD5('nightmare'), id, 1, CONCAT(name, '@mydomain.com'), 'en', '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2'
FROM `account`.`role` WHERE id <> 20
ORDER BY id;
@ -146,7 +150,9 @@ INSERT INTO `vn`.`warehouse`(`id`, `name`, `code`, `isComparative`, `isInventory
(3, 'Warehouse Three', NULL, 1, 1, 1, 1, 0, 0, 2, 1, 1),
(4, 'Warehouse Four', NULL, 1, 1, 1, 1, 0, 0, 2, 1, 1),
(5, 'Warehouse Five', NULL, 1, 1, 1, 1, 0, 0, 2, 1, 1),
(13, 'Inventory', NULL, 1, 1, 1, 0, 0, 0, 2, 1, 0);
(13, 'Inventory', NULL, 1, 1, 1, 0, 0, 0, 2, 1, 0),
(60, 'Algemesi', NULL, 1, 1, 1, 0, 0, 0, 2, 1, 0);
INSERT INTO `vn`.`sector`(`id`, `description`, `warehouseFk`, `isPreviousPreparedByPacking`, `code`)
VALUES
@ -795,14 +801,14 @@ INSERT INTO `vn`.`temperature`(`code`, `name`, `description`)
('warm', 'Warm', 'Warm'),
('cool', 'Cool', 'Cool');
INSERT INTO `vn`.`itemType`(`id`, `code`, `name`, `categoryFk`, `warehouseFk`, `life`,`workerFk`, `isPackaging`, `temperatureFk`)
INSERT INTO `vn`.`itemType`(`id`, `code`, `name`, `categoryFk`, `life`, `workerFk`, `isPackaging`, `temperatureFk`)
VALUES
(1, 'CRI', 'Crisantemo', 2, 1, 31, 35, 0, 'cool'),
(2, 'ITG', 'Anthurium', 1, 1, 31, 35, 0, 'cool'),
(3, 'WPN', 'Paniculata', 2, 1, 31, 35, 0, 'cool'),
(4, 'PRT', 'Delivery ports', 3, 1, NULL, 35, 1, 'warm'),
(5, 'CON', 'Container', 3, 1, NULL, 35, 1, 'warm'),
(6, 'ALS', 'Alstroemeria', 1, 1, 31, 16, 0, 'warm');
(1, 'CRI', 'Crisantemo', 2, 31, 35, 0, 'cool'),
(2, 'ITG', 'Anthurium', 1, 31, 35, 0, 'cool'),
(3, 'WPN', 'Paniculata', 2, 31, 35, 0, 'cool'),
(4, 'PRT', 'Delivery ports', 3, NULL, 35, 1, 'warm'),
(5, 'CON', 'Container', 3, NULL, 35, 1, 'warm'),
(6, 'ALS', 'Alstroemeria', 1, 31, 16, 0, 'warm');
INSERT INTO `vn`.`ink`(`id`, `name`, `picture`, `showOrder`, `hex`)
VALUES
@ -860,25 +866,25 @@ INSERT INTO `vn`.`itemFamily`(`code`, `description`)
('VT', 'Sales');
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `expenceFk`,
`comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`, `isFloramondo`, `genericFk`, `itemPackingTypeFk`, `hasMinPrice`, `packingShelve`)
`comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`, `isFloramondo`, `genericFk`, `itemPackingTypeFk`, `hasMinPrice`, `packingShelve`, `weightByPiece`)
VALUES
(1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'VT', 0, NULL, 'V', 0, 15),
(2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '2', NULL, 0, 2, 'VT', 0, NULL, 'H', 0, 10),
(3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '3', NULL, 0, 5, 'VT', 0, NULL, NULL, 0, 5),
(4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 4751000000, NULL, 0, '4', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL),
(5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '5', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL),
(6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '6', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL),
(7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '7', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL),
(8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '8', NULL, 0, 5, 'VT', 0, NULL, NULL, 0, NULL),
(9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '9', NULL, 0, 4, 'VT', 1, NULL, NULL, 0, NULL),
(10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '10', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL),
(11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 4751000000, NULL, 0, '11', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL),
(12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '12', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL),
(13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '13', NULL, 1, 2, 'VT', 1, NULL, NULL, 1, NULL),
(14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 4, 'VT', 1, NULL, NULL, 0, NULL),
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL, NULL, 0, NULL),
(16, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL, NULL, 0, NULL),
(71, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'VT', 0, NULL, NULL, 0, NULL);
(1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'VT', 0, NULL, 'V', 0, 15,3),
(2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '2', NULL, 0, 2, 'VT', 0, NULL, 'H', 0, 10,2),
(3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '3', NULL, 0, 5, 'VT', 0, NULL, NULL, 0, 5,5),
(4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 4751000000, NULL, 0, '4', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '5', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '6', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '7', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '8', NULL, 0, 5, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '9', NULL, 0, 4, 'VT', 1, NULL, NULL, 0, NULL,NULL),
(10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '10', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 4751000000, NULL, 0, '11', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '12', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL,NULL),
(13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '13', NULL, 1, 2, 'VT', 1, NULL, NULL, 1, NULL,NULL),
(14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 4, 'VT', 1, NULL, NULL, 0, NULL,NULL),
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL, NULL, 0, NULL,NULL),
(16, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL, NULL, 0, NULL,NULL),
(71, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'VT', 0, NULL, NULL, 0, NULL,NULL);
-- Update the taxClass after insert of the items
UPDATE `vn`.`itemTaxCountry` SET `taxClassFk` = 2
@ -912,18 +918,21 @@ INSERT INTO `vn`.`expeditionStateType`(`id`, `description`, `code`)
(3, 'Perdida', 'LOST');
INSERT INTO `vn`.`expedition`(`id`, `agencyModeFk`, `ticketFk`, `isBox`, `created`, `itemFk`, `counter`, `workerFk`, `externalId`, `packagingFk`, `stateTypeFk`)
INSERT INTO `vn`.`expedition`(`id`, `agencyModeFk`, `ticketFk`, `isBox`, `created`, `itemFk`, `counter`, `workerFk`, `externalId`, `packagingFk`, `stateTypeFk`, `hostFk`)
VALUES
(1, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 15, 1, 18, 'UR9000006041', 94, 1),
(2, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 16, 2, 18, 'UR9000006041', 94, 1),
(3, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 3, 18, 'UR9000006041', 94, 2),
(4, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 4, 18, 'UR9000006041', 94, 2),
(5, 1, 2, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 1, 18, NULL, 94, 3),
(6, 7, 3, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), NULL, 1, 18, NULL, 94, 3),
(7, 2, 4, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH), NULL, 1, 18, NULL, 94, NULL),
(8, 3, 5, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH), NULL, 1, 18, NULL, 94, 1),
(9, 3, 6, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 1, 18, NULL, 94, 2),
(10, 7, 7, 71, NOW(), NULL, 1, 18, NULL, 94, 3);
(1, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 15, 1, 18, 'UR9000006041', 94, 1, 'pc1'),
(2, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 16, 2, 18, 'UR9000006041', 94, 1, NULL),
(3, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 3, 18, 'UR9000006041', 94, 2, NULL),
(4, 1, 1, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 4, 18, 'UR9000006041', 94, 2, NULL),
(5, 1, 2, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 1, 18, NULL, 94, 3, NULL),
(6, 7, 3, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), NULL, 1, 18, NULL, 94, 3, NULL),
(7, 2, 4, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH), NULL, 1, 18, NULL, 94, NULL,NULL),
(8, 3, 5, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH), NULL, 1, 18, NULL, 94, 1, NULL),
(9, 3, 6, 71, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), NULL, 1, 18, NULL, 94, 2, NULL),
(10, 7, 7, 71, NOW(), NULL, 1, 18, NULL, 94, 3, NULL),
(11, 7, 8, 71, NOW(), NULL, 1, 18, NULL, 94, 3, NULL),
(12, 7, 9, 71, NOW(), NULL, 1, 18, NULL, 94, 3, NULL),
(13, 1, 10,71, NOW(), NULL, 1, 18, NULL, 94, 3, NULL);
INSERT INTO `vn`.`expeditionState`(`id`, `created`, `expeditionFk`, `typeFk`, `userFk`)
@ -1371,13 +1380,6 @@ INSERT INTO `vn`.`entry`(`id`, `supplierFk`, `created`, `travelFk`, `isConfirmed
(7, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 7, 0, 442, 'Movement 7', 0, 0, 'this is the note seven', 'observation seven'),
(8, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 7, 0, 442, 'Movement 8', 1, 1, '', '');
INSERT INTO `vn`.`claimRatio`(`clientFk`, `yearSale`, `claimAmount`, `claimingRate`, `priceIncreasing`, `packingRate`)
VALUES
(1101, 500, NULL, 0.00, 0.00, 1.00),
(1102, 1000, 2.00, 0.01, 0.05, 1.00),
(1103, 2000, 0.00, 0.00, 0.02, 1.00),
(1104, 2500, 150.00, 0.02, 0.10, 1.00);
INSERT INTO `bs`.`waste`(`buyer`, `year`, `week`, `family`, `itemFk`, `itemTypeFk`, `saleTotal`, `saleWaste`, `rate`)
VALUES
('CharlesXavier', YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK), 1), 'Carnation', 1, 1, '1062', '51', '4.8'),
@ -1734,12 +1736,12 @@ INSERT INTO `vn`.`claimState`(`id`, `code`, `description`, `roleFk`, `priority`,
( 6, 'mana', 'Mana', 1, 4, 0),
( 7, 'lack', 'Faltas', 1, 2, 0);
INSERT INTO `vn`.`claim`(`id`, `ticketCreated`, `claimStateFk`, `clientFk`, `workerFk`, `responsibility`, `isChargedToMana`, `created`, `packages`)
INSERT INTO `vn`.`claim`(`id`, `ticketCreated`, `claimStateFk`, `clientFk`, `workerFk`, `responsibility`, `isChargedToMana`, `created`, `packages`, `rma`)
VALUES
(1, util.VN_CURDATE(), 1, 1101, 18, 3, 0, util.VN_CURDATE(), 0),
(2, util.VN_CURDATE(), 2, 1101, 18, 3, 0, util.VN_CURDATE(), 1),
(3, util.VN_CURDATE(), 3, 1101, 18, 1, 1, util.VN_CURDATE(), 5),
(4, util.VN_CURDATE(), 3, 1104, 18, 5, 0, util.VN_CURDATE(), 10);
(1, util.VN_CURDATE(), 1, 1101, 18, 3, 0, util.VN_CURDATE(), 0, '02676A049183'),
(2, util.VN_CURDATE(), 2, 1101, 18, 3, 0, util.VN_CURDATE(), 1, NULL),
(3, util.VN_CURDATE(), 3, 1101, 18, 1, 1, util.VN_CURDATE(), 5, NULL),
(4, util.VN_CURDATE(), 3, 1104, 18, 5, 0, util.VN_CURDATE(), 10, NULL);
INSERT INTO `vn`.`claimObservation` (`claimFk`, `workerFk`, `text`, `created`)
VALUES
@ -1776,6 +1778,28 @@ INSERT INTO `vn`.`claimEnd`(`id`, `saleFk`, `claimFk`, `workerFk`, `claimDestina
(1, 31, 4, 21, 2),
(2, 32, 3, 21, 3);
INSERT INTO `vn`.`claimConfig`(`id`, `pickupContact`, `maxResponsibility`)
VALUES
(1, 'Contact description', 50),
(2, 'Contact description', 30);
INSERT INTO `vn`.`claimRatio`(`clientFk`, `yearSale`, `claimAmount`, `claimingRate`, `priceIncreasing`, `packingRate`)
VALUES
(1101, 500, NULL, 0.00, 0.00, 1.00),
(1102, 1000, 2.00, 0.01, 0.05, 1.00),
(1103, 2000, 0.00, 0.00, 0.02, 1.00),
(1104, 2500, 150.00, 0.02, 0.10, 1.00);
INSERT INTO vn.claimRma (`id`, `code`, `created`, `workerFk`)
VALUES
(1, '02676A049183', DEFAULT, 1106),
(2, '02676A049183', DEFAULT, 1106),
(3, '02676A049183', DEFAULT, 1107),
(4, '02676A049183', DEFAULT, 1107),
(5, '01837B023653', DEFAULT, 1106);
INSERT INTO `hedera`.`tpvMerchant`(`id`, `description`, `companyFk`, `bankFk`, `secretKey`)
VALUES
(1, 'Arkham Bank', 442, 1, 'h12387193H10238'),
@ -2634,6 +2658,39 @@ INSERT INTO `vn`.`workerTimeControlConfig` (`id`, `dayBreak`, `dayBreakDriver`,
VALUES
(1, 43200, 32400, 129600, 259200, 604800, '', '', 'Leidos.exito', 'Leidos.error', 'timeControl', 5.33, 0.33, 40, '22:00:00', '06:00:00', 57600, 1200, 18000, 57600, 6, 13);
INSERT INTO `vn`.`host` (`id`, `code`, `description`, `warehouseFk`, `bankFk`)
VALUES
(1, 'pc1', 'pc host', 1, 1);
INSERT INTO `vn`.`packingSite` (`id`, `code`, `hostFk`, `monitorId`)
VALUES
(1, 'h1', 1, '');
INSERT INTO `vn`.`packingSiteConfig` (`shinobiUrl`, `shinobiToken`, `shinobiGroupKey`, `avgBoxingTime`)
VALUES
('', 'SHINNOBI_TOKEN', 'GROUP_TOKEN', 6000);
INSERT INTO `util`.`notificationConfig`
SET `cleanDays` = 90;
INSERT INTO `util`.`notification` (`id`, `name`, `description`)
VALUES
(1, 'print-email', 'notification fixture one');
INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`)
VALUES
(1, 9);
INSERT INTO `util`.`notificationQueue` (`id`, `notificationFk`, `params`, `authorFk`, `status`, `created`)
VALUES
(1, 'print-email', '{"id": "1"}', 9, 'pending', util.VN_CURDATE()),
(2, 'print-email', '{"id": "2"}', null, 'pending', util.VN_CURDATE()),
(3, 'print-email', null, null, 'pending', util.VN_CURDATE());
INSERT INTO `util`.`notificationSubscription` (`notificationFk`, `userFk`)
VALUES
(1, 1109),
(1, 1110);
INSERT INTO `vn`.`routeConfig` (`id`, `defaultWorkCenterFk`)
VALUES
(1, 9);
@ -2649,3 +2706,7 @@ INSERT INTO `vn`.`collection` (`id`, `created`, `workerFk`, `stateFk`, `itemPack
INSERT INTO `vn`.`ticketCollection` (`ticketFk`, `collectionFk`, `created`, `level`, `wagon`, `smartTagFk`, `usedShelves`, `itemCount`, `liters`)
VALUES
(9, 3, util.VN_NOW(), NULL, 0, NULL, NULL, NULL, NULL);
UPDATE `account`.`user`
SET `hasGrant` = 1
WHERE `id` = 66;

View File

@ -51,14 +51,12 @@ export default {
accountDescriptor: {
menuButton: 'vn-user-descriptor vn-icon-button[icon="more_vert"]',
deleteAccount: '.vn-menu [name="deleteUser"]',
changeRole: '.vn-menu [name="changeRole"]',
setPassword: '.vn-menu [name="setPassword"]',
activateAccount: '.vn-menu [name="enableAccount"]',
activateUser: '.vn-menu [name="activateUser"]',
deactivateUser: '.vn-menu [name="deactivateUser"]',
newPassword: 'vn-textfield[ng-model="$ctrl.newPassword"]',
repeatPassword: 'vn-textfield[ng-model="$ctrl.repeatPassword"]',
newRole: 'vn-autocomplete[ng-model="$ctrl.newRole"]',
activeAccountIcon: 'vn-icon[icon="contact_mail"]',
activeUserIcon: 'vn-icon[icon="icon-disabled"]',
acceptButton: 'button[response="accept"]',
@ -143,6 +141,11 @@ export default {
verifyCert: 'vn-account-samba vn-check[ng-model="$ctrl.config.verifyCert"]',
save: 'vn-account-samba vn-submit'
},
accountPrivileges: {
checkHasGrant: 'vn-user-privileges vn-check[ng-model="$ctrl.user.hasGrant"]',
role: 'vn-user-privileges vn-autocomplete[ng-model="$ctrl.user.roleFk"]',
save: 'vn-user-privileges vn-submit'
},
clientsIndex: {
createClientButton: `vn-float-button`
},
@ -391,7 +394,7 @@ export default {
intrastadCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Intrastat"]',
originCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Origin"]',
buyerCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Buyer"]',
densityCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Density"]',
weightByPieceCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Weight/Piece"]',
saveFieldsButton: '.vn-popover.shown vn-button[label="Save"] > button'
},
itemFixedPrice: {
@ -835,14 +838,16 @@ export default {
confirmButton: '.vn-confirm.shown button[response="accept"]',
},
routeIndex: {
anyResult: 'vn-table a',
firstRouteCheckbox: 'a:nth-child(1) vn-td:nth-child(1) > vn-check',
anyResult: 'vn-route-index tbody > tr',
firstRouteCheckbox: 'vn-route-index tbody > tr:nth-child(1) > td:nth-child(1) > vn-check',
addNewRouteButton: 'vn-route-index a[ui-sref="route.create"]',
cloneButton: 'vn-route-index button > vn-icon[icon="icon-clone"]',
submitClonationButton: 'tpl-buttons > button[response="accept"]',
openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]',
searchAgencyAutocomlete: 'vn-route-search-panel vn-autocomplete[ng-model="filter.agencyModeFk"]',
advancedSearchButton: 'vn-route-search-panel button[type=submit]',
previewButton: 'vn-route-index tbody > tr:nth-child(7) > td:nth-child(11) > vn-icon-button[icon="preview"]',
},
createRouteView: {
worker: 'vn-route-create vn-autocomplete[ng-model="$ctrl.route.workerFk"]',
@ -862,6 +867,8 @@ export default {
firstTicketDescriptor: '.vn-popover.shown vn-ticket-descriptor',
firstAlias: 'vn-route-summary vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(3) > span',
firstClientDescriptor: '.vn-popover.shown vn-client-descriptor',
goToRouteSummaryButton: 'vn-route-summary > vn-card > h5 > a',
},
routeBasicData: {
worker: 'vn-route-basic-data vn-autocomplete[ng-model="$ctrl.route.workerFk"]',
@ -1014,9 +1021,9 @@ export default {
save: 'vn-travel-create vn-submit > button'
},
travelExtraCommunity: {
anySearchResult: 'vn-travel-extra-community > vn-data-viewer div > vn-tbody > vn-tr',
firstTravelReference: 'vn-travel-extra-community vn-tbody:nth-child(2) vn-td-editable[name="reference"]',
firstTravelLockedKg: 'vn-travel-extra-community vn-tbody:nth-child(2) vn-td-editable[name="lockedKg"]',
anySearchResult: 'vn-travel-extra-community > vn-card div > tbody > tr[ng-attr-id="{{::travel.id}}"]',
firstTravelReference: 'vn-travel-extra-community tbody:nth-child(2) vn-td-editable[name="reference"]',
firstTravelLockedKg: 'vn-travel-extra-community tbody:nth-child(2) vn-td-editable[name="lockedKg"]',
removeContinentFilter: 'vn-searchbar > form > vn-textfield > div.container > div.prepend > prepend > div > span:nth-child(3) > vn-icon > i'
},
travelBasicData: {

View File

@ -8,7 +8,7 @@ describe('Client Edit web access path', () => {
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('employee', 'client');
await page.loginAndModule('salesPerson', 'client');
await page.accessToSearchResult('max');
await page.accessToSection('client.card.webAccess');
});

View File

@ -31,7 +31,7 @@ describe('Item index path', () => {
await page.waitToClick(selectors.itemsIndex.intrastadCheckbox);
await page.waitToClick(selectors.itemsIndex.originCheckbox);
await page.waitToClick(selectors.itemsIndex.buyerCheckbox);
await page.waitToClick(selectors.itemsIndex.densityCheckbox);
await page.waitToClick(selectors.itemsIndex.weightByPieceCheckbox);
await page.waitToClick(selectors.itemsIndex.saveFieldsButton);
const message = await page.waitForSnackbar();
@ -64,7 +64,7 @@ describe('Item index path', () => {
await page.waitToClick(selectors.itemsIndex.intrastadCheckbox);
await page.waitToClick(selectors.itemsIndex.originCheckbox);
await page.waitToClick(selectors.itemsIndex.buyerCheckbox);
await page.waitToClick(selectors.itemsIndex.densityCheckbox);
await page.waitToClick(selectors.itemsIndex.weightByPieceCheckbox);
await page.waitToClick(selectors.itemsIndex.saveFieldsButton);
const message = await page.waitForSnackbar();

View File

@ -9,7 +9,8 @@ describe('Route summary path', () => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('employee', 'route');
await page.waitToClick('vn-route-index vn-tbody > a:nth-child(7)');
await page.waitToClick(selectors.routeIndex.previewButton);
await page.waitToClick(selectors.routeSummary.goToRouteSummaryButton);
});
afterAll(async() => {
@ -34,6 +35,8 @@ describe('Route summary path', () => {
});
it('should click on the first ticket ID making the descriptor popover visible', async() => {
await page.waitForState('route.card.summary');
await page.waitForTimeout(250);
await page.waitToClick(selectors.routeSummary.firstTicketID);
await page.waitForSelector(selectors.routeSummary.firstTicketDescriptor);
const visible = await page.isVisible(selectors.routeSummary.firstTicketDescriptor);

View File

@ -22,6 +22,10 @@ describe('Travel extra community path', () => {
await page.writeOnEditableTD(selectors.travelExtraCommunity.firstTravelReference, 'edited reference');
await page.waitForSpinnerLoad();
await page.writeOnEditableTD(selectors.travelExtraCommunity.firstTravelLockedKg, '1500');
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should reload the index and confirm the reference and locked kg were edited', async() => {

View File

@ -31,7 +31,7 @@ describe('Supplier fiscal data path', () => {
await page.clearInput(selectors.supplierFiscalData.taxNumber);
await page.write(selectors.supplierFiscalData.taxNumber, 'Wrong tax number');
await page.clearInput(selectors.supplierFiscalData.account);
await page.write(selectors.supplierFiscalData.account, 'edited account number');
await page.write(selectors.supplierFiscalData.account, '0123456789');
await page.autocompleteSearch(selectors.supplierFiscalData.sageWihholding, 'retencion estimacion objetiva');
await page.autocompleteSearch(selectors.supplierFiscalData.sageTaxType, 'operaciones no sujetas');
@ -70,7 +70,7 @@ describe('Supplier fiscal data path', () => {
it('should check the account was edited', async() => {
const result = await page.waitToGetProperty(selectors.supplierFiscalData.account, 'value');
expect(result).toEqual('edited account number');
expect(result).toEqual('0123456789');
});
it('should check the sageWihholding was edited', async() => {

View File

@ -62,27 +62,6 @@ describe('Account create and basic data path', () => {
});
describe('Descriptor option', () => {
describe('Edit role', () => {
it('should edit the role using the descriptor menu', async() => {
await page.waitToClick(selectors.accountDescriptor.menuButton);
await page.waitToClick(selectors.accountDescriptor.changeRole);
await page.autocompleteSearch(selectors.accountDescriptor.newRole, 'adminBoss');
await page.waitToClick(selectors.accountDescriptor.acceptButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Role changed succesfully!');
});
it('should reload the roles section to see now there are more roles', async() => {
// when role updates db takes time to return changes, without this timeout the result would have been 3
await page.waitForTimeout(1000);
await page.reloadSection('account.card.roles');
const rolesCount = await page.countElement(selectors.accountRoles.anyResult);
expect(rolesCount).toEqual(61);
});
});
describe('activate account', () => {
it(`should check the active account icon isn't present in the descriptor`, async() => {
await page.waitForNumberOfElements(selectors.accountDescriptor.activeAccountIcon, 0);

View File

@ -29,4 +29,13 @@ describe('Account LDAP path', () => {
expect(message.text).toContain('Data saved!');
});
it('should reset data', async() => {
await page.waitToClick(selectors.accountLdap.checkEnable);
await page.waitToClick(selectors.accountLdap.save);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
});

View File

@ -29,4 +29,13 @@ describe('Account Samba path', () => {
expect(message.text).toContain('Data saved!');
});
it('should reset data', async() => {
await page.waitToClick(selectors.accountSamba.checkEnable);
await page.waitToClick(selectors.accountSamba.save);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
});

View File

@ -0,0 +1,112 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Account privileges path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('developer', 'account');
await page.accessToSearchResult('1101');
await page.accessToSection('account.card.privileges');
});
afterAll(async() => {
await browser.close();
});
describe('as developer', () => {
it('should throw error when give privileges', async() => {
await page.waitToClick(selectors.accountPrivileges.checkHasGrant);
await page.waitToClick(selectors.accountPrivileges.save);
const message = await page.waitForSnackbar();
expect(message.text).toContain(`You don't have grant privilege`);
});
it('should throw error when change role', async() => {
await page.autocompleteSearch(selectors.accountPrivileges.role, 'employee');
await page.waitToClick(selectors.accountPrivileges.save);
const message = await page.waitForSnackbar();
expect(message.text).toContain(`You don't have grant privilege`);
});
});
describe('as sysadmin', () => {
beforeAll(async() => {
await page.loginAndModule('sysadmin', 'account');
await page.accessToSearchResult('9');
await page.accessToSection('account.card.privileges');
});
it('should give privileges', async() => {
await page.waitToClick(selectors.accountPrivileges.checkHasGrant);
await page.waitToClick(selectors.accountPrivileges.save);
const message = await page.waitForSnackbar();
await page.reloadSection('account.card.privileges');
const result = await page.checkboxState(selectors.accountPrivileges.checkHasGrant);
expect(message.text).toContain(`Data saved!`);
expect(result).toBe('checked');
});
it('should throw error when change role and not own role', async() => {
await page.autocompleteSearch(selectors.accountPrivileges.role, 'itBoss');
await page.waitToClick(selectors.accountPrivileges.save);
const message = await page.waitForSnackbar();
expect(message.text).toContain(`You don't own the role and you can't assign it to another user`);
});
it('should change role to employee', async() => {
await page.autocompleteSearch(selectors.accountPrivileges.role, 'employee');
await page.waitToClick(selectors.accountPrivileges.save);
const message = await page.waitForSnackbar();
await page.reloadSection('account.card.privileges');
const result = await page.waitToGetProperty(selectors.accountPrivileges.role, 'value');
expect(message.text).toContain(`Data saved!`);
expect(result).toContain('employee');
});
it('should return role to developer', async() => {
await page.autocompleteSearch(selectors.accountPrivileges.role, 'developer');
await page.waitToClick(selectors.accountPrivileges.save);
const message = await page.waitForSnackbar();
await page.reloadSection('account.card.privileges');
const result = await page.waitToGetProperty(selectors.accountPrivileges.role, 'value');
expect(message.text).toContain(`Data saved!`);
expect(result).toContain('developer');
});
});
describe('as developer again', () => {
it('should remove privileges', async() => {
await page.accessToSearchResult('9');
await page.accessToSection('account.card.privileges');
await page.waitToClick(selectors.accountPrivileges.checkHasGrant);
await page.waitToClick(selectors.accountPrivileges.save);
const message = await page.waitForSnackbar();
expect(message.text).toContain(`Data saved!`);
});
it('should logIn in developer', async() => {
await page.reloadSection('account.card.privileges');
const result = await page.checkboxState(selectors.accountPrivileges.checkHasGrant);
expect(result).toBe('unchecked');
});
});
});

View File

@ -23,12 +23,15 @@ export default class InputTime extends Field {
let date = null;
let value = this.input.value;
if (this.field && !this.modelDate)
this.modelDate = this.field;
if (value) {
let split = value.split(':').map(i => parseInt(i) || null);
date = this.field instanceof Date
? this.field
: new Date(this.field || null);
date = this.modelDate
? new Date(this.modelDate)
: new Date();
date.setHours(split[0], split[1], 0, 0);
}

View File

@ -6,8 +6,98 @@ export default class Textfield extends Field {
super($element, $scope, $compile);
this.buildInput('text');
}
set maxLength(value) {
this.input.maxLength = value;
}
get maxLength() {
return this.input.maxLength;
}
set insertable(value) {
if (this._insertable === value)
return;
if (this._insertable)
this.input.removeEventListener('keypress', this.keyPressListener);
if (value) {
this.keyPressListener = async e => await this.onKeyPress(e);
this.input.addEventListener('keypress', this.keyPressListener);
}
this._insertable = value;
}
get insertable() {
return this._insertable;
}
async onKeyPress(e) {
if (e.key == 'Enter')
return; // If the enter key is pressed dismiss it
let maxLength = this.maxLength;
// Call the function that obtains the current cursor position
let pointerPosition = getCaretPosition(e.target);
// If the cursor position is on the last allowed character,
// prevent any keystroke from doing anything
if (pointerPosition >= maxLength) {
e.preventDefault();
e.stopPropagation();
return;
}
// In case by any ways the input is longer than the max especified size, cut it to it.
let currentArrValue = e.target.value.slice(0, maxLength);
// Transform said input to a array with each character on one position
currentArrValue = currentArrValue.split('');
// Cut the array in 2 parts, one with everything right of the caret,
// and one with everything left to it
let rightToTheCaret = currentArrValue.slice(pointerPosition);
let leftToTheCaret = currentArrValue.slice(0, pointerPosition);
// Remove the first number on the array that was right of the caret
rightToTheCaret.shift();
// The part that was left to the caret is not modified in any way and is put back into the textField
e.target.value = leftToTheCaret.join('');
// Add one millisecond of delay to give the UI time to update,
// so that it detects the changes on the textField
await new Promise(r => setTimeout(r, 1));
// Add the values that should be right to the Caret back in the textField
e.target.value = e.target.value + rightToTheCaret.join('');
// Update the current pointer position so that it moves 1 position to the right
pointerPosition++;
e.target.setSelectionRange(pointerPosition, pointerPosition);
return false;
}
}
function getCaretPosition(targetElement) {
let caretPosition = 0;
if (targetElement.selectionStart || targetElement.selectionStart == 0) // Firefox Support
caretPosition = targetElement.selectionStart;
return caretPosition;
}
ngModule.vnComponent('vnTextfield', {
controller: Textfield
controller: Textfield,
bindings: {
maxLength: '<?',
insertable: '<?'
}
});

View File

@ -54,6 +54,21 @@ export default class App {
localStorage.setItem('salix-version', newVersion);
}
}
getUrl(route, appName = 'lilium') {
const env = process.env.NODE_ENV;
const filter = {
where: {and: [
{appName: appName},
{environment: env}
]}
};
return this.logger.$http.get('Urls/findOne', {filter})
.then(res => {
return res.data.url + route;
});
}
}
ngModule.service('vnApp', App);

View File

@ -10,24 +10,12 @@ class Email {
/**
* Sends an email displaying a notification when it's sent.
*
* @param {String} template The email report name
* @param {String} path The email report name
* @param {Object} params The email parameters
* @return {Promise} Promise resolved when it's sent
*/
send(template, params) {
return this.$http.get(`email/${template}`, {params})
.then(() => this.vnApp.showMessage(this.$t('Notification sent!')));
}
/**
* Sends an email displaying a notification when it's sent.
*
* @param {String} template The email report name
* @param {Object} params The email parameters
* @return {Promise} Promise resolved when it's sent
*/
sendCsv(template, params) {
return this.$http.get(`csv/${template}/send`, {params})
send(path, params) {
return this.$http.post(path, params)
.then(() => this.vnApp.showMessage(this.$t('Notification sent!')));
}
}

View File

@ -10,30 +10,16 @@ class Report {
* Shows a report in another window, automatically adds the authorization
* token to params.
*
* @param {String} report The report name
* @param {String} path The report name
* @param {Object} params The report parameters
*/
show(report, params) {
show(path, params) {
params = Object.assign({
authorization: this.vnToken.token
access_token: this.vnToken.token
}, params);
const serializedParams = this.$httpParamSerializer(params);
window.open(`api/report/${report}?${serializedParams}`);
}
/**
* Shows a report in another window, automatically adds the authorization
* token to params.
*
* @param {String} report The report name
* @param {Object} params The report parameters
*/
showCsv(report, params) {
params = Object.assign({
authorization: this.vnToken.token
}, params);
const serializedParams = this.$httpParamSerializer(params);
window.open(`api/csv/${report}/download?${serializedParams}`);
const query = serializedParams ? `?${serializedParams}` : '';
window.open(`api/${path}${query}`);
}
}
Report.$inject = ['$httpParamSerializer', 'vnToken'];

250
front/package-lock.json generated
View File

@ -1,78 +1,232 @@
{
"name": "salix-front",
"version": "1.0.0",
"lockfileVersion": 1,
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "salix-front",
"version": "1.0.0",
"license": "GPL-3.0",
"dependencies": {
"@uirouter/angularjs": "^1.0.20",
"angular": "^1.7.5",
"angular-animate": "^1.7.8",
"angular-moment": "^1.3.0",
"angular-translate": "^2.18.1",
"angular-translate-loader-partial": "^2.18.1",
"croppie": "^2.6.5",
"js-yaml": "^3.13.1",
"mg-crud": "^1.1.2",
"oclazyload": "^0.6.3",
"require-yaml": "0.0.1",
"validator": "^6.3.0"
}
},
"node_modules/@uirouter/angularjs": {
"version": "1.0.30",
"resolved": "https://registry.npmjs.org/@uirouter/angularjs/-/angularjs-1.0.30.tgz",
"integrity": "sha512-qkc3RFZc91S5K0gc/QVAXc9LGDPXjR04vDgG/11j8+yyZEuQojXxKxdLhKIepiPzqLmGRVqzBmBc27gtqaEeZg==",
"dependencies": {
"@uirouter/core": "6.0.8"
},
"engines": {
"node": ">=4.0.0"
},
"peerDependencies": {
"angular": ">=1.2.0"
}
},
"node_modules/@uirouter/core": {
"version": "6.0.8",
"resolved": "https://registry.npmjs.org/@uirouter/core/-/core-6.0.8.tgz",
"integrity": "sha512-Gc/BAW47i4L54p8dqYCJJZuv2s3tqlXQ0fvl6Zp2xrblELPVfxmjnc0eurx3XwfQdaqm3T6uls6tQKkof/4QMw==",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/angular": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/angular/-/angular-1.8.3.tgz",
"integrity": "sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw==",
"deprecated": "For the actively supported Angular, see https://www.npmjs.com/package/@angular/core. AngularJS support has officially ended. For extended AngularJS support options, see https://goo.gle/angularjs-path-forward."
},
"node_modules/angular-animate": {
"version": "1.8.2",
"license": "MIT"
},
"node_modules/angular-moment": {
"version": "1.3.0",
"license": "MIT",
"dependencies": {
"moment": ">=2.8.0 <3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/angular-translate": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.19.0.tgz",
"integrity": "sha512-Z/Fip5uUT2N85dPQ0sMEe1JdF5AehcDe4tg/9mWXNDVU531emHCg53ZND9Oe0dyNiGX5rWcJKmsL1Fujus1vGQ==",
"dependencies": {
"angular": "^1.8.0"
},
"engines": {
"node": "*"
}
},
"node_modules/angular-translate-loader-partial": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/angular-translate-loader-partial/-/angular-translate-loader-partial-2.19.0.tgz",
"integrity": "sha512-NnMw13LMV4bPQmJK7/pZOZAnPxe0M5OtUHchADs5Gye7V7feonuEnrZ8e1CKhBlv9a7IQyWoqcBa4Lnhg8gk5w==",
"dependencies": {
"angular-translate": "~2.19.0"
}
},
"node_modules/argparse": {
"version": "1.0.10",
"license": "MIT",
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/croppie": {
"version": "2.6.5",
"license": "MIT"
},
"node_modules/esprima": {
"version": "4.0.1",
"license": "BSD-2-Clause",
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/js-yaml": {
"version": "3.14.1",
"license": "MIT",
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/mg-crud": {
"version": "1.1.2",
"license": "MIT",
"dependencies": {
"angular": "^1.6.1"
}
},
"node_modules/moment": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
"engines": {
"node": "*"
}
},
"node_modules/oclazyload": {
"version": "0.6.3",
"license": "MIT"
},
"node_modules/require-yaml": {
"version": "0.0.1",
"license": "BSD",
"dependencies": {
"js-yaml": ""
}
},
"node_modules/require-yaml/node_modules/argparse": {
"version": "2.0.1",
"license": "Python-2.0"
},
"node_modules/require-yaml/node_modules/js-yaml": {
"version": "4.1.0",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"license": "BSD-3-Clause"
},
"node_modules/validator": {
"version": "6.3.0",
"license": "MIT",
"engines": {
"node": ">= 0.10"
}
}
},
"dependencies": {
"@uirouter/angularjs": {
"version": "1.0.29",
"resolved": "https://registry.npmjs.org/@uirouter/angularjs/-/angularjs-1.0.29.tgz",
"integrity": "sha512-RImWnBarNixkMto0o8stEaGwZmvhv5cnuOLXyMU2pY8MP2rgEF74ZNJTLeJCW14LR7XDUxVH8Mk8bPI6lxedmQ==",
"version": "1.0.30",
"resolved": "https://registry.npmjs.org/@uirouter/angularjs/-/angularjs-1.0.30.tgz",
"integrity": "sha512-qkc3RFZc91S5K0gc/QVAXc9LGDPXjR04vDgG/11j8+yyZEuQojXxKxdLhKIepiPzqLmGRVqzBmBc27gtqaEeZg==",
"requires": {
"@uirouter/core": "6.0.7"
"@uirouter/core": "6.0.8"
}
},
"@uirouter/core": {
"version": "6.0.7",
"resolved": "https://registry.npmjs.org/@uirouter/core/-/core-6.0.7.tgz",
"integrity": "sha512-KUTJxL+6q0PiBnFx4/Z+Hsyg0pSGiaW5yZQeJmUxknecjpTbnXkLU8H2EqRn9N2B+qDRa7Jg8RcgeNDPY72O1w=="
"version": "6.0.8",
"resolved": "https://registry.npmjs.org/@uirouter/core/-/core-6.0.8.tgz",
"integrity": "sha512-Gc/BAW47i4L54p8dqYCJJZuv2s3tqlXQ0fvl6Zp2xrblELPVfxmjnc0eurx3XwfQdaqm3T6uls6tQKkof/4QMw=="
},
"angular": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/angular/-/angular-1.8.2.tgz",
"integrity": "sha512-IauMOej2xEe7/7Ennahkbb5qd/HFADiNuLSESz9Q27inmi32zB0lnAsFeLEWcox3Gd1F6YhNd1CP7/9IukJ0Gw=="
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/angular/-/angular-1.8.3.tgz",
"integrity": "sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw=="
},
"angular-animate": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.8.2.tgz",
"integrity": "sha512-Jbr9+grNMs9Kj57xuBU3Ju3NOPAjS1+g2UAwwDv7su1lt0/PLDy+9zEwDiu8C8xJceoTbmBNKiWGPJGBdCQLlA=="
"version": "1.8.2"
},
"angular-moment": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/angular-moment/-/angular-moment-1.3.0.tgz",
"integrity": "sha512-KG8rvO9MoaBLwtGnxTeUveSyNtrL+RNgGl1zqWN36+HDCCVGk2DGWOzqKWB6o+eTTbO3Opn4hupWKIElc8XETA==",
"requires": {
"moment": ">=2.8.0 <3.0.0"
}
},
"angular-translate": {
"version": "2.18.4",
"resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.18.4.tgz",
"integrity": "sha512-KohNrkH6J9PK+VW0L/nsRTcg5Fw70Ajwwe3Jbfm54Pf9u9Fd+wuingoKv+h45mKf38eT+Ouu51FPua8VmZNoCw==",
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.19.0.tgz",
"integrity": "sha512-Z/Fip5uUT2N85dPQ0sMEe1JdF5AehcDe4tg/9mWXNDVU531emHCg53ZND9Oe0dyNiGX5rWcJKmsL1Fujus1vGQ==",
"requires": {
"angular": "^1.8.0"
}
},
"angular-translate-loader-partial": {
"version": "2.18.4",
"resolved": "https://registry.npmjs.org/angular-translate-loader-partial/-/angular-translate-loader-partial-2.18.4.tgz",
"integrity": "sha512-bsjR+FbB0sdA2528E/ugwKdlPPQhA1looxLxI3otayBTFXBpED33besfSZhYAISLgNMSL038vSssfRUen9qD8w==",
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/angular-translate-loader-partial/-/angular-translate-loader-partial-2.19.0.tgz",
"integrity": "sha512-NnMw13LMV4bPQmJK7/pZOZAnPxe0M5OtUHchADs5Gye7V7feonuEnrZ8e1CKhBlv9a7IQyWoqcBa4Lnhg8gk5w==",
"requires": {
"angular-translate": "~2.18.4"
"angular-translate": "~2.19.0"
}
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "~1.0.2"
}
},
"croppie": {
"version": "2.6.5",
"resolved": "https://registry.npmjs.org/croppie/-/croppie-2.6.5.tgz",
"integrity": "sha512-IlChnVUGG5T3w2gRZIaQgBtlvyuYnlUWs2YZIXXR3H9KrlO1PtBT3j+ykxvy9eZIWhk+V5SpBmhCQz5UXKrEKQ=="
"version": "2.6.5"
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
"version": "4.0.1"
},
"js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
@ -80,39 +234,29 @@
},
"mg-crud": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/mg-crud/-/mg-crud-1.1.2.tgz",
"integrity": "sha1-p6AWGzWSPK7/8ZpIBpS2V1vDggw=",
"requires": {
"angular": "^1.6.1"
}
},
"moment": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
},
"oclazyload": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/oclazyload/-/oclazyload-0.6.3.tgz",
"integrity": "sha1-Kjirv/QJDAihEBZxkZRbWfLoJ5w="
"version": "0.6.3"
},
"require-yaml": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/require-yaml/-/require-yaml-0.0.1.tgz",
"integrity": "sha1-LhsY2RPDuqcqWk03O28Tjd0sMr0=",
"requires": {
"js-yaml": "^4.1.0"
"js-yaml": ""
},
"dependencies": {
"argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
"version": "2.0.1"
},
"js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"requires": {
"argparse": "^2.0.1"
}
@ -120,14 +264,10 @@
}
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
"version": "1.0.3"
},
"validator": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-6.3.0.tgz",
"integrity": "sha1-R84j7Y1Ord+p1LjvAHG2zxB418g="
"version": "6.3.0"
}
}
}

View File

@ -131,5 +131,9 @@
"Fichadas impares": "Odd signs",
"Descanso diario 9h.": "Daily rest 9h.",
"Descanso semanal 36h. / 72h.": "Weekly rest 36h. / 72h.",
"Password does not meet requirements": "Password does not meet requirements"
}
"Password does not meet requirements": "Password does not meet requirements",
"You don't have privileges to change the zone": "You don't have privileges to change the zone or for these parameters there are more than one shipping options, talk to agencies",
"Not enough privileges to edit a client": "Not enough privileges to edit a client",
"You don't have grant privilege": "You don't have grant privilege",
"You don't own the role and you can't assign it to another user": "You don't own the role and you can't assign it to another user"
}

View File

@ -96,7 +96,7 @@
"This postcode already exists": "Este código postal ya existe",
"Concept cannot be blank": "El concepto no puede quedar en blanco",
"File doesn't exists": "El archivo no existe",
"You don't have privileges to change the zone or for these parameters there are more than one shipping options, talk to agencies": "No tienes permisos para cambiar la zona o para esos parámetros hay más de una opción de envío, hable con las agencias",
"You don't have privileges to change the zone": "No tienes permisos para cambiar la zona o para esos parámetros hay más de una opción de envío, hable con las agencias",
"This ticket is already on weekly tickets": "Este ticket ya está en tickets programados",
"Ticket id cannot be blank": "El id de ticket no puede quedar en blanco",
"Weekday cannot be blank": "El día de la semana no puede quedar en blanco",
@ -208,7 +208,7 @@
"Wasn't able to invoice the following clients": "No se han podido facturar los siguientes clientes",
"Can't verify data unless the client has a business type": "No se puede verificar datos de un cliente que no tiene tipo de negocio",
"You don't have enough privileges to set this credit amount": "No tienes suficientes privilegios para establecer esta cantidad de crédito",
"You can't change the credit set to zero from a manager": "No puedes cambiar el cŕedito establecido a cero por un gerente",
"You can't change the credit set to zero from a financialBoss": "No puedes cambiar el cŕedito establecido a cero por un jefe de finanzas",
"Amounts do not match": "Las cantidades no coinciden",
"The PDF document does not exists": "El documento PDF no existe. Prueba a regenerarlo desde la opción 'Regenerar PDF factura'",
"The type of business must be filled in basic data": "El tipo de negocio debe estar rellenado en datos básicos",
@ -232,5 +232,10 @@
"Fichadas impares": "Fichadas impares",
"Descanso diario 12h.": "Descanso diario 12h.",
"Descanso semanal 36h. / 72h.": "Descanso semanal 36h. / 72h.",
"Dirección incorrecta": "Dirección incorrecta"
"Dirección incorrecta": "Dirección incorrecta",
"Modifiable user details only by an administrator": "Detalles de usuario modificables solo por un administrador",
"Modifiable password only via recovery or by an administrator": "Contraseña modificable solo a través de la recuperación o por un administrador",
"Not enough privileges to edit a client": "No tienes suficientes privilegios para editar un cliente",
"You don't have grant privilege": "No tienes privilegios para dar privilegios",
"You don't own the role and you can't assign it to another user": "No eres el propietario del rol y no puedes asignarlo a otro usuario"
}

View File

@ -1,3 +1,3 @@
module.exports = function(app) {
require('../../../print/boot.js')(app);
require('vn-print').boot(app);
};

View File

@ -40,6 +40,7 @@
"image/png",
"image/jpeg",
"image/jpg",
"image/webp",
"video/mp4"
]
},
@ -60,7 +61,8 @@
"multipart/x-zip",
"image/png",
"image/jpeg",
"image/jpg"
"image/jpg",
"image/webp"
]
},
"imageStorage": {
@ -72,7 +74,8 @@
"allowedContentTypes": [
"image/png",
"image/jpeg",
"image/jpg"
"image/jpg",
"image/webp"
]
},
"invoiceStorage": {
@ -96,6 +99,7 @@
"image/png",
"image/jpeg",
"image/jpg",
"image/webp",
"video/mp4"
]
},

View File

@ -1,3 +1,9 @@
/**
* Transforms an object to a raw data CSV file.
*
* @param {Object} rows Data
* @return {String} Formatted CSV data
*/
function toCSV(rows) {
const [columns] = rows;
let content = Object.keys(columns).join('\t');

View File

@ -11,14 +11,6 @@
translate>
Delete
</vn-item>
<vn-item
ng-click="$ctrl.onChangeRole()"
name="changeRole"
vn-acl="hr"
vn-acl-action="remove"
translate>
Change role
</vn-item>
<vn-item
ng-if="::$root.user.id == $ctrl.id"
ng-click="$ctrl.onChangePassClick(true)"
@ -79,7 +71,7 @@
label="User"
value="{{$ctrl.user.name}}">
</vn-label-value>
<vn-label-value
<vn-label-value
label="Role"
value="{{$ctrl.user.role.name}}">
</vn-label-value>
@ -128,22 +120,6 @@
question="Are you sure you want to continue?"
message="User will be deactivated">
</vn-confirm>
<vn-dialog
vn-id="changeRole"
on-accept="$ctrl.onChangeRoleAccept()">
<tpl-body>
<vn-autocomplete
label="Role"
ng-model="$ctrl.newRole"
url="Roles"
vn-focus>
</vn-autocomplete>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Accept</button>
</tpl-buttons>
</vn-dialog>
<vn-dialog
vn-id="changePass"
on-accept="$ctrl.onPassChange()"
@ -176,4 +152,4 @@
</vn-dialog>
<vn-popup vn-id="summary">
<vn-user-summary user="$ctrl.user"></vn-user-summary>
</vn-popup>
</vn-popup>

View File

@ -30,20 +30,6 @@ class Controller extends Descriptor {
.then(() => this.vnApp.showSuccess(this.$t('User removed')));
}
onChangeRole() {
this.newRole = this.user.role.id;
this.$.changeRole.show();
}
onChangeRoleAccept() {
const params = {roleFk: this.newRole};
return this.$http.patch(`Accounts/${this.id}`, params)
.then(() => {
this.emit('change');
this.vnApp.showSuccess(this.$t('Role changed succesfully!'));
});
}
onChangePassClick(askOldPass) {
this.$http.get('UserPasswords/findOne')
.then(res => {

View File

@ -30,17 +30,6 @@ describe('component vnUserDescriptor', () => {
});
});
describe('onChangeRoleAccept()', () => {
it('should call backend method to change role', () => {
$httpBackend.expectPATCH('Accounts/1').respond();
controller.onChangeRoleAccept();
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
expect(controller.emit).toHaveBeenCalledWith('change');
});
});
describe('onPassChange()', () => {
it('should throw an error when password is empty', () => {
expect(() => {

View File

@ -18,3 +18,4 @@ import './roles';
import './ldap';
import './samba';
import './accounts';
import './privileges';

View File

@ -0,0 +1,42 @@
<mg-ajax path="Accounts/{{post.params.id}}/privileges" options="vnPost"></mg-ajax>
<vn-watcher
vn-id="watcher"
url="Accounts"
data="$ctrl.user"
id-value="$ctrl.$params.id"
form="form"
save="post">
</vn-watcher>
<form
name="form"
ng-submit="watcher.submit()"
class="vn-w-md">
<vn-card class="vn-pa-lg" vn-focus>
<vn-vertical>
<vn-check
label="Has grant"
ng-model="$ctrl.user.hasGrant">
</vn-check>
</vn-vertical>
<vn-vertical
class="vn-mt-md">
<vn-autocomplete
label="Role"
ng-model="$ctrl.user.roleFk"
url="Roles">
</vn-autocomplete>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Save">
</vn-submit>
<vn-button
class="cancel"
label="Undo changes"
disabled="!watcher.dataChanged()"
ng-click="watcher.loadOriginalData()">
</vn-button>
</vn-button-bar>
</form>

View File

@ -0,0 +1,9 @@
import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {}
ngModule.component('vnUserPrivileges', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,2 @@
Privileges: Privilegios
Has grant: Puede delegar privilegios

View File

@ -19,7 +19,8 @@
{"state": "account.card.basicData", "icon": "settings"},
{"state": "account.card.roles", "icon": "group"},
{"state": "account.card.mailForwarding", "icon": "forward"},
{"state": "account.card.aliases", "icon": "email"}
{"state": "account.card.aliases", "icon": "email"},
{"state": "account.card.privileges", "icon": "badge"}
],
"role": [
{"state": "account.role.card.basicData", "icon": "settings"},
@ -99,6 +100,13 @@
"description": "Mail aliases",
"acl": ["marketing", "hr"]
},
{
"url": "/privileges",
"state": "account.card.privileges",
"component": "vn-user-privileges",
"description": "Privileges",
"acl": ["hr"]
},
{
"url": "/role?q",
"state": "account.role",
@ -249,4 +257,4 @@
"acl": ["developer"]
}
]
}
}

View File

@ -0,0 +1,59 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('claimPickupEmail', {
description: 'Sends the the claim pickup order email with an attached PDF',
accessType: 'WRITE',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipient',
type: 'string',
description: 'The recipient email',
required: true,
},
{
arg: 'replyTo',
type: 'string',
description: 'The sender email to reply to',
required: false
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id to send to the recipient preferred language',
required: false
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: '/:id/claim-pickup-email',
verb: 'POST'
}
});
Self.claimPickupEmail = async ctx => {
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('claim-pickup-order', params);
return email.send();
};
};

View File

@ -0,0 +1,55 @@
const { Report } = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('claimPickupPdf', {
description: 'Returns the claim pickup order pdf',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The claim id',
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient 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/claim-pickup-pdf',
verb: 'GET'
}
});
Self.claimPickupPdf = async(ctx, id) => {
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('claim-pickup-order', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
};

View File

@ -47,7 +47,7 @@ module.exports = Self => {
{
relation: 'claimState',
scope: {
fields: ['id', 'description']
fields: ['id', 'code', 'description']
}
},
{

View File

@ -2,6 +2,9 @@
"Claim": {
"dataSource": "vn"
},
"ClaimContainer": {
"dataSource": "claimStorage"
},
"ClaimBeginning": {
"dataSource": "vn"
},
@ -41,7 +44,7 @@
"ClaimObservation": {
"dataSource": "vn"
},
"ClaimContainer": {
"dataSource": "claimStorage"
}
"ClaimRma": {
"dataSource": "vn"
}
}

View File

@ -0,0 +1,9 @@
const LoopBackContext = require('loopback-context');
module.exports = Self => {
Self.observe('before save', async function(ctx) {
const changes = ctx.data || ctx.instance;
const loopBackContext = LoopBackContext.getCurrentContext();
changes.workerFk = loopBackContext.active.accessToken.userId;
});
};

View File

@ -0,0 +1,30 @@
{
"name": "ClaimRma",
"base": "VnModel",
"options": {
"mysql": {
"table": "claimRma"
}
},
"properties": {
"id": {
"id": true,
"type": "number",
"description": "Identifier"
},
"code": {
"type": "string",
"required": true
},
"created": {
"type": "date"
}
},
"relations": {
"worker": {
"type": "belongsTo",
"model": "Worker",
"foreignKey": "workerFk"
}
}
}

View File

@ -9,4 +9,6 @@ module.exports = Self => {
require('../methods/claim/isEditable')(Self);
require('../methods/claim/updateClaimDestination')(Self);
require('../methods/claim/downloadFile')(Self);
require('../methods/claim/claimPickupPdf')(Self);
require('../methods/claim/claimPickupEmail')(Self);
};

View File

@ -46,6 +46,9 @@
},
"packages": {
"type": "number"
},
"rma": {
"type": "string"
}
},
"relations": {
@ -54,6 +57,12 @@
"model": "ClaimState",
"foreignKey": "claimStateFk"
},
"rmas": {
"type": "hasMany",
"model": "ClaimRma",
"foreignKey": "code",
"primaryKey": "rma"
},
"client": {
"type": "belongsTo",
"model": "Client",

View File

@ -2,36 +2,36 @@
url="ClaimEnds/filter"
link="{claimFk: $ctrl.$params.id}"
data="$ctrl.salesClaimed"
auto-load="true"
auto-load="true"
on-save="$ctrl.onSave()">
</vn-crud-model>
<vn-crud-model
<vn-crud-model
auto-load="true"
url="ClaimDestinations"
data="claimDestinations">
</vn-crud-model>
<vn-card class="vn-mb-md vn-pa-lg vn-w-lg" style="text-align: right"
<vn-card class="vn-mb-md vn-pa-lg vn-w-lg" style="text-align: right"
ng-if="$ctrl.salesClaimed.length > 0">
<vn-label-value label="Total claimed"
<vn-label-value label="Total claimed"
value="{{$ctrl.claimedTotal | currency: 'EUR':2}}">
</vn-label-value>
</vn-card>
<vn-card class="vn-pa-lg vn-w-lg">
<smart-table
model="model"
options="$ctrl.smartTableOptions"
<smart-table
model="model"
options="$ctrl.smartTableOptions"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-actions>
<section class="header">
<vn-tool-bar class="vn-mb-md">
<vn-button
label="Import claim"
label="Import claim"
disabled="$ctrl.claim.claimStateFk == $ctrl.resolvedStateId"
vn-http-click="$ctrl.importToNewRefundTicket()"
translate-attr="{title: 'Imports claim details'}">
</vn-button>
<vn-button
label="Change destination"
label="Change destination"
disabled="$ctrl.checked.length == 0"
ng-click="changeDestination.show()">
</vn-button>
@ -47,7 +47,7 @@
</vn-range>
</vn-tool-bar>
<vn-check class="right"
vn-one
vn-one
label="Is paid with mana"
ng-model="$ctrl.claim.isChargedToMana"
on-change="$ctrl.save({isChargedToMana: value})">
@ -66,12 +66,24 @@
</th>
<th number field="itemFk">Id</th>
<th number field="ticketFk">Ticket</th>
<th field="claimDestinationFk">Destination</th>
<th expand field="landed">Landed</th>
<th number field="quantity">Quantity</th>
<th field="concept">Description</th>
<th number field="price">Price</th>
<th number field="discount">Disc.</th>
<th field="claimDestinationFk">
<span translate>Destination</span>
</th>
<th expand field="landed">
<span translate>Landed</span>
</th>
<th number field="quantity">
<span translate>Quantity</span>
</th>
<th field="concept">
<span translate>Description</span>
</th>
<th number field="price">
<span translate>Price</span>
</th>
<th number field="discount">
<span translate>Disc.</span>
</th>
<th number field="total">Total</th>
</tr>
</thead>
@ -80,13 +92,13 @@
ng-repeat="saleClaimed in $ctrl.salesClaimed"
vn-repeat-last on-last="$ctrl.focusLastInput()">
<td>
<vn-check
<vn-check
ng-model="saleClaimed.$checked"
vn-click-stop>
</vn-check>
</td>
<td number>
<vn-span
<vn-span
ng-click="itemDescriptor.show($event, saleClaimed.itemFk)"
class="link">
{{::saleClaimed.itemFk | zeroFill:6}}
@ -94,7 +106,7 @@
</td>
<td number>
<vn-span
class="link"
class="link"
ng-click="ticketDescriptor.show($event, saleClaimed.ticketFk)">
{{::saleClaimed.ticketFk}}
</vn-span>
@ -129,7 +141,7 @@
</slot-table>
</smart-table>
<button-bar class="vn-pa-md">
<vn-button
<vn-button
label="Regularize"
disabled="$ctrl.claim.claimStateFk == $ctrl.resolvedStateId"
vn-http-click="$ctrl.regularize()">
@ -140,17 +152,17 @@
vn-id="item-descriptor"
warehouse-fk="$ctrl.vnConfig.warehouseFk">
</vn-item-descriptor-popover>
<vn-ticket-descriptor-popover
<vn-ticket-descriptor-popover
vn-id="ticket-descriptor">
</vn-ticket-descriptor-popover>
<vn-confirm
vn-id="update-greuge"
<vn-confirm
vn-id="update-greuge"
question="Insert greuges on client card"
message="Do you want to insert greuges?"
on-accept="$ctrl.onUpdateGreugeAccept()">
</vn-confirm>
<!-- Dialog of change destionation -->
<!-- Dialog of change destionation -->
<vn-dialog
vn-id="changeDestination"
on-accept="$ctrl.onResponse()">
@ -173,4 +185,4 @@
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Save</button>
</tpl-buttons>
</vn-dialog>
</vn-dialog>

View File

@ -10,6 +10,8 @@
</vn-item>
<vn-item
ng-click="confirmPickupOrder.show()"
vn-acl="salesPerson"
vn-acl-action="remove"
translate>
Send Pickup order
</vn-item>

View File

@ -11,17 +11,15 @@ class Controller extends Descriptor {
}
showPickupOrder() {
this.vnReport.show('claim-pickup-order', {
recipientId: this.claim.clientFk,
claimId: this.claim.id
this.vnReport.show(`Claims/${this.claim.id}/claim-pickup-pdf`, {
recipientId: this.claim.clientFk
});
}
sendPickupOrder() {
return this.vnEmail.send('claim-pickup-order', {
return this.vnEmail.send(`Claims/${this.claim.id}/claim-pickup-email`, {
recipient: this.claim.client.email,
recipientId: this.claim.clientFk,
claimId: this.claim.id
recipientId: this.claim.clientFk
});
}

View File

@ -24,12 +24,13 @@ describe('Item Component vnClaimDescriptor', () => {
window.open = jasmine.createSpy('open');
const params = {
recipientId: claim.clientFk,
claimId: claim.id
recipientId: claim.clientFk
};
controller.showPickupOrder();
expect(controller.vnReport.show).toHaveBeenCalledWith('claim-pickup-order', params);
const expectedPath = `Claims/${claim.id}/claim-pickup-pdf`;
expect(controller.vnReport.show).toHaveBeenCalledWith(expectedPath, params);
});
});
@ -39,12 +40,13 @@ describe('Item Component vnClaimDescriptor', () => {
const params = {
recipient: claim.client.email,
recipientId: claim.clientFk,
claimId: claim.id
recipientId: claim.clientFk
};
controller.sendPickupOrder();
expect(controller.vnEmail.send).toHaveBeenCalledWith('claim-pickup-order', params);
const expectedPath = `Claims/${claim.id}/claim-pickup-email`;
expect(controller.vnEmail.send).toHaveBeenCalledWith(expectedPath, params);
});
});

View File

@ -28,7 +28,7 @@
url="Workers/activeWithRole"
search-function="{firstName: $search}"
value-field="id"
where="{role: {inq: ['salesBoss', 'salesPerson', 'officeBoss']}}"
where="{role: {inq: ['salesTeamBoss', 'salesPerson', 'officeBoss']}}"
label="Salesperson">
<tpl-item>{{firstName}} {{name}}</tpl-item>
</vn-autocomplete>
@ -38,7 +38,7 @@
url="Workers/activeWithRole"
search-function="{firstName: $search}"
value-field="id"
where="{role: {inq: ['salesBoss', 'salesPerson']}}"
where="{role: {inq: ['salesTeamBoss', 'salesPerson']}}"
label="Attended by">
<tpl-item>{{firstName}} {{name}}</tpl-item>
</vn-autocomplete>

View File

@ -0,0 +1,69 @@
const {Email} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('campaignMetricsEmail', {
description: 'Sends the campaign metrics email with an attached PDF',
accessType: 'WRITE',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipient',
type: 'string',
description: 'The recipient email',
required: true,
},
{
arg: 'replyTo',
type: 'string',
description: 'The sender email to reply to',
required: false
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id to send to the recipient preferred language',
required: false
},
{
arg: 'from',
type: 'string',
required: true
},
{
arg: 'to',
type: 'string',
required: true
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: '/:id/campaign-metrics-email',
verb: 'POST'
}
});
Self.campaignMetricsEmail = async ctx => {
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('campaign-metrics', params);
return email.send();
};
};

View File

@ -0,0 +1,66 @@
const {Report} = require('vn-print');
module.exports = Self => {
Self.remoteMethodCtx('campaignMetricsPdf', {
description: 'Returns the campaign metrics pdf',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The client id',
http: {source: 'path'}
},
{
arg: 'recipientId',
type: 'number',
description: 'The recipient id',
required: false
},
{
arg: 'from',
type: 'string',
required: true
},
{
arg: 'to',
type: 'string',
required: true
}
],
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/campaign-metrics-pdf',
verb: 'GET'
}
});
Self.campaignMetricsPdf = async(ctx, id) => {
const args = Object.assign({}, ctx.args);
const params = {lang: ctx.req.getLocale()};
delete args.ctx;
for (const param in args)
params[param] = args[param];
const report = new Report('campaign-metrics', params);
const stream = await report.toPdfStream();
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
};
};

View File

@ -25,10 +25,9 @@ module.exports = Self => {
const client = await Self.app.models.Client.findById(id, myOptions);
const emails = client.email ? client.email.split(',') : null;
const findParams = [];
if (emails.length) {
if (client.email) {
const emails = client.email.split(',');
for (let email of emails)
findParams.push({email: email});
}

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