7489-testToMaster #2515
|
@ -10,5 +10,8 @@
|
||||||
"eslint.format.enable": true,
|
"eslint.format.enable": true,
|
||||||
"[javascript]": {
|
"[javascript]": {
|
||||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||||
}
|
},
|
||||||
|
"cSpell.words": [
|
||||||
|
"salix"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
25
CHANGELOG.md
25
CHANGELOG.md
|
@ -5,21 +5,29 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [2342.01] - 2023-10-19
|
## [2348.01] - 2023-11-30
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
### Changed
|
### Changed
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
## [2340.01] - 2023-10-05
|
## [2346.01] - 2023-11-16
|
||||||
|
|
||||||
|
### Added
|
||||||
|
### Changed
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
|
||||||
|
## [2342.01] - 2023-11-02
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- (Usuarios -> Foto) Se muestra la foto del trabajador
|
- (Usuarios -> Foto) Se muestra la foto del trabajador
|
||||||
|
|
||||||
### Changed
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- (Usuarios -> Historial) Abre el descriptor del usuario correctamente
|
- (Usuarios -> Historial) Abre el descriptor del usuario correctamente
|
||||||
|
|
||||||
|
|
||||||
|
## [2340.01] - 2023-10-05
|
||||||
|
|
||||||
## [2338.01] - 2023-09-21
|
## [2338.01] - 2023-09-21
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
@ -29,17 +37,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
### Changed
|
### Changed
|
||||||
- (Trabajadores -> Calendario) Icono de check arreglado cuando pulsas un tipo de dia
|
- (Trabajadores -> Calendario) Icono de check arreglado cuando pulsas un tipo de dia
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
## [2336.01] - 2023-09-07
|
## [2336.01] - 2023-09-07
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
|
|
||||||
## [2334.01] - 2023-08-24
|
## [2334.01] - 2023-08-24
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -26,15 +26,14 @@ module.exports = Self => {
|
||||||
|
|
||||||
Self.sendCheckingPresence = async(ctx, recipientId, message) => {
|
Self.sendCheckingPresence = async(ctx, recipientId, message) => {
|
||||||
if (!recipientId) return false;
|
if (!recipientId) return false;
|
||||||
|
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
|
|
||||||
const userId = ctx.req.accessToken.userId;
|
const userId = ctx.req.accessToken.userId;
|
||||||
const sender = await models.VnUser.findById(userId, {fields: ['id']});
|
const sender = await models.VnUser.findById(userId, {fields: ['id']});
|
||||||
const recipient = await models.VnUser.findById(recipientId, null);
|
const recipient = await models.VnUser.findById(recipientId, null);
|
||||||
|
|
||||||
// Prevent sending messages to yourself
|
// Prevent sending messages to yourself
|
||||||
if (recipientId == userId) return false;
|
if (recipientId == userId) return false;
|
||||||
|
|
||||||
if (!recipient)
|
if (!recipient)
|
||||||
throw new Error(`Could not send message "${message}" to worker id ${recipientId} from user ${userId}`);
|
throw new Error(`Could not send message "${message}" to worker id ${recipientId} from user ${userId}`);
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ module.exports = Self => {
|
||||||
|
|
||||||
Self.getTickets = async(ctx, id, print, options) => {
|
Self.getTickets = async(ctx, id, print, options) => {
|
||||||
const userId = ctx.req.accessToken.userId;
|
const userId = ctx.req.accessToken.userId;
|
||||||
const origin = ctx.req.headers.origin;
|
const url = await Self.app.models.Url.getUrl();
|
||||||
const $t = ctx.req.__;
|
const $t = ctx.req.__;
|
||||||
const myOptions = {};
|
const myOptions = {};
|
||||||
|
|
||||||
|
@ -36,7 +36,6 @@ module.exports = Self => {
|
||||||
myOptions.userId = userId;
|
myOptions.userId = userId;
|
||||||
|
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
const [tickets] = await Self.rawSql(`CALL vn.collection_getTickets(?)`, [id], myOptions);
|
const [tickets] = await Self.rawSql(`CALL vn.collection_getTickets(?)`, [id], myOptions);
|
||||||
const sales = await Self.rawSql(`
|
const sales = await Self.rawSql(`
|
||||||
SELECT s.ticketFk,
|
SELECT s.ticketFk,
|
||||||
|
@ -86,24 +85,19 @@ module.exports = Self => {
|
||||||
if (tickets && tickets.length) {
|
if (tickets && tickets.length) {
|
||||||
for (const ticket of tickets) {
|
for (const ticket of tickets) {
|
||||||
const ticketId = ticket.ticketFk;
|
const ticketId = ticket.ticketFk;
|
||||||
|
|
||||||
// SEND ROCKET
|
|
||||||
if (ticket.observaciones != '') {
|
if (ticket.observaciones != '') {
|
||||||
for (observation of ticket.observaciones.split(' ')) {
|
for (observation of ticket.observaciones.split(' ')) {
|
||||||
if (['#', '@'].includes(observation.charAt(0))) {
|
if (['#', '@'].includes(observation.charAt(0))) {
|
||||||
promises.push(Self.app.models.Chat.send(ctx, observation,
|
promises.push(Self.app.models.Chat.send(ctx, observation,
|
||||||
$t('The ticket is in preparation', {
|
$t('The ticket is in preparation', {
|
||||||
ticketId: ticketId,
|
ticketId: ticketId,
|
||||||
ticketUrl: `${origin}/#!/ticket/${ticketId}/summary`,
|
ticketUrl: `${url}ticket/${ticketId}/summary`,
|
||||||
salesPersonId: ticket.salesPersonFk
|
salesPersonId: ticket.salesPersonFk
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SET COLLECTION
|
|
||||||
if (sales && sales.length) {
|
if (sales && sales.length) {
|
||||||
// GET BARCODES
|
|
||||||
const barcodes = await Self.rawSql(`
|
const barcodes = await Self.rawSql(`
|
||||||
SELECT s.id saleFk, b.code, c.id
|
SELECT s.id saleFk, b.code, c.id
|
||||||
FROM vn.sale s
|
FROM vn.sale s
|
||||||
|
@ -114,13 +108,10 @@ module.exports = Self => {
|
||||||
WHERE s.ticketFk = ?
|
WHERE s.ticketFk = ?
|
||||||
AND tr.landed >= util.VN_CURDATE() - INTERVAL 1 YEAR`,
|
AND tr.landed >= util.VN_CURDATE() - INTERVAL 1 YEAR`,
|
||||||
[ticketId], myOptions);
|
[ticketId], myOptions);
|
||||||
|
|
||||||
// BINDINGS
|
|
||||||
ticket.sales = [];
|
ticket.sales = [];
|
||||||
for (const sale of sales) {
|
for (const sale of sales) {
|
||||||
if (sale.ticketFk === ticketId) {
|
if (sale.ticketFk === ticketId) {
|
||||||
sale.Barcodes = [];
|
sale.Barcodes = [];
|
||||||
|
|
||||||
if (barcodes && barcodes.length) {
|
if (barcodes && barcodes.length) {
|
||||||
for (const barcode of barcodes) {
|
for (const barcode of barcodes) {
|
||||||
if (barcode.saleFk === sale.saleFk) {
|
if (barcode.saleFk === sale.saleFk) {
|
||||||
|
@ -131,7 +122,6 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ticket.sales.push(sale);
|
ticket.sales.push(sale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,7 +130,6 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
|
||||||
return collection;
|
return collection;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,6 +18,14 @@ describe('setSaleQuantity()', () => {
|
||||||
|
|
||||||
it('should change quantity sale', async() => {
|
it('should change quantity sale', async() => {
|
||||||
const tx = await models.Ticket.beginTransaction({});
|
const tx = await models.Ticket.beginTransaction({});
|
||||||
|
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
|
||||||
|
if (sqlStatement.includes('catalog_calcFromItem')) {
|
||||||
|
sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY
|
||||||
|
SELECT 100 as available;`;
|
||||||
|
params = null;
|
||||||
|
}
|
||||||
|
return models.Ticket.rawSql(sqlStatement, params, options);
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const options = {transaction: tx};
|
const options = {transaction: tx};
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('getList', {
|
||||||
|
description: 'Get list of the available and active notification subscriptions',
|
||||||
|
accessType: 'READ',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'id',
|
||||||
|
type: 'number',
|
||||||
|
description: 'User to modify',
|
||||||
|
http: {source: 'path'}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: 'object',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/:id/getList`,
|
||||||
|
verb: 'GET'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.getList = async(id, options) => {
|
||||||
|
const activeNotificationsMap = new Map();
|
||||||
|
|
||||||
|
const myOptions = {};
|
||||||
|
|
||||||
|
if (typeof options == 'object')
|
||||||
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
const availableNotificationsMap = await Self.getAvailable(id, myOptions);
|
||||||
|
const activeNotifications = await Self.app.models.NotificationSubscription.find({
|
||||||
|
fields: ['id', 'notificationFk'],
|
||||||
|
include: {relation: 'notification'},
|
||||||
|
where: {userFk: id}
|
||||||
|
}, myOptions);
|
||||||
|
|
||||||
|
for (active of activeNotifications) {
|
||||||
|
activeNotificationsMap.set(active.notificationFk, {
|
||||||
|
id: active.id,
|
||||||
|
notificationFk: active.notificationFk,
|
||||||
|
name: active.notification().name,
|
||||||
|
description: active.notification().description,
|
||||||
|
active: true
|
||||||
|
});
|
||||||
|
availableNotificationsMap.delete(active.notificationFk);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
active: [...activeNotificationsMap.entries()],
|
||||||
|
available: [...availableNotificationsMap.entries()]
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,13 @@
|
||||||
|
const models = require('vn-loopback/server/server').models;
|
||||||
|
|
||||||
|
describe('NotificationSubscription getList()', () => {
|
||||||
|
it('should return a list of available and active notifications of a user', async() => {
|
||||||
|
const userId = 9;
|
||||||
|
const {active, available} = await models.NotificationSubscription.getList(userId);
|
||||||
|
const notifications = await models.Notification.find({});
|
||||||
|
const totalAvailable = notifications.length - active.length;
|
||||||
|
|
||||||
|
expect(active.length).toEqual(2);
|
||||||
|
expect(available.length).toEqual(totalAvailable);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,40 @@
|
||||||
|
module.exports = function(Self) {
|
||||||
|
Self.remoteMethod('getByUser', {
|
||||||
|
description: 'returns the starred modules for the current user',
|
||||||
|
accessType: 'READ',
|
||||||
|
accepts: [{
|
||||||
|
arg: 'userId',
|
||||||
|
type: 'number',
|
||||||
|
description: 'The user id',
|
||||||
|
required: true,
|
||||||
|
http: {source: 'path'}
|
||||||
|
}],
|
||||||
|
returns: {
|
||||||
|
type: 'object',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/:userId/get-by-user`,
|
||||||
|
verb: 'GET'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.getByUser = async userId => {
|
||||||
|
const models = Self.app.models;
|
||||||
|
const appNames = ['hedera'];
|
||||||
|
const filter = {
|
||||||
|
fields: ['appName', 'url'],
|
||||||
|
where: {
|
||||||
|
appName: {inq: appNames},
|
||||||
|
environment: process.env.NODE_ENV ?? 'development',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isWorker = await models.Account.findById(userId, {fields: ['id']});
|
||||||
|
if (!isWorker)
|
||||||
|
return models.Url.find(filter);
|
||||||
|
|
||||||
|
appNames.push('salix');
|
||||||
|
return models.Url.find(filter);
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,30 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('getUrl', {
|
||||||
|
description: 'Returns the colling app name',
|
||||||
|
accessType: 'READ',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'app',
|
||||||
|
type: 'string',
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: 'object',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/getUrl`,
|
||||||
|
verb: 'get'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Self.getUrl = async(appName = 'salix') => {
|
||||||
|
const {url} = await Self.app.models.Url.findOne({
|
||||||
|
where: {
|
||||||
|
appName,
|
||||||
|
enviroment: process.env.NODE_ENV || 'development'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,19 @@
|
||||||
|
const {models} = require('vn-loopback/server/server');
|
||||||
|
|
||||||
|
describe('getByUser()', () => {
|
||||||
|
const worker = 1;
|
||||||
|
const notWorker = 2;
|
||||||
|
it(`should return only hedera url if not is worker`, async() => {
|
||||||
|
const urls = await models.Url.getByUser(notWorker);
|
||||||
|
|
||||||
|
expect(urls.length).toEqual(1);
|
||||||
|
expect(urls[0].appName).toEqual('hedera');
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should return more than hedera url`, async() => {
|
||||||
|
const urls = await models.Url.getByUser(worker);
|
||||||
|
|
||||||
|
expect(urls.length).toBeGreaterThan(1);
|
||||||
|
expect(urls.find(url => url.appName == 'salix').appName).toEqual('salix');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,39 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethodCtx('updateUser', {
|
||||||
|
description: 'Update user data',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'id',
|
||||||
|
type: 'integer',
|
||||||
|
description: 'The user id',
|
||||||
|
required: true,
|
||||||
|
http: {source: 'path'}
|
||||||
|
}, {
|
||||||
|
arg: 'name',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The user name',
|
||||||
|
}, {
|
||||||
|
arg: 'nickname',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The user nickname',
|
||||||
|
}, {
|
||||||
|
arg: 'email',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The user email'
|
||||||
|
}, {
|
||||||
|
arg: 'lang',
|
||||||
|
type: 'string',
|
||||||
|
description: 'The user lang'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
http: {
|
||||||
|
path: `/:id/update-user`,
|
||||||
|
verb: 'PATCH'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.updateUser = async(ctx, id, name, nickname, email, lang) => {
|
||||||
|
await Self.userSecurity(ctx, id);
|
||||||
|
await Self.upsertWithWhere({id}, {name, nickname, email, lang});
|
||||||
|
};
|
||||||
|
};
|
|
@ -7,17 +7,14 @@ module.exports = Self => {
|
||||||
|
|
||||||
Self.observe('before save', async function(ctx) {
|
Self.observe('before save', async function(ctx) {
|
||||||
if (!ctx.isNewInstance) return;
|
if (!ctx.isNewInstance) return;
|
||||||
|
|
||||||
let {message} = ctx.instance;
|
let {message} = ctx.instance;
|
||||||
if (!message) return;
|
if (!message) return;
|
||||||
|
|
||||||
const parts = message.match(/(?<=\[)[a-zA-Z0-9_\-+!@#$%^&*()={};':"\\|,.<>/?\s]*(?=])/g);
|
const parts = message.match(/(?<=\[)[a-zA-Z0-9_\-+!@#$%^&*()={};':"\\|,.<>/?\s]*(?=])/g);
|
||||||
if (!parts) return;
|
if (!parts) return;
|
||||||
|
|
||||||
const replacedParts = parts.map(part => {
|
const replacedParts = parts.map(part => {
|
||||||
return part.replace(/[!$%^&*()={};':"\\,.<>/?]/g, '');
|
return part.replace(/[!$%^&*()={};':"\\,.<>/?]/g, '');
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const [index, part] of parts.entries())
|
for (const [index, part] of parts.entries())
|
||||||
message = message.replace(part, replacedParts[index]);
|
message = message.replace(part, replacedParts[index]);
|
||||||
|
|
||||||
|
|
|
@ -1,62 +1,74 @@
|
||||||
const UserError = require('vn-loopback/util/user-error');
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
|
require('../methods/notification/getList')(Self);
|
||||||
|
|
||||||
Self.observe('before save', async function(ctx) {
|
Self.observe('before save', async function(ctx) {
|
||||||
|
await checkModifyPermission(ctx);
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.observe('before delete', async function(ctx) {
|
||||||
|
await checkModifyPermission(ctx);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function checkModifyPermission(ctx) {
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
|
const instance = ctx.instance;
|
||||||
const userId = ctx.options.accessToken.userId;
|
const userId = ctx.options.accessToken.userId;
|
||||||
const user = await ctx.instance.userFk;
|
|
||||||
const modifiedUser = await getUserToModify(null, user, models);
|
|
||||||
|
|
||||||
if (userId != modifiedUser.id && userId != modifiedUser.bossFk)
|
let notificationFk;
|
||||||
throw new UserError('You dont have permission to modify this user');
|
let workerId;
|
||||||
});
|
|
||||||
|
|
||||||
Self.remoteMethod('deleteNotification', {
|
if (instance) {
|
||||||
description: 'Deletes a notification subscription',
|
notificationFk = instance.notificationFk;
|
||||||
accepts: [
|
workerId = instance.userFk;
|
||||||
{
|
} else {
|
||||||
arg: 'ctx',
|
const notificationSubscription = await models.NotificationSubscription.findById(ctx.where.id);
|
||||||
type: 'object',
|
notificationFk = notificationSubscription.notificationFk;
|
||||||
http: {source: 'context'}
|
workerId = notificationSubscription.userFk;
|
||||||
},
|
|
||||||
{
|
|
||||||
arg: 'notificationId',
|
|
||||||
type: 'number',
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
],
|
|
||||||
returns: {
|
|
||||||
type: 'object',
|
|
||||||
root: true
|
|
||||||
},
|
|
||||||
http: {
|
|
||||||
verb: 'POST',
|
|
||||||
path: '/deleteNotification'
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
Self.deleteNotification = async function(ctx, notificationId) {
|
const worker = await models.Worker.findById(workerId, {fields: ['id', 'bossFk']});
|
||||||
const models = Self.app.models;
|
const available = await Self.getAvailable(workerId);
|
||||||
const user = ctx.req.accessToken.userId;
|
const hasAcl = available.has(notificationFk);
|
||||||
const modifiedUser = await getUserToModify(notificationId, null, models);
|
|
||||||
|
|
||||||
if (user != modifiedUser.id && user != modifiedUser.bossFk)
|
if (!hasAcl || (userId != worker.id && userId != worker.bossFk))
|
||||||
throw new UserError('You dont have permission to modify this user');
|
throw new UserError('The notification subscription of this worker cant be modified');
|
||||||
|
|
||||||
await models.NotificationSubscription.destroyById(notificationId);
|
|
||||||
};
|
|
||||||
|
|
||||||
async function getUserToModify(notificationId, userFk, models) {
|
|
||||||
let userToModify = userFk;
|
|
||||||
if (notificationId) {
|
|
||||||
const subscription = await models.NotificationSubscription.findById(notificationId);
|
|
||||||
userToModify = subscription.userFk;
|
|
||||||
}
|
|
||||||
return await models.Worker.findOne({
|
|
||||||
fields: ['id', 'bossFk'],
|
|
||||||
where: {
|
|
||||||
id: userToModify
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Self.getAvailable = async function(userId, options) {
|
||||||
|
const availableNotificationsMap = new Map();
|
||||||
|
const models = Self.app.models;
|
||||||
|
|
||||||
|
const myOptions = {};
|
||||||
|
|
||||||
|
if (typeof options == 'object')
|
||||||
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
const roles = await models.RoleMapping.find({
|
||||||
|
fields: ['roleId'],
|
||||||
|
where: {principalId: userId}
|
||||||
|
}, myOptions);
|
||||||
|
|
||||||
|
const availableNotifications = await models.NotificationAcl.find({
|
||||||
|
fields: ['notificationFk', 'roleFk'],
|
||||||
|
include: {relation: 'notification'},
|
||||||
|
where: {
|
||||||
|
roleFk: {
|
||||||
|
inq: roles.map(role => role.roleId),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}, myOptions);
|
||||||
|
|
||||||
|
for (available of availableNotifications) {
|
||||||
|
availableNotificationsMap.set(available.notificationFk, {
|
||||||
|
id: null,
|
||||||
|
notificationFk: available.notificationFk,
|
||||||
|
name: available.notification().name,
|
||||||
|
description: available.notification().description,
|
||||||
|
active: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return availableNotificationsMap;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,74 +1,126 @@
|
||||||
const models = require('vn-loopback/server/server').models;
|
const models = require('vn-loopback/server/server').models;
|
||||||
|
|
||||||
describe('loopback model NotificationSubscription', () => {
|
describe('loopback model NotificationSubscription', () => {
|
||||||
it('Should fail to delete a notification if the user is not editing itself or a subordinate', async() => {
|
it('should fail to add a notification subscription if the worker doesnt have ACLs', async() => {
|
||||||
const tx = await models.NotificationSubscription.beginTransaction({});
|
const tx = await models.NotificationSubscription.beginTransaction({});
|
||||||
|
let error;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const options = {transaction: tx};
|
const options = {transaction: tx, accessToken: {userId: 9}};
|
||||||
const user = 9;
|
await models.NotificationSubscription.create({notificationFk: 1, userFk: 62}, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error.message).toEqual('The notification subscription of this worker cant be modified');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail to add a notification subscription if the user isnt editing itself or subordinate', async() => {
|
||||||
|
const tx = await models.NotificationSubscription.beginTransaction({});
|
||||||
|
let error;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx, accessToken: {userId: 1}};
|
||||||
|
await models.NotificationSubscription.create({notificationFk: 1, userFk: 9}, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error.message).toEqual('The notification subscription of this worker cant be modified');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail to delete a notification subscription if the user isnt editing itself or subordinate', async() => {
|
||||||
|
const tx = await models.NotificationSubscription.beginTransaction({});
|
||||||
|
let error;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx, accessToken: {userId: 9}};
|
||||||
const notificationSubscriptionId = 2;
|
const notificationSubscriptionId = 2;
|
||||||
const ctx = {req: {accessToken: {userId: user}}};
|
await models.NotificationSubscription.destroyAll({id: notificationSubscriptionId}, options);
|
||||||
const notification = await models.NotificationSubscription.findById(notificationSubscriptionId);
|
|
||||||
|
|
||||||
let error;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await models.NotificationSubscription.deleteNotification(ctx, notification.id, options);
|
|
||||||
} catch (e) {
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error.message).toContain('You dont have permission to modify this user');
|
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
throw e;
|
error = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expect(error.message).toEqual('The notification subscription of this worker cant be modified');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should delete a notification if the user is editing itself', async() => {
|
it('should add a notification subscription if the user is editing itself', async() => {
|
||||||
const tx = await models.NotificationSubscription.beginTransaction({});
|
const tx = await models.NotificationSubscription.beginTransaction({});
|
||||||
|
let error;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const options = {transaction: tx};
|
const options = {transaction: tx, accessToken: {userId: 9}};
|
||||||
const user = 9;
|
await models.NotificationSubscription.create({notificationFk: 2, userFk: 9}, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete a notification subscription if the user is editing itself', async() => {
|
||||||
|
const tx = await models.NotificationSubscription.beginTransaction({});
|
||||||
|
let error;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx, accessToken: {userId: 9}};
|
||||||
|
const notificationSubscriptionId = 6;
|
||||||
|
await models.NotificationSubscription.destroyAll({id: notificationSubscriptionId}, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add a notification subscription if the user is editing a subordinate', async() => {
|
||||||
|
const tx = await models.NotificationSubscription.beginTransaction({});
|
||||||
|
let error;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx, accessToken: {userId: 9}};
|
||||||
|
await models.NotificationSubscription.create({notificationFk: 1, userFk: 5}, options);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete a notification subscription if the user is editing a subordinate', async() => {
|
||||||
|
const tx = await models.NotificationSubscription.beginTransaction({});
|
||||||
|
let error;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx, accessToken: {userId: 19}};
|
||||||
const notificationSubscriptionId = 4;
|
const notificationSubscriptionId = 4;
|
||||||
const ctx = {req: {accessToken: {userId: user}}};
|
await models.NotificationSubscription.destroyAll({id: notificationSubscriptionId}, options);
|
||||||
const notification = await models.NotificationSubscription.findById(notificationSubscriptionId);
|
|
||||||
|
|
||||||
await models.NotificationSubscription.deleteNotification(ctx, notification.id, options);
|
|
||||||
|
|
||||||
const deletedNotification = await models.NotificationSubscription.findById(notificationSubscriptionId);
|
|
||||||
|
|
||||||
expect(deletedNotification).toBeNull();
|
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
throw e;
|
error = e;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
it('Should delete a notification if the user is editing a subordinate', async() => {
|
expect(error).toBeUndefined();
|
||||||
const tx = await models.NotificationSubscription.beginTransaction({});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const options = {transaction: tx};
|
|
||||||
const user = 9;
|
|
||||||
const notificationSubscriptionId = 5;
|
|
||||||
const ctx = {req: {accessToken: {userId: user}}};
|
|
||||||
const notification = await models.NotificationSubscription.findById(notificationSubscriptionId);
|
|
||||||
|
|
||||||
await models.NotificationSubscription.deleteNotification(ctx, notification.id, options);
|
|
||||||
|
|
||||||
const deletedNotification = await models.NotificationSubscription.findById(notificationSubscriptionId);
|
|
||||||
|
|
||||||
expect(deletedNotification).toBeNull();
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const models = require('vn-loopback/server/server').models;
|
const models = require('vn-loopback/server/server').models;
|
||||||
|
const ForbiddenError = require('vn-loopback/util/forbiddenError');
|
||||||
|
|
||||||
describe('loopback model VnUser', () => {
|
describe('loopback model VnUser', () => {
|
||||||
it('should return true if the user has the given role', async() => {
|
it('should return true if the user has the given role', async() => {
|
||||||
|
@ -12,4 +13,42 @@ describe('loopback model VnUser', () => {
|
||||||
|
|
||||||
expect(result).toBeFalsy();
|
expect(result).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('userSecurity', () => {
|
||||||
|
const itManagementId = 115;
|
||||||
|
const hrId = 37;
|
||||||
|
const employeeId = 1;
|
||||||
|
|
||||||
|
it('should check if you are the same user', async() => {
|
||||||
|
const ctx = {options: {accessToken: {userId: employeeId}}};
|
||||||
|
await models.VnUser.userSecurity(ctx, employeeId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check for higher privileges', async() => {
|
||||||
|
const ctx = {options: {accessToken: {userId: itManagementId}}};
|
||||||
|
await models.VnUser.userSecurity(ctx, employeeId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check if you have medium privileges and the user email is not verified', async() => {
|
||||||
|
const ctx = {options: {accessToken: {userId: hrId}}};
|
||||||
|
await models.VnUser.userSecurity(ctx, employeeId);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error if you have medium privileges and the users email is verified', async() => {
|
||||||
|
const tx = await models.VnUser.beginTransaction({});
|
||||||
|
const ctx = {options: {accessToken: {userId: hrId}}};
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
const userToUpdate = await models.VnUser.findById(1, null, options);
|
||||||
|
userToUpdate.updateAttribute('emailVerified', 1, options);
|
||||||
|
|
||||||
|
await models.VnUser.userSecurity(ctx, employeeId, options);
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (error) {
|
||||||
|
await tx.rollback();
|
||||||
|
|
||||||
|
expect(error).toEqual(new ForbiddenError());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
require('../methods/url/getByUser')(Self);
|
||||||
|
require('../methods/url/getUrl')(Self);
|
||||||
|
};
|
|
@ -1,6 +1,7 @@
|
||||||
const vnModel = require('vn-loopback/common/models/vn-model');
|
const vnModel = require('vn-loopback/common/models/vn-model');
|
||||||
const LoopBackContext = require('loopback-context');
|
|
||||||
const {Email} = require('vn-print');
|
const {Email} = require('vn-print');
|
||||||
|
const ForbiddenError = require('vn-loopback/util/forbiddenError');
|
||||||
|
const LoopBackContext = require('loopback-context');
|
||||||
|
|
||||||
module.exports = function(Self) {
|
module.exports = function(Self) {
|
||||||
vnModel(Self);
|
vnModel(Self);
|
||||||
|
@ -12,6 +13,7 @@ module.exports = function(Self) {
|
||||||
require('../methods/vn-user/privileges')(Self);
|
require('../methods/vn-user/privileges')(Self);
|
||||||
require('../methods/vn-user/validate-auth')(Self);
|
require('../methods/vn-user/validate-auth')(Self);
|
||||||
require('../methods/vn-user/renew-token')(Self);
|
require('../methods/vn-user/renew-token')(Self);
|
||||||
|
require('../methods/vn-user/update-user')(Self);
|
||||||
|
|
||||||
Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create');
|
Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create');
|
||||||
|
|
||||||
|
@ -90,11 +92,7 @@ module.exports = function(Self) {
|
||||||
};
|
};
|
||||||
|
|
||||||
Self.on('resetPasswordRequest', async function(info) {
|
Self.on('resetPasswordRequest', async function(info) {
|
||||||
const loopBackContext = LoopBackContext.getCurrentContext();
|
const url = await Self.app.models.Url.getUrl();
|
||||||
const httpCtx = {req: loopBackContext.active};
|
|
||||||
const httpRequest = httpCtx.req.http.req;
|
|
||||||
const headers = httpRequest.headers;
|
|
||||||
const origin = headers.origin;
|
|
||||||
|
|
||||||
const defaultHash = '/reset-password?access_token=$token$';
|
const defaultHash = '/reset-password?access_token=$token$';
|
||||||
const recoverHashes = {
|
const recoverHashes = {
|
||||||
|
@ -110,7 +108,7 @@ module.exports = function(Self) {
|
||||||
const params = {
|
const params = {
|
||||||
recipient: info.email,
|
recipient: info.email,
|
||||||
lang: user.lang,
|
lang: user.lang,
|
||||||
url: origin + '/#!' + recoverHash
|
url: url.slice(0, -1) + recoverHash
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = Object.assign({}, info.options);
|
const options = Object.assign({}, info.options);
|
||||||
|
@ -178,45 +176,75 @@ module.exports = function(Self) {
|
||||||
Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls
|
Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls
|
||||||
.filter(acl => acl.property != 'changePassword');
|
.filter(acl => acl.property != 'changePassword');
|
||||||
|
|
||||||
// FIXME: https://redmine.verdnatura.es/issues/5761
|
Self.userSecurity = async(ctx, userId, options) => {
|
||||||
// Self.afterRemote('prototype.patchAttributes', async(ctx, instance) => {
|
const models = Self.app.models;
|
||||||
// if (!ctx.args || !ctx.args.data.email) return;
|
const accessToken = ctx?.options?.accessToken || LoopBackContext.getCurrentContext().active.accessToken;
|
||||||
|
const ctxToken = {req: {accessToken}};
|
||||||
|
|
||||||
// const loopBackContext = LoopBackContext.getCurrentContext();
|
if (userId === accessToken.userId) return;
|
||||||
// const httpCtx = {req: loopBackContext.active};
|
|
||||||
// const httpRequest = httpCtx.req.http.req;
|
|
||||||
// const headers = httpRequest.headers;
|
|
||||||
// const origin = headers.origin;
|
|
||||||
// const url = origin.split(':');
|
|
||||||
|
|
||||||
// class Mailer {
|
const myOptions = {};
|
||||||
// async send(verifyOptions, cb) {
|
if (typeof options == 'object')
|
||||||
// const params = {
|
Object.assign(myOptions, options);
|
||||||
// url: verifyOptions.verifyHref,
|
|
||||||
// recipient: verifyOptions.to,
|
|
||||||
// lang: ctx.req.getLocale()
|
|
||||||
// };
|
|
||||||
|
|
||||||
// const email = new Email('email-verify', params);
|
const hasHigherPrivileges = await models.ACL.checkAccessAcl(ctxToken, 'VnUser', 'higherPrivileges', myOptions);
|
||||||
// email.send();
|
if (hasHigherPrivileges) return;
|
||||||
|
|
||||||
// cb(null, verifyOptions.to);
|
const hasMediumPrivileges = await models.ACL.checkAccessAcl(ctxToken, 'VnUser', 'mediumPrivileges', myOptions);
|
||||||
// }
|
const user = await models.VnUser.findById(userId, {fields: ['id', 'emailVerified']}, myOptions);
|
||||||
// }
|
if (!user.emailVerified && hasMediumPrivileges) return;
|
||||||
|
|
||||||
// const options = {
|
throw new ForbiddenError();
|
||||||
// type: 'email',
|
};
|
||||||
// to: instance.email,
|
|
||||||
// from: {},
|
|
||||||
// redirect: `${origin}/#!/account/${instance.id}/basic-data?emailConfirmed`,
|
|
||||||
// template: false,
|
|
||||||
// mailer: new Mailer,
|
|
||||||
// host: url[1].split('/')[2],
|
|
||||||
// port: url[2],
|
|
||||||
// protocol: url[0],
|
|
||||||
// user: Self
|
|
||||||
// };
|
|
||||||
|
|
||||||
// await instance.verify(options);
|
Self.observe('after save', async ctx => {
|
||||||
// });
|
const instance = ctx?.instance;
|
||||||
|
const newEmail = instance?.email;
|
||||||
|
const oldEmail = ctx?.hookState?.oldInstance?.email;
|
||||||
|
if (!ctx.isNewInstance && (!newEmail || !oldEmail || newEmail == oldEmail)) return;
|
||||||
|
|
||||||
|
const loopBackContext = LoopBackContext.getCurrentContext();
|
||||||
|
const httpCtx = {req: loopBackContext.active};
|
||||||
|
const httpRequest = httpCtx.req.http.req;
|
||||||
|
const headers = httpRequest.headers;
|
||||||
|
const origin = headers.origin;
|
||||||
|
const url = origin.split(':');
|
||||||
|
|
||||||
|
const env = process.env.NODE_ENV;
|
||||||
|
const liliumUrl = await Self.app.models.Url.findOne({
|
||||||
|
where: {and: [
|
||||||
|
{appName: 'lilium'},
|
||||||
|
{environment: env}
|
||||||
|
]}
|
||||||
|
});
|
||||||
|
|
||||||
|
class Mailer {
|
||||||
|
async send(verifyOptions, cb) {
|
||||||
|
const params = {
|
||||||
|
url: verifyOptions.verifyHref,
|
||||||
|
recipient: verifyOptions.to
|
||||||
|
};
|
||||||
|
|
||||||
|
const email = new Email('email-verify', params);
|
||||||
|
email.send();
|
||||||
|
|
||||||
|
cb(null, verifyOptions.to);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
type: 'email',
|
||||||
|
to: newEmail,
|
||||||
|
from: {},
|
||||||
|
redirect: `${liliumUrl.url}verifyEmail?userId=${instance.id}`,
|
||||||
|
template: false,
|
||||||
|
mailer: new Mailer,
|
||||||
|
host: url[1].split('/')[2],
|
||||||
|
port: url[2],
|
||||||
|
protocol: url[0],
|
||||||
|
user: Self
|
||||||
|
};
|
||||||
|
|
||||||
|
await instance.verify(options, ctx.options);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,19 +13,12 @@
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"id": true
|
"id": true
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
"username": {
|
"username": {
|
||||||
"type": "string",
|
"type": "string"
|
||||||
"mysql": {
|
|
||||||
"columnName": "name"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"password": {
|
|
||||||
"type": "string",
|
|
||||||
"required": true
|
|
||||||
},
|
},
|
||||||
"roleFk": {
|
"roleFk": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
|
@ -45,6 +38,9 @@
|
||||||
"email": {
|
"email": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"emailVerified": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"created": {
|
"created": {
|
||||||
"type": "date"
|
"type": "date"
|
||||||
},
|
},
|
||||||
|
@ -144,7 +140,8 @@
|
||||||
"image",
|
"image",
|
||||||
"hasGrant",
|
"hasGrant",
|
||||||
"realm",
|
"realm",
|
||||||
"email"
|
"email",
|
||||||
|
"emailVerified"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
DELIMITER $$
|
||||||
|
$$
|
||||||
|
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_canAdvance`(vDateFuture DATE, vDateToAdvance DATE, vWarehouseFk INT)
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Devuelve los tickets y la cantidad de lineas de venta que se pueden adelantar.
|
||||||
|
*
|
||||||
|
* @param vDateFuture Fecha de los tickets que se quieren adelantar.
|
||||||
|
* @param vDateToAdvance Fecha a cuando se quiere adelantar.
|
||||||
|
* @param vWarehouseFk Almacén
|
||||||
|
*/
|
||||||
|
DECLARE vDateInventory DATE;
|
||||||
|
|
||||||
|
SELECT inventoried INTO vDateInventory FROM config;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tmp.stock;
|
||||||
|
CREATE TEMPORARY TABLE tmp.stock
|
||||||
|
(itemFk INT PRIMARY KEY,
|
||||||
|
amount INT)
|
||||||
|
ENGINE = MEMORY;
|
||||||
|
|
||||||
|
INSERT INTO tmp.stock(itemFk, amount)
|
||||||
|
SELECT itemFk, SUM(quantity) amount FROM
|
||||||
|
(
|
||||||
|
SELECT itemFk, quantity
|
||||||
|
FROM itemTicketOut
|
||||||
|
WHERE shipped >= vDateInventory
|
||||||
|
AND shipped < vDateFuture
|
||||||
|
AND warehouseFk = vWarehouseFk
|
||||||
|
UNION ALL
|
||||||
|
SELECT itemFk, quantity
|
||||||
|
FROM itemEntryIn
|
||||||
|
WHERE landed >= vDateInventory
|
||||||
|
AND landed < vDateFuture
|
||||||
|
AND isVirtualStock = FALSE
|
||||||
|
AND warehouseInFk = vWarehouseFk
|
||||||
|
UNION ALL
|
||||||
|
SELECT itemFk, quantity
|
||||||
|
FROM itemEntryOut
|
||||||
|
WHERE shipped >= vDateInventory
|
||||||
|
AND shipped < vDateFuture
|
||||||
|
AND warehouseOutFk = vWarehouseFk
|
||||||
|
) t
|
||||||
|
GROUP BY itemFk HAVING amount != 0;
|
||||||
|
|
||||||
|
CREATE OR REPLACE TEMPORARY TABLE tmp.filter
|
||||||
|
(INDEX (id))
|
||||||
|
SELECT
|
||||||
|
origin.ticketFk futureId,
|
||||||
|
dest.ticketFk id,
|
||||||
|
dest.state,
|
||||||
|
origin.futureState,
|
||||||
|
origin.futureIpt,
|
||||||
|
dest.ipt,
|
||||||
|
origin.workerFk,
|
||||||
|
origin.futureLiters,
|
||||||
|
origin.futureLines,
|
||||||
|
dest.shipped,
|
||||||
|
origin.shipped futureShipped,
|
||||||
|
dest.totalWithVat,
|
||||||
|
origin.totalWithVat futureTotalWithVat,
|
||||||
|
dest.agency,
|
||||||
|
origin.futureAgency,
|
||||||
|
dest.lines,
|
||||||
|
dest.liters,
|
||||||
|
origin.futureLines - origin.hasStock AS notMovableLines,
|
||||||
|
(origin.futureLines = origin.hasStock) AS isFullMovable,
|
||||||
|
origin.futureZoneFk,
|
||||||
|
origin.futureZoneName,
|
||||||
|
origin.classColor futureClassColor,
|
||||||
|
dest.classColor
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
s.ticketFk,
|
||||||
|
c.salesPersonFk workerFk,
|
||||||
|
t.shipped,
|
||||||
|
t.totalWithVat,
|
||||||
|
st.name futureState,
|
||||||
|
t.addressFk,
|
||||||
|
am.name futureAgency,
|
||||||
|
count(s.id) futureLines,
|
||||||
|
GROUP_CONCAT(DISTINCT ipt.code ORDER BY ipt.code) futureIpt,
|
||||||
|
CAST(SUM(litros) AS DECIMAL(10,0)) futureLiters,
|
||||||
|
SUM((s.quantity <= IFNULL(st.amount,0))) hasStock,
|
||||||
|
z.id futureZoneFk,
|
||||||
|
z.name futureZoneName,
|
||||||
|
st.classColor
|
||||||
|
FROM ticket t
|
||||||
|
JOIN client c ON c.id = t.clientFk
|
||||||
|
JOIN sale s ON s.ticketFk = t.id
|
||||||
|
JOIN saleVolume sv ON sv.saleFk = s.id
|
||||||
|
JOIN item i ON i.id = s.itemFk
|
||||||
|
JOIN ticketState ts ON ts.ticketFk = t.id
|
||||||
|
JOIN state st ON st.id = ts.stateFk
|
||||||
|
JOIN agencyMode am ON t.agencyModeFk = am.id
|
||||||
|
JOIN zone z ON t.zoneFk = z.id
|
||||||
|
LEFT JOIN itemPackingType ipt ON ipt.code = i.itemPackingTypeFk
|
||||||
|
LEFT JOIN tmp.stock st ON st.itemFk = i.id
|
||||||
|
WHERE t.shipped BETWEEN vDateFuture AND util.dayend(vDateFuture)
|
||||||
|
AND t.warehouseFk = vWarehouseFk
|
||||||
|
GROUP BY t.id
|
||||||
|
) origin
|
||||||
|
JOIN (
|
||||||
|
SELECT
|
||||||
|
t.id ticketFk,
|
||||||
|
t.addressFk,
|
||||||
|
st.name state,
|
||||||
|
GROUP_CONCAT(DISTINCT ipt.code ORDER BY ipt.code) ipt,
|
||||||
|
t.shipped,
|
||||||
|
t.totalWithVat,
|
||||||
|
am.name agency,
|
||||||
|
CAST(SUM(litros) AS DECIMAL(10,0)) liters,
|
||||||
|
CAST(COUNT(*) AS DECIMAL(10,0)) `lines`,
|
||||||
|
st.classColor
|
||||||
|
FROM ticket t
|
||||||
|
JOIN sale s ON s.ticketFk = t.id
|
||||||
|
JOIN saleVolume sv ON sv.saleFk = s.id
|
||||||
|
JOIN item i ON i.id = s.itemFk
|
||||||
|
JOIN ticketState ts ON ts.ticketFk = t.id
|
||||||
|
JOIN state st ON st.id = ts.stateFk
|
||||||
|
JOIN agencyMode am ON t.agencyModeFk = am.id
|
||||||
|
LEFT JOIN itemPackingType ipt ON ipt.code = i.itemPackingTypeFk
|
||||||
|
WHERE t.shipped BETWEEN vDateToAdvance AND util.dayend(vDateToAdvance)
|
||||||
|
AND t.warehouseFk = vWarehouseFk
|
||||||
|
AND st.order <= 5
|
||||||
|
GROUP BY t.id
|
||||||
|
) dest ON dest.addressFk = origin.addressFk
|
||||||
|
WHERE origin.hasStock != 0;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE tmp.stock;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
UPDATE `vn`.`workerTimeControlConfig`
|
||||||
|
SET `timeToBreakTime` = 18000;
|
||||||
|
|
||||||
|
ALTER TABLE `vn`.`workerTimeControlConfig`
|
||||||
|
DROP COLUMN IF EXISTS `maxTimeToBreak`;
|
||||||
|
ALTER TABLE `vn`.`workerTimeControlConfig`
|
||||||
|
ADD COLUMN maxTimeToBreak INT DEFAULT 3600 NULL;
|
||||||
|
|
||||||
|
ALTER TABLE `vn`.`workerTimeControlConfig`
|
||||||
|
DROP COLUMN IF EXISTS `maxWorkShortCycle`;
|
||||||
|
|
||||||
|
ALTER TABLE `vn`.`workerTimeControlConfig`
|
||||||
|
ADD COLUMN `maxWorkShortCycle` INT(10) UNSIGNED DEFAULT 561600
|
||||||
|
COMMENT 'Máximo tiempo que un trabajador puede estar trabajando con el que adquirirá el derecho a un descanso semanal corto';
|
||||||
|
|
||||||
|
ALTER TABLE `vn`.`workerTimeControlConfig`
|
||||||
|
DROP COLUMN IF EXISTS `maxWorkLongCycle`;
|
||||||
|
|
||||||
|
ALTER TABLE `vn`.`workerTimeControlConfig`
|
||||||
|
ADD COLUMN `maxWorkLongCycle` INT(10) UNSIGNED DEFAULT 950400
|
||||||
|
COMMENT 'Máximo tiempo que un trabajador puede estar trabajando con el que adquirirá el derecho a un descanso semanal largo';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS `vn`.`workerTimeControlError` (
|
||||||
|
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
|
`code` char(35) NOT NULL,
|
||||||
|
`description` varchar(255) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
UNIQUE KEY `code` (`code`)
|
||||||
|
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||||
|
|
||||||
|
INSERT IGNORE INTO `vn`.`workerTimeControlError` (`code`, `description`)
|
||||||
|
VALUES
|
||||||
|
('IS_NOT_ALLOWED_FUTURE', 'No se permite fichar a futuro'),
|
||||||
|
('INACTIVE_BUSINESS', 'No hay un contrato en vigor'),
|
||||||
|
('IS_NOT_ALLOWED_WORK', 'No está permitido trabajar'),
|
||||||
|
('ODD_WORKERTIMECONTROL', 'Fichadas impares'),
|
||||||
|
('DAY_MAX_TIME', 'Superado el tiempo máximo entre entrada y salida'),
|
||||||
|
('BREAK_DAY', 'Descanso diario'),
|
||||||
|
('BREAK_WEEK', 'Descanso semanal'),
|
||||||
|
('WRONG_DIRECTION', 'Dirección incorrecta'),
|
||||||
|
('UNDEFINED_ERROR', 'Error sin definir');
|
|
@ -0,0 +1,194 @@
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`timeControl_calculate`(
|
||||||
|
vDatedFrom DATETIME,
|
||||||
|
vDatedTo DATETIME)
|
||||||
|
BEGIN
|
||||||
|
/*
|
||||||
|
* Agrupa por trabajador y día, el tiempo de trabajo y descanso retribuido(si tiene).
|
||||||
|
* Los registros horarios incorrectos (tmp.timeControlError) no se considerarán.
|
||||||
|
* Si un trabajador ha trabajado más de un cierto umbral de tiempo (vTimeToBreakTime)
|
||||||
|
* y no ha tenido descansos que superen un parámetro determinado(vMaxTimeToBreak),
|
||||||
|
* se le añadirá un tiempo de descanso (vBreakTime) a sus horas trabajadas.
|
||||||
|
* El tiempo de descanso solo se añade si el trabajador realmente disfrutó del descanso.
|
||||||
|
* Si disfrutó de menos tiempo de descanso, solo se añade el tiempo que disfrutó.
|
||||||
|
*
|
||||||
|
* @param vDatedFrom
|
||||||
|
* @param vDatedTo
|
||||||
|
*
|
||||||
|
* @return tmp.timeControlCalculate
|
||||||
|
* (workerFk, dated, timeWorkSeconds, timeWorkSexagesimal, timeWorkDecimal, timed)
|
||||||
|
*/
|
||||||
|
DECLARE vHourSeconds INTEGER;
|
||||||
|
DECLARE vDatedFromYesterday DATETIME;
|
||||||
|
DECLARE vDatedToTomorrow DATETIME;
|
||||||
|
DECLARE vTimeToBreakTime INT;
|
||||||
|
DECLARE vBreakTime INT;
|
||||||
|
DECLARE vMaxTimeToBreak INT;
|
||||||
|
|
||||||
|
SELECT DATE_SUB(vDatedFrom, INTERVAL 1 DAY), DATE_ADD(vDatedTo, INTERVAL 1 DAY)
|
||||||
|
INTO vDatedFromYesterday, vDatedToTomorrow;
|
||||||
|
|
||||||
|
SELECT timeToBreakTime, breakTime, maxTimeToBreak, TIME_TO_SEC('01:00:00')
|
||||||
|
INTO vTimeToBreakTime, vBreakTime, vMaxTimeToBreak, vHourSeconds
|
||||||
|
FROM workerTimeControlConfig
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
CALL timeControl_getError(vDatedFromYesterday, vDatedToTomorrow);
|
||||||
|
|
||||||
|
CREATE OR REPLACE TEMPORARY TABLE tmp.workerTimeControl
|
||||||
|
(INDEX(userFk, timed), INDEX(timed), INDEX(direction))
|
||||||
|
ENGINE = MEMORY
|
||||||
|
SELECT wtc.userFk,
|
||||||
|
wtc.timed,
|
||||||
|
DATE(wtc.timed) dated,
|
||||||
|
wtc.direction,
|
||||||
|
TRUE isReal
|
||||||
|
FROM workerTimeControl wtc
|
||||||
|
JOIN tmp.`user` u ON u.userFk = wtc.userFk
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT wtc.userFk, MIN(wtc.timed) firstIn
|
||||||
|
FROM workerTimeControl wtc
|
||||||
|
JOIN tmp.`user` u ON u.userFk = wtc.userFk
|
||||||
|
LEFT JOIN tmp.timeControlError tce ON tce.id = wtc.id
|
||||||
|
WHERE wtc.timed BETWEEN vDatedFromYesterday AND vDatedToTomorrow
|
||||||
|
AND wtc.direction = 'in'
|
||||||
|
AND tce.id IS NULL
|
||||||
|
GROUP BY userFk
|
||||||
|
) fi ON wtc.userFk = fi.userFk
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT wtc.userFk, MAX(wtc.timed) lastOut
|
||||||
|
FROM workerTimeControl wtc
|
||||||
|
JOIN tmp.`user` u ON u.userFk = wtc.userFk
|
||||||
|
LEFT JOIN tmp.timeControlError tce ON tce.id = wtc.id
|
||||||
|
WHERE wtc.timed BETWEEN vDatedFromYesterday AND vDatedToTomorrow
|
||||||
|
AND wtc.direction = 'out'
|
||||||
|
AND tce.id IS NULL
|
||||||
|
GROUP BY userFk
|
||||||
|
) lo ON wtc.userFk = lo.userFk
|
||||||
|
LEFT JOIN tmp.timeControlError tce ON tce.id = wtc.id
|
||||||
|
WHERE wtc.timed BETWEEN fi.firstIn AND lo.lastOut
|
||||||
|
AND tce.id IS NULL
|
||||||
|
ORDER BY wtc.userFk, wtc.timed;
|
||||||
|
|
||||||
|
CREATE OR REPLACE TEMPORARY TABLE tmp.wtcToinsert
|
||||||
|
(INDEX(timed))
|
||||||
|
ENGINE = MEMORY
|
||||||
|
WITH wtc AS(
|
||||||
|
SELECT timed,
|
||||||
|
userFk,
|
||||||
|
dated,
|
||||||
|
direction,
|
||||||
|
LEAD(dated) OVER
|
||||||
|
(PARTITION BY userFk, dated ORDER BY timed) nextDay,
|
||||||
|
LEAD(userFk) OVER
|
||||||
|
(PARTITION BY userFk ORDER BY timed) nextUserFk,
|
||||||
|
ROW_NUMBER() OVER (ORDER BY userFk, timed) MOD 2 isOdd
|
||||||
|
FROM tmp.workerTimeControl
|
||||||
|
WHERE timed BETWEEN vDatedFromYesterday AND vDatedToTomorrow
|
||||||
|
ORDER BY userFk, timed
|
||||||
|
), wtcToinsert AS(
|
||||||
|
SELECT userFk,
|
||||||
|
dated,
|
||||||
|
IF(userFk = nextUserFk
|
||||||
|
AND nextDay IS NULL
|
||||||
|
AND isOdd
|
||||||
|
AND direction <> 'out', TRUE, FALSE) outNextDay,
|
||||||
|
IF(userFk = nextUserFk
|
||||||
|
AND nextDay IS NULL
|
||||||
|
AND NOT isOdd
|
||||||
|
AND direction <> 'out', TRUE, FALSE) outNextDayWhitBreak
|
||||||
|
FROM wtc
|
||||||
|
HAVING outNextDay OR outNextDayWhitBreak
|
||||||
|
)SELECT userFk, util.dayEnd(dated) timed, 'out' direction
|
||||||
|
FROM wtcToinsert
|
||||||
|
WHERE outNextDay
|
||||||
|
UNION ALL
|
||||||
|
SELECT userFk, dated + INTERVAL 1 DAY, 'in'
|
||||||
|
FROM wtcToinsert
|
||||||
|
WHERE outNextDay
|
||||||
|
UNION ALL
|
||||||
|
SELECT userFk, util.dayEnd(dated) - INTERVAL 1 SECOND, 'middle'
|
||||||
|
FROM wtcToinsert
|
||||||
|
WHERE outNextDayWhitBreak
|
||||||
|
UNION ALL
|
||||||
|
SELECT userFk, util.dayEnd(dated), 'out'
|
||||||
|
FROM wtcToinsert
|
||||||
|
WHERE outNextDayWhitBreak
|
||||||
|
UNION ALL
|
||||||
|
SELECT userFk, dated + INTERVAL 1 DAY, 'in'
|
||||||
|
FROM wtcToinsert
|
||||||
|
WHERE outNextDayWhitBreak
|
||||||
|
UNION ALL
|
||||||
|
SELECT userFk, dated + INTERVAL 1 DAY + INTERVAL 1 SECOND, 'middle'
|
||||||
|
FROM wtcToinsert
|
||||||
|
WHERE outNextDayWhitBreak;
|
||||||
|
|
||||||
|
INSERT INTO tmp.workerTimeControl (userFk, timed, dated, direction, isReal)
|
||||||
|
SELECT userFk, timed, DATE(timed), direction, FALSE
|
||||||
|
FROM tmp.wtcToinsert;
|
||||||
|
|
||||||
|
SET @accumulatedForBreakTime = 0;
|
||||||
|
SET @oldrealDay = NULL;
|
||||||
|
CREATE OR REPLACE TEMPORARY TABLE tmp.timeControlCalculate
|
||||||
|
WITH workerTimed AS (
|
||||||
|
SELECT
|
||||||
|
userFk,
|
||||||
|
dated,
|
||||||
|
timed,
|
||||||
|
(direction ='in' AND isReal) breakPoint,
|
||||||
|
SUM(CASE WHEN (direction ='in' AND isReal) THEN TRUE ELSE FALSE END)
|
||||||
|
OVER (ORDER BY userFk, timed) AS realDay,
|
||||||
|
TIMESTAMPDIFF(SECOND, LAG(timed)
|
||||||
|
OVER (PARTITION BY userFk, dated ORDER BY timed), timed) gapTime,
|
||||||
|
ROW_NUMBER()
|
||||||
|
OVER (PARTITION BY userFk, dated ORDER BY timed) MOD 2 isOdd
|
||||||
|
FROM tmp.workerTimeControl
|
||||||
|
WHERE timed BETWEEN vDatedFromYesterday AND vDatedToTomorrow
|
||||||
|
), accumulated AS (
|
||||||
|
SELECT SUM(IF(isOdd, 0, gapTime))
|
||||||
|
OVER (PARTITION BY userFk,dated ORDER BY userFk,timed) accumulatedWorkTime,
|
||||||
|
SUM(IF(NOT isOdd OR breakPoint, 0, IFNULL(gapTime, 0)))
|
||||||
|
OVER (PARTITION BY realDay ORDER BY realDay,timed) accumulatedBreakTime,
|
||||||
|
IF(realDay <> @oldrealDay OR (isOdd AND gapTime >= vMaxTimeToBreak),
|
||||||
|
@accumulatedForBreakTime := 0,
|
||||||
|
@accumulatedForBreakTime := @accumulatedForBreakTime +
|
||||||
|
IF(isOdd, 0, gapTime )) accumulatedForBreakTime,
|
||||||
|
@oldrealDay := realDay,
|
||||||
|
userFk,
|
||||||
|
dated,
|
||||||
|
realDay
|
||||||
|
FROM workerTimed
|
||||||
|
), totalWorked AS (
|
||||||
|
SELECT userFk,
|
||||||
|
dated,
|
||||||
|
MAX(accumulatedWorkTime) +
|
||||||
|
IF(MAX(accumulatedForBreakTime) >= vTimeToBreakTime,
|
||||||
|
LEAST(vBreakTime, MAX(accumulatedBreakTime)),
|
||||||
|
0) timeWorkSeconds
|
||||||
|
FROM accumulated
|
||||||
|
GROUP BY userFk, dated
|
||||||
|
)SELECT tw.userFk,
|
||||||
|
tw.dated,
|
||||||
|
timeWorkSeconds,
|
||||||
|
SEC_TO_TIME(timeWorkSeconds) timeWorkSexagesimal,
|
||||||
|
timeWorkSeconds / vHourSeconds timeWorkDecimal,
|
||||||
|
sub.tableTimed
|
||||||
|
FROM totalWorked tw
|
||||||
|
JOIN (
|
||||||
|
SELECT userFk,
|
||||||
|
dated,
|
||||||
|
GROUP_CONCAT(DATE_FORMAT(timed, "%H:%i") ORDER BY timed ASC
|
||||||
|
SEPARATOR ' - ')tableTimed
|
||||||
|
FROM tmp.workerTimeControl
|
||||||
|
WHERE timed BETWEEN vDatedFromYesterday AND vDatedToTomorrow
|
||||||
|
AND isReal
|
||||||
|
GROUP BY userFk, dated
|
||||||
|
)sub ON sub.dated = tw.dated
|
||||||
|
AND sub.userFk = tw.userFk
|
||||||
|
WHERE tw.dated BETWEEN vDatedFrom AND vDatedTo;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE tmp.timeControlError;
|
||||||
|
DROP TEMPORARY TABLE tmp.wtcToinsert;
|
||||||
|
DROP TEMPORARY TABLE tmp.workerTimeControl;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,286 @@
|
||||||
|
DELIMITER $$
|
||||||
|
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`workerTimeControl_clockIn`(
|
||||||
|
vWorkerFk INT,
|
||||||
|
vTimed DATETIME,
|
||||||
|
vDirection VARCHAR(10)
|
||||||
|
)
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Verifica si el empleado puede fichar
|
||||||
|
* @param vWorkerFk Identificador del trabajador
|
||||||
|
* @param vTimed valor de la fichada, IF vTimed IS NULL vTimed = NOW
|
||||||
|
* @param vDirection solo se pueden pasa los valores del campo
|
||||||
|
* workerTimeControl.direction ENUM('in', 'out', 'middle')
|
||||||
|
* @return Si todo es correcto, retorna el número de id la tabla workerTimeControl.
|
||||||
|
* Si hay algún problema, devuelve el mesaje que se debe mostrar al usuario
|
||||||
|
* Solo retorna el primer problema, en caso de no ocurrir ningún error se añadirá
|
||||||
|
* fichada a la tabla vn.workerTimeControl
|
||||||
|
*/
|
||||||
|
|
||||||
|
DECLARE vLastIn DATETIME;
|
||||||
|
DECLARE vLastOut DATETIME;
|
||||||
|
DECLARE vNextIn DATETIME;
|
||||||
|
DECLARE vNextOut DATETIME;
|
||||||
|
DECLARE vNextDirection ENUM('in', 'out');
|
||||||
|
DECLARE vLastDirection ENUM('in', 'out');
|
||||||
|
DECLARE vDayMaxTime INTEGER;
|
||||||
|
DECLARE vDayBreak INT;
|
||||||
|
DECLARE vShortWeekBreak INT;
|
||||||
|
DECLARE vLongWeekBreak INT;
|
||||||
|
DECLARE vWeekScope INT;
|
||||||
|
DECLARE vMailTo VARCHAR(50) DEFAULT NULL;
|
||||||
|
DECLARE vUserName VARCHAR(50) DEFAULT NULL;
|
||||||
|
DECLARE vIsError BOOLEAN DEFAULT FALSE;
|
||||||
|
DECLARE vErrorMessage VARCHAR(255) DEFAULT NULL;
|
||||||
|
DECLARE vErrorCode VARCHAR(50);
|
||||||
|
DECLARE vDated DATE;
|
||||||
|
DECLARE vIsAllowedToWork VARCHAR(50);
|
||||||
|
DECLARE vIsManual BOOLEAN DEFAULT TRUE;
|
||||||
|
DECLARE vMaxWorkShortCycle INT;
|
||||||
|
DECLARE vMaxWorkLongCycle INT;
|
||||||
|
|
||||||
|
DECLARE EXIT HANDLER FOR SQLSTATE '45000'
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
SELECT CONCAT(u.name, '@verdnatura.es'),
|
||||||
|
CONCAT(w.firstName, ' ', w.lastName)
|
||||||
|
INTO vMailTo, vUserName
|
||||||
|
FROM account.user u
|
||||||
|
JOIN worker w ON w.bossFk = u.id
|
||||||
|
WHERE w.id = vWorkerFk;
|
||||||
|
|
||||||
|
SELECT `description` INTO vErrorMessage
|
||||||
|
FROM workerTimeControlError
|
||||||
|
WHERE `code` = vErrorCode;
|
||||||
|
|
||||||
|
IF vErrorMessage IS NULL THEN
|
||||||
|
SET vErrorMessage = 'Error sin definir';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SELECT vErrorMessage `error`;
|
||||||
|
SELECT CONCAT(vUserName,
|
||||||
|
' no ha podido fichar por el siguiente problema: ',
|
||||||
|
vErrorMessage)
|
||||||
|
INTO vErrorMessage;
|
||||||
|
|
||||||
|
CALL mail_insert( vMailTo, vMailTo, 'Error al fichar', vErrorMessage);
|
||||||
|
END;
|
||||||
|
|
||||||
|
IF (vTimed IS NULL) THEN
|
||||||
|
SET vTimed = util.VN_NOW();
|
||||||
|
SET vIsManual = FALSE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SET vDated = DATE(vTimed);
|
||||||
|
|
||||||
|
SELECT IF(pc.name = 'Conductor +3500kg',
|
||||||
|
wc.dayBreakDriver,
|
||||||
|
wc.dayBreak),
|
||||||
|
wc.shortWeekBreak,
|
||||||
|
wc.longWeekBreak,
|
||||||
|
wc.weekScope,
|
||||||
|
wc.dayMaxTime,
|
||||||
|
wc.maxWorkShortCycle,
|
||||||
|
wc.maxWorkLongCycle
|
||||||
|
INTO vDayBreak,
|
||||||
|
vShortWeekBreak,
|
||||||
|
vLongWeekBreak,
|
||||||
|
vWeekScope,
|
||||||
|
vDayMaxTime,
|
||||||
|
vMaxWorkShortCycle,
|
||||||
|
vMaxWorkLongCycle
|
||||||
|
FROM business b
|
||||||
|
JOIN professionalCategory pc
|
||||||
|
ON pc.id = b.workerBusinessProfessionalCategoryFk
|
||||||
|
JOIN workerTimeControlConfig wc
|
||||||
|
WHERE b.workerFk = vWorkerFk
|
||||||
|
AND vDated BETWEEN b.started AND IFNULL(b.ended, vDated);
|
||||||
|
|
||||||
|
-- CONTRATO EN VIGOR
|
||||||
|
IF vDayBreak IS NULL THEN
|
||||||
|
SET vErrorCode = 'INACTIVE_BUSINESS';
|
||||||
|
CALL util.throw(vErrorCode);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- FICHADAS A FUTURO
|
||||||
|
IF vTimed > util.VN_NOW() + INTERVAL 1 MINUTE THEN
|
||||||
|
SET vErrorCode = 'IS_NOT_ALLOWED_FUTURE';
|
||||||
|
CALL util.throw(vErrorCode);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- VERIFICAR SI ESTÁ PERMITIDO TRABAJAR
|
||||||
|
CALL timeBusiness_calculateByWorker(vWorkerFk, vDated, vDated);
|
||||||
|
SELECT isAllowedToWork INTO vIsAllowedToWork
|
||||||
|
FROM tmp.timeBusinessCalculate;
|
||||||
|
DROP TEMPORARY TABLE tmp.timeBusinessCalculate;
|
||||||
|
|
||||||
|
IF NOT vIsAllowedToWork THEN
|
||||||
|
SET vErrorCode = 'IS_NOT_ALLOWED_WORK';
|
||||||
|
CALL util.throw(vErrorCode);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- DIRECCION CORRECTA
|
||||||
|
CALL workerTimeControl_direction(vWorkerFk, vTimed);
|
||||||
|
IF (SELECT
|
||||||
|
IF(IF(option1 IN ('inMiddle', 'outMiddle'),
|
||||||
|
'middle',
|
||||||
|
option1) <> vDirection
|
||||||
|
AND IF(option2 IN ('inMiddle', 'outMiddle'),
|
||||||
|
'middle',
|
||||||
|
IFNULL(option2, '')) <> vDirection,
|
||||||
|
TRUE ,
|
||||||
|
FALSE)
|
||||||
|
FROM tmp.workerTimeControlDirection
|
||||||
|
) THEN
|
||||||
|
SET vIsError = TRUE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE tmp.workerTimeControlDirection;
|
||||||
|
IF vIsError THEN
|
||||||
|
SET vErrorCode = 'WRONG_DIRECTION';
|
||||||
|
CALL util.throw(vErrorCode);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- FICHADAS IMPARES
|
||||||
|
SELECT timed INTO vLastIn
|
||||||
|
FROM workerTimeControl
|
||||||
|
WHERE userFk = vWorkerFk
|
||||||
|
AND direction = 'in'
|
||||||
|
AND timed < vTimed
|
||||||
|
ORDER BY timed DESC
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
IF (SELECT IF(vDirection = 'in',
|
||||||
|
MOD(COUNT(*), 2) ,
|
||||||
|
IF (vDirection = 'out', NOT MOD(COUNT(*), 2), FALSE))
|
||||||
|
FROM workerTimeControl
|
||||||
|
WHERE userFk = vWorkerFk
|
||||||
|
AND timed BETWEEN vLastIn AND vTimed
|
||||||
|
) THEN
|
||||||
|
SET vErrorCode = 'ODD_WORKERTIMECONTROL';
|
||||||
|
CALL util.throw(vErrorCode);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- DESCANSO DIARIO
|
||||||
|
SELECT timed INTO vLastOut
|
||||||
|
FROM workerTimeControl
|
||||||
|
WHERE userFk = vWorkerFk
|
||||||
|
AND direction = 'out'
|
||||||
|
AND timed < vTimed
|
||||||
|
ORDER BY timed DESC
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
SELECT timed INTO vNextIn
|
||||||
|
FROM workerTimeControl
|
||||||
|
WHERE userFk = vWorkerFk
|
||||||
|
AND direction = 'in'
|
||||||
|
AND timed > vTimed
|
||||||
|
ORDER BY timed ASC
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
CASE vDirection
|
||||||
|
WHEN 'in' THEN
|
||||||
|
IF UNIX_TIMESTAMP(vTimed) - UNIX_TIMESTAMP(vLastOut) <= vDayBreak THEN
|
||||||
|
SET vIsError = TRUE;
|
||||||
|
END IF;
|
||||||
|
WHEN 'out' THEN
|
||||||
|
IF UNIX_TIMESTAMP(vNextIn) - UNIX_TIMESTAMP(vTimed) <= vDayBreak THEN
|
||||||
|
SET vIsError = TRUE;
|
||||||
|
END IF;
|
||||||
|
ELSE BEGIN END;
|
||||||
|
END CASE;
|
||||||
|
|
||||||
|
IF vIsError THEN
|
||||||
|
SET vErrorCode = 'BREAK_DAY';
|
||||||
|
CALL util.throw(vErrorCode);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
IF (vDirection IN('in', 'out')) THEN
|
||||||
|
-- VERIFICA MAXIMO TIEMPO DESDE ENTRADA HASTA LA SALIDA
|
||||||
|
|
||||||
|
SELECT timed INTO vNextOut
|
||||||
|
FROM workerTimeControl
|
||||||
|
WHERE userFk = vWorkerFk
|
||||||
|
AND direction = 'out'
|
||||||
|
AND timed > vTimed
|
||||||
|
ORDER BY timed ASC
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
SELECT direction INTO vNextDirection
|
||||||
|
FROM workerTimeControl
|
||||||
|
WHERE userFk = vWorkerFk
|
||||||
|
AND direction IN('in','out')
|
||||||
|
AND timed > vTimed
|
||||||
|
ORDER BY timed ASC
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
SELECT direction INTO vLastDirection
|
||||||
|
FROM workerTimeControl
|
||||||
|
WHERE userFk = vWorkerFk
|
||||||
|
AND direction IN('in', 'out')
|
||||||
|
AND timed < vTimed
|
||||||
|
ORDER BY timed ASC
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
IF (vDirection ='in'
|
||||||
|
AND vNextDirection = 'out'
|
||||||
|
AND UNIX_TIMESTAMP(vNextOut) - UNIX_TIMESTAMP(vTimed) > vDayMaxTime) OR
|
||||||
|
(vDirection ='out'
|
||||||
|
AND vLastDirection = 'in'
|
||||||
|
AND UNIX_TIMESTAMP(vTimed) -UNIX_TIMESTAMP(vLastIn) > vDayMaxTime) THEN
|
||||||
|
SET vErrorCode = 'DAY_MAX_TIME';
|
||||||
|
CALL util.throw(vErrorCode);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- VERIFICA DESCANSO SEMANAL
|
||||||
|
|
||||||
|
WITH wtc AS(
|
||||||
|
(SELECT timed
|
||||||
|
FROM vn.workerTimeControl
|
||||||
|
WHERE userFk = vWorkerFk
|
||||||
|
AND direction IN ('in', 'out')
|
||||||
|
AND timed BETWEEN vTimed - INTERVAL (vWeekScope * 2) SECOND
|
||||||
|
AND vTimed + INTERVAL (vWeekScope * 2) SECOND )
|
||||||
|
UNION
|
||||||
|
(SELECT vTimed)
|
||||||
|
), wtcGap AS(
|
||||||
|
SELECT timed,
|
||||||
|
TIMESTAMPDIFF(SECOND, LAG(timed) OVER (ORDER BY timed), timed) gap
|
||||||
|
FROM wtc
|
||||||
|
ORDER BY timed
|
||||||
|
), wtcBreak AS(
|
||||||
|
SELECT timed,
|
||||||
|
IF(IFNULL(gap, 0) > vShortWeekBreak, TRUE, FALSE) hasShortBreak,
|
||||||
|
IF(IFNULL(gap, 0) > vLongWeekBreak, TRUE, FALSE) hasLongBreak
|
||||||
|
FROM wtcGap
|
||||||
|
ORDER BY timed
|
||||||
|
), wtcBreakCounter AS(
|
||||||
|
SELECT timed,
|
||||||
|
SUM(hasShortBreak) OVER (ORDER BY timed) breakCounter ,
|
||||||
|
LEAD(hasLongBreak) OVER (ORDER BY timed) nextHasLongBreak
|
||||||
|
FROM wtcBreak
|
||||||
|
)SELECT TIMESTAMPDIFF(SECOND, MIN(timed), MAX(timed)) > vMaxWorkLongCycle OR
|
||||||
|
(TIMESTAMPDIFF(SECOND, MIN(timed), MAX(timed))> vMaxWorkShortCycle
|
||||||
|
AND NOT SUM(IFNULL(nextHasLongBreak, 1)))
|
||||||
|
hasError INTO vIsError
|
||||||
|
FROM wtcBreakCounter
|
||||||
|
GROUP BY breakCounter
|
||||||
|
HAVING hasError
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
IF vIsError THEN
|
||||||
|
SET vErrorCode = 'BREAK_WEEK';
|
||||||
|
CALL util.throw(vErrorCode);
|
||||||
|
END IF;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- SE PERMITE FICHAR
|
||||||
|
INSERT INTO workerTimeControl(userFk, timed, direction, `manual`)
|
||||||
|
VALUES(vWorkerFk, vTimed, vDirection, vIsManual);
|
||||||
|
|
||||||
|
SELECT LAST_INSERT_ID() id;
|
||||||
|
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,3 @@
|
||||||
|
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||||
|
VALUES
|
||||||
|
('WorkerDepartment', '*', '*', 'ALLOW', 'ROLE', 'employee');
|
|
@ -0,0 +1,7 @@
|
||||||
|
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||||
|
VALUES
|
||||||
|
('VnUser', 'higherPrivileges', '*', 'ALLOW', 'ROLE', 'itManagement'),
|
||||||
|
('VnUser', 'mediumPrivileges', '*', 'ALLOW', 'ROLE', 'hr'),
|
||||||
|
('VnUser', 'updateUser', '*', 'ALLOW', 'ROLE', 'employee');
|
||||||
|
|
||||||
|
ALTER TABLE `account`.`user` ADD `username` varchar(30) AS (name) VIRTUAL;
|
|
@ -0,0 +1,3 @@
|
||||||
|
|
||||||
|
INSERT INTO `salix`.`ACL` ( model, property, accessType, permission, principalType, principalId)
|
||||||
|
VALUES('TicketCollection', '*', 'WRITE', 'ALLOW', 'ROLE', 'production');
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue