#6434 - SignIn issue_improve-signInLogMethod #1848

Closed
jsegarra wants to merge 65 commits from 6434-improve-signInLog into test
145 changed files with 3726 additions and 1710 deletions
Showing only changes of commit 3f8d1ff617 - Show all commits

View File

@ -10,5 +10,9 @@
"eslint.format.enable": true,
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
}
},
"cSpell.words": [
"salix",
"fdescribe"
]
}

View File

@ -5,27 +5,34 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2348.01] - 2023-11-30
### Added
- (Ticket -> Adelantar) Permite mover lineas sin generar negativos
- (Ticket -> Adelantar) Permite modificar la fecha de los tickets
### Changed
### Fixed
- (Ticket -> RocketChat) Arreglada detección de cambios
## [2346.01] - 2023-11-16
### Added
### Changed
### Fixed
## [2342.01] - 2023-11-02
### Added
### Changed
### Fixed
## [2340.01] - 2023-10-05
### Added
- (Usuarios -> Foto) Se muestra la foto del trabajador
### Changed
### Fixed
- (Usuarios -> Historial) Abre el descriptor del usuario correctamente
## [2340.01] - 2023-10-05
## [2338.01] - 2023-09-21
### Added
@ -35,17 +42,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- (Trabajadores -> Calendario) Icono de check arreglado cuando pulsas un tipo de dia
### Fixed
## [2336.01] - 2023-09-07
### Added
### Changed
### Fixed
## [2334.01] - 2023-08-24
### Added

View File

@ -1,4 +1,4 @@
FROM debian:bullseye-slim
FROM debian:bookworm-slim
ENV TZ Europe/Madrid
ARG DEBIAN_FRONTEND=noninteractive
@ -25,7 +25,13 @@ RUN apt-get update \
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 \
fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
# Extra dependencies
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
samba-common-bin samba-dsdb-modules\
&& rm -rf /var/lib/apt/lists/* \
&& npm -g install pm2

View File

@ -8,7 +8,7 @@ Salix is also the scientific name of a beautifull tree! :)
Required applications.
* Node.js >= 16.x LTS
* Node.js
* Docker
* Git
@ -17,20 +17,7 @@ You will need to install globally the following items.
$ sudo npm install -g jest gulp-cli
```
For the usage of jest --watch on macOs.
```
$ brew install watchman
```
* [watchman](https://facebook.github.io/watchman/)
## Linux Only Prerequisites
Your user must be on the docker group to use it so you will need to run this command:
```
$ sudo usermod -a -G docker yourusername
```
## Getting Started // Installing
## Installing dependencies and launching
Pull from repository.
@ -76,29 +63,6 @@ In Visual Studio Code we use the ESLint extension.
ext install dbaeumer.vscode-eslint
```
Gitlens for visualization of code authorship
```
ext install eamodio.gitlens
```
Spanish language pack
```
ext install ms-ceintl.vscode-language-pack-es
```
### Recommended extensions
Material icon Theme
```
ext install pkief.material-icon-theme
```
Material UI Themes
```
ext install equinusocio.vsc-material-theme
```
## Built With
* [angularjs](https://angularjs.org/)

View File

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

View File

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

View File

@ -49,8 +49,13 @@ module.exports = Self => {
if (vnUser.twoFactor)
throw new ForbiddenError(null, 'REQUIRES_2FA');
}
return Self.validateLogin(user, password);
const validateLogin = await Self.validateLogin(user, password);
await Self.app.models.SignInLog.create({
token: validateLogin.token,
userFk: vnUser.id,
ip: ctx.req.ip
});
return validateLogin;
};
Self.passExpired = async vnUser => {

View File

@ -12,8 +12,21 @@ describe('VnUser Sign-in()', () => {
},
args: {}
};
const {VnUser, AccessToken} = models;
const {VnUser, AccessToken, SignInLog} = models;
describe('when credentials are correct', () => {
it('should return the token if user uses email', async() => {
let login = await VnUser.signIn(unauthCtx, 'salesAssistant@mydomain.com', 'nightmare');
let accessToken = await AccessToken.findById(login.token);
let ctx = {req: {accessToken: accessToken}};
let signInLog = await SignInLog.find({where: {token: accessToken.id}});
expect(signInLog.length).toEqual(1);
expect(signInLog[0].userFk).toEqual(accessToken.userId);
expect(login.token).toBeDefined();
await VnUser.logout(ctx.req.accessToken.id);
});
it('should return the token', async() => {
let login = await VnUser.signIn(unauthCtx, 'salesAssistant', 'nightmare');
let accessToken = await AccessToken.findById(login.token);

View File

@ -1,62 +1,74 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
require('../methods/notification/getList')(Self);
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 instance = ctx.instance;
const userId = ctx.options.accessToken.userId;
const user = await ctx.instance.userFk;
const modifiedUser = await getUserToModify(null, user, models);
if (userId != modifiedUser.id && userId != modifiedUser.bossFk)
throw new UserError('You dont have permission to modify this user');
});
let notificationFk;
let workerId;
Self.remoteMethod('deleteNotification', {
description: 'Deletes a notification subscription',
accepts: [
{
arg: 'ctx',
type: 'object',
http: {source: 'context'}
},
{
arg: 'notificationId',
type: 'number',
required: true
},
],
returns: {
type: 'object',
root: true
},
http: {
verb: 'POST',
path: '/deleteNotification'
if (instance) {
notificationFk = instance.notificationFk;
workerId = instance.userFk;
} else {
const notificationSubscription = await models.NotificationSubscription.findById(ctx.where.id);
notificationFk = notificationSubscription.notificationFk;
workerId = notificationSubscription.userFk;
}
});
Self.deleteNotification = async function(ctx, notificationId) {
const models = Self.app.models;
const user = ctx.req.accessToken.userId;
const modifiedUser = await getUserToModify(notificationId, null, models);
const worker = await models.Worker.findById(workerId, {fields: ['id', 'bossFk']});
const available = await Self.getAvailable(workerId);
const hasAcl = available.has(notificationFk);
if (user != modifiedUser.id && user != modifiedUser.bossFk)
throw new UserError('You dont have permission to modify this user');
await models.NotificationSubscription.destroyById(notificationId);
};
async function getUserToModify(notificationId, userFk, models) {
let userToModify = userFk;
if (notificationId) {
const subscription = await models.NotificationSubscription.findById(notificationId);
userToModify = subscription.userFk;
}
return await models.Worker.findOne({
fields: ['id', 'bossFk'],
where: {
id: userToModify
}
});
if (!hasAcl || (userId != worker.id && userId != worker.bossFk))
throw new UserError('The notification subscription of this worker cant be modified');
}
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;
};
};

View File

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

View File

@ -2,6 +2,7 @@ const vnModel = require('vn-loopback/common/models/vn-model');
const {Email} = require('vn-print');
const ForbiddenError = require('vn-loopback/util/forbiddenError');
const LoopBackContext = require('loopback-context');
const UserError = require('vn-loopback/util/user-error');
module.exports = function(Self) {
vnModel(Self);
@ -92,7 +93,11 @@ module.exports = function(Self) {
};
Self.on('resetPasswordRequest', async function(info) {
const url = await Self.app.models.Url.getUrl();
const loopBackContext = LoopBackContext.getCurrentContext();
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 recoverHashes = {
@ -108,7 +113,7 @@ module.exports = function(Self) {
const params = {
recipient: info.email,
lang: user.lang,
url: url.slice(0, -1) + recoverHash
url: origin + '/#!' + recoverHash
};
const options = Object.assign({}, info.options);
@ -119,12 +124,21 @@ module.exports = function(Self) {
return email.send();
});
Self.signInValidate = (user, userToken) => {
const [[key, value]] = Object.entries(Self.userUses(user));
if (userToken[key].toLowerCase().trim() !== value.toLowerCase().trim()) {
console.error('ERROR!!! - Signin with other user', userToken, user);
throw new UserError('Try again');
}
};
Self.validateLogin = async function(user, password) {
let loginInfo = Object.assign({password}, Self.userUses(user));
token = await Self.login(loginInfo, 'user');
const loginInfo = Object.assign({password}, Self.userUses(user));
const token = await Self.login(loginInfo, 'user');
const userToken = await token.user.get();
Self.signInValidate(user, userToken);
try {
await Self.app.models.Account.sync(userToken.name, password);
} catch (err) {
@ -220,8 +234,11 @@ module.exports = function(Self) {
class Mailer {
async send(verifyOptions, cb) {
const url = new URL(verifyOptions.verifyHref);
if (process.env.NODE_ENV) url.port = '';
const params = {
url: verifyOptions.verifyHref,
url: url.href,
recipient: verifyOptions.to
};

View File

@ -74,7 +74,7 @@ BEGIN
clientFk,
dued,
companyFk,
cplusInvoiceType477Fk
siiTypeInvoiceOutFk
)
SELECT
1,

View File

@ -96,7 +96,7 @@ BEGIN
clientFk,
dued,
companyFk,
cplusInvoiceType477Fk
siiTypeInvoiceOutFk
)
SELECT
1,

View File

@ -96,7 +96,7 @@ BEGIN
clientFk,
dued,
companyFk,
cplusInvoiceType477Fk
siiTypeInvoiceOutFk
)
SELECT
1,

View File

View File

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

View File

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

View File

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

View File

@ -1,3 +1,43 @@
CREATE SCHEMA IF NOT EXISTS `vn2008`;
CREATE OR REPLACE DEFINER=`root`@`localhost`
SQL SECURITY DEFINER
VIEW `vn`.`awbVolume`
AS SELECT `d`.`awbFk` AS `awbFk`,
`b`.`stickers` * `i`.`density` * IF(
`p`.`volume` > 0,
`p`.`volume`,
`p`.`width` * `p`.`depth` * IF(`p`.`height` = 0, `i`.`size` + 10, `p`.`height`)
) / (`vc`.`aerealVolumetricDensity` * 1000) AS `volume`,
`b`.`id` AS `buyFk`
FROM (
(
(
(
(
(
(
(
`vn`.`buy` `b`
JOIN `vn`.`item` `i` ON(`b`.`itemFk` = `i`.`id`)
)
JOIN `vn`.`itemType` `it` ON(`i`.`typeFk` = `it`.`id`)
)
JOIN `vn`.`packaging` `p` ON(`p`.`id` = `b`.`packagingFk`)
)
JOIN `vn`.`entry` `e` ON(`b`.`entryFk` = `e`.`id`)
)
JOIN `vn`.`travel` `t` ON(`t`.`id` = `e`.`travelFk`)
)
JOIN `vn`.`duaEntry` `de` ON(`de`.`entryFk` = `e`.`id`)
)
JOIN `vn`.`dua` `d` ON(`d`.`id` = `de`.`duaFk`)
)
JOIN `vn`.`volumeConfig` `vc`
)
WHERE `t`.`shipped` > makedate(year(`util`.`VN_CURDATE`()) - 1, 1);
CREATE OR REPLACE DEFINER=`root`@`localhost`
SQL SECURITY DEFINER
VIEW `vn2008`.`Compres`
@ -108,39 +148,3 @@ FROM (
LEFT JOIN `edi`.`supplier` `s` ON(`e`.`pro` = `s`.`supplier_id`)
);
CREATE OR REPLACE DEFINER=`root`@`localhost`
SQL SECURITY DEFINER
VIEW `vn`.`awbVolume`
AS SELECT `d`.`awbFk` AS `awbFk`,
`b`.`stickers` * `i`.`density` * IF(
`p`.`volume` > 0,
`p`.`volume`,
`p`.`width` * `p`.`depth` * IF(`p`.`height` = 0, `i`.`size` + 10, `p`.`height`)
) / (`vc`.`aerealVolumetricDensity` * 1000) AS `volume`,
`b`.`id` AS `buyFk`
FROM (
(
(
(
(
(
(
(
`vn`.`buy` `b`
JOIN `vn`.`item` `i` ON(`b`.`itemFk` = `i`.`id`)
)
JOIN `vn`.`itemType` `it` ON(`i`.`typeFk` = `it`.`id`)
)
JOIN `vn`.`packaging` `p` ON(`p`.`id` = `b`.`packagingFk`)
)
JOIN `vn`.`entry` `e` ON(`b`.`entryFk` = `e`.`id`)
)
JOIN `vn`.`travel` `t` ON(`t`.`id` = `e`.`travelFk`)
)
JOIN `vn`.`duaEntry` `de` ON(`de`.`entryFk` = `e`.`id`)
)
JOIN `vn`.`dua` `d` ON(`d`.`id` = `de`.`duaFk`)
)
JOIN `vn`.`volumeConfig` `vc`
)
WHERE `t`.`shipped` > makedate(year(`util`.`VN_CURDATE`()) - 1, 1);

View File

@ -0,0 +1,4 @@
UPDATE `salix`.`ACL`
SET `property` = 'state',
`model` = 'Ticket'
WHERE `property` = 'changeState';

View File

@ -0,0 +1,98 @@
-- Place your SQL code here
ALTER TABLE `vn`.`productionConfig` ADD shortageAddressFk int(11) COMMENT 'Consignatario por defecto para añadir un item de alta';
ALTER TABLE `vn`.`productionConfig` ADD CONSTRAINT productionConfig_FK FOREIGN KEY (shortageAddressFk) REFERENCES vn.address(id) ON DELETE RESTRICT ON UPDATE CASCADE;
ALTER TABLE `vn`.`sale` MODIFY COLUMN originalQuantity double(9,1) DEFAULT NULL NULL COMMENT 'Se utiliza para notificar a través de rocket los cambios de quantity';
INSERT INTO `salix`.`ACL` ( model, property, accessType, permission, principalType, principalId) VALUES( 'AddressShortage', '*', 'READ', 'ALLOW', 'ROLE', 'production');
-- vn.addressShortage definition
CREATE TABLE `vn`.`addressShortage` (
`addressFk` int(11) NOT NULL,
PRIMARY KEY (`addressFk`),
CONSTRAINT `addressShortage_FK` FOREIGN KEY (`addressFk`) REFERENCES `address` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`item_setVisibleDiscard`(
vItemFk INT,
vWarehouseFk INT,
vQuantity INT,
vAddressFk INT)
BEGIN
/**
* Procedimiento para dar dar de baja/alta un item, si vAddressFk es NULL se entiende que se da de alta y se toma el addressFk de la configuración
*
* @param vItemFk Identificador del ítem
* @param vWarehouseFk id del warehouse
* @param vQuantity a dar de alta/baja
* @param vAddressFk id address
*/
DECLARE vTicketFk INT;
DECLARE vClientFk INT;
DECLARE vDefaultCompanyFk INT;
DECLARE vCalc INT;
DECLARE vAddressShortage INT;
SELECT barcodeToItem(vItemFk) INTO vItemFk;
SELECT DEFAULT(companyFk) INTO vDefaultCompanyFk
FROM vn.ticket LIMIT 1;
IF vAddressFk IS NULL THEN
SELECT pc.shortageAddressFk INTO vAddressShortage
FROM productionConfig pc ;
ELSE
SET vAddressShortage = vAddressFk;
END IF;
SELECT a.clientFk INTO vClientFk
FROM address a
WHERE a.id = vAddressFk;
SELECT t.id INTO vTicketFk
FROM ticket t
JOIN address a ON a.id = t.addressFk
JOIN ticketState ts ON ts.ticketFk = t.id
WHERE t.warehouseFk = vWarehouseFk
AND a.id = vAddressShortage
AND DATE(t.shipped) = util.VN_CURDATE()
AND ts.code = 'DELIVERED'
LIMIT 1;
CALL cache.visible_refresh(vCalc, TRUE, vWarehouseFk);
IF vTicketFk IS NULL THEN
CALL ticket_add(
vClientFk,
util.VN_CURDATE(),
vWarehouseFk,
vDefaultCompanyFk,
vAddressFk,
NULL,
NULL,
util.VN_CURDATE(),
account.myUser_getId(),
FALSE,
vTicketFk);
END IF;
INSERT INTO sale(ticketFk, itemFk, concept, quantity)
SELECT vTicketFk,
vItemFk,
CONCAT(longName,' ', worker_getCode(), ' ', LEFT(CAST(util.VN_NOW() AS TIME),5)),
vQuantity
FROM item
WHERE id = vItemFk;
UPDATE cache.visible
SET visible = visible - vQuantity
WHERE calc_id = vCalc
AND item_id = vItemFk;
END$$
DELIMITER ;

View File

@ -21,11 +21,11 @@ DELETE FROM `salix`.`ACL`
'getSummary'
);
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalid`)
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalId`)
VALUES ('Claim','filter','READ','ALLOW','ROLE','claimViewer');
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalid`)
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalId`)
VALUES ('Claim','find','READ','ALLOW','ROLE','claimViewer');
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalid`)
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalId`)
VALUES ('Claim','findById','READ','ALLOW','ROLE','claimViewer');
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalid`)
VALUES ('Claim','getSummary','READ','ALLOW','ROLE','claimViewer');
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalId`)
VALUES ('Claim','getSummary','READ','ALLOW','ROLE','claimViewer');

View File

@ -0,0 +1,95 @@
ALTER TABLE `vn`.`client` MODIFY COLUMN `credit` decimal(10,2) unsigned DEFAULT 0.00 NOT NULL;
DELETE FROM `salix`.`ACL` WHERE `model` = 'Client' AND `property` = 'create';
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`client_beforeUpdate`
BEFORE UPDATE ON `client`
FOR EACH ROW
BEGIN
DECLARE vText VARCHAR(255) DEFAULT NULL;
DECLARE vPayMethodFk INT;
SET NEW.editorFk = account.myUser_getId();
IF NOT(NEW.credit <=> OLD.credit) THEN
INSERT INTO clientCredit
SET clientFk = NEW.id,
amount = NEW.credit,
workerFk = NEW.editorFk;
END IF;
-- Comprueba que el formato de los teléfonos es válido
IF !(NEW.phone <=> OLD.phone) AND (NEW.phone <> '') THEN
CALL pbx.phone_isValid(NEW.phone);
END IF;
IF !(NEW.mobile <=> OLD.mobile) AND (NEW.mobile <> '')THEN
CALL pbx.phone_isValid(NEW.mobile);
END IF;
SELECT id INTO vPayMethodFk
FROM vn.payMethod
WHERE code = 'bankDraft';
IF NEW.payMethodFk = vPayMethodFk AND NEW.dueDay = 0 THEN
SET NEW.dueDay = 5;
END IF;
-- Avisar al comercial si ha llegado la documentación sepa/core
IF NEW.hasSepaVnl AND !OLD.hasSepaVnl THEN
SET vText = 'Sepa de VNL';
END IF;
IF NEW.hasCoreVnl AND !OLD.hasCoreVnl THEN
SET vText = 'Core de VNL';
END IF;
IF vText IS NOT NULL
THEN
INSERT INTO mail(receiver, replyTo, `subject`, body)
SELECT
CONCAT(IF(ac.id,u.name, 'jgallego'), '@verdnatura.es'),
'administracion@verdnatura.es',
CONCAT('Cliente ', NEW.id),
CONCAT('Recibida la documentación: ', vText)
FROM worker w
LEFT JOIN account.user u ON w.id = u.id AND u.active
LEFT JOIN account.account ac ON ac.id = u.id
WHERE w.id = NEW.salesPersonFk;
END IF;
IF NEW.salespersonFk IS NULL AND OLD.salespersonFk IS NOT NULL THEN
IF (SELECT COUNT(clientFk)
FROM clientProtected
WHERE clientFk = NEW.id
) > 0 THEN
CALL util.throw("HAS_CLIENT_PROTECTED");
END IF;
END IF;
IF !(NEW.salesPersonFk <=> OLD.salesPersonFk) THEN
SET NEW.lastSalesPersonFk = IFNULL(NEW.salesPersonFk, OLD.salesPersonFk);
END IF;
IF !(NEW.businessTypeFk <=> OLD.businessTypeFk) AND (NEW.businessTypeFk = 'individual' OR OLD.businessTypeFk = 'individual') THEN
SET NEW.isTaxDataChecked = 0;
END IF;
END$$
DELIMITER ;
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`client_AfterInsert`
AFTER INSERT ON `client`
FOR EACH ROW
BEGIN
IF NEW.credit IS NOT NULL AND NEW.credit THEN
INSERT INTO clientCredit
SET clientFk = NEW.id,
workerFk = NEW.editorFk,
amount = NEW.credit;
END IF;
END$$
DELIMITER ;

View File

@ -0,0 +1,13 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`expeditionState_BeforeInsert`
BEFORE INSERT ON `expeditionState`
FOR EACH ROW
BEGIN
SET NEW.userFk = IFNULL(NEW.userFk, account.myUser_getId());
END$$
DELIMITER ;

View File

@ -0,0 +1,55 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_setState`(
vSelf INT,
vStateCode VARCHAR(255) COLLATE utf8_general_ci
)
BEGIN
/**
* Modifica el estado de un ticket si se cumplen las condiciones necesarias.
*
* @param vSelf el id del ticket
* @param vStateCode estado a modificar del ticket
*/
DECLARE vticketAlertLevel INT;
DECLARE vTicketStateCode VARCHAR(255);
DECLARE vCanChangeState BOOL;
DECLARE vPackedAlertLevel INT;
DECLARE vZoneFk INT;
SELECT s.alertLevel, s.`code`, t.zoneFk
INTO vticketAlertLevel, vTicketStateCode, vZoneFk
FROM state s
JOIN ticketTracking tt ON tt.stateFk = s.id
JOIN ticket t ON t.id = tt.ticketFk
WHERE tt.ticketFk = vSelf
ORDER BY tt.created DESC
LIMIT 1;
SELECT id INTO vPackedAlertLevel FROM alertLevel WHERE code = 'PACKED';
IF vStateCode = 'OK' AND vZoneFk IS NULL THEN
CALL util.throw('ASSIGN_ZONE_FIRST');
END IF;
SET vCanChangeState = (
vStateCode <> 'ON_CHECKING' OR
vticketAlertLevel < vPackedAlertLevel
)AND NOT (
vTicketStateCode IN ('CHECKED', 'CHECKING')
AND vStateCode IN ('PREPARED', 'ON_PREPARATION')
);
IF vCanChangeState THEN
INSERT INTO ticketTracking (stateFk, ticketFk, workerFk)
SELECT id, vSelf, account.myUser_getId()
FROM state
WHERE `code` = vStateCode COLLATE utf8_unicode_ci;
IF vStateCode = 'PACKED' THEN
CALL ticket_doCmr(vSelf);
END IF;
ELSE
CALL util.throw('INCORRECT_TICKET_STATE');
END IF;
END$$
DELIMITER ;

View File

@ -0,0 +1,6 @@
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('CplusRectificationType', '*', 'READ', 'ALLOW', 'ROLE', 'administrative'),
('SiiTypeInvoiceOut', '*', 'READ', 'ALLOW', 'ROLE', 'administrative'),
('InvoiceCorrectionType', '*', 'READ', 'ALLOW', 'ROLE', 'administrative'),
('InvoiceOut', 'transferInvoice', 'WRITE', 'ALLOW', 'ROLE', 'administrative');

View File

@ -0,0 +1 @@
CALL `account`.`role_sync`();

View File

@ -0,0 +1,20 @@
--
-- Table structure for table `signInLog`
-- Description: log to debug cross-login error
--
DROP TABLE IF EXISTS `account`.`signInLog`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `account`.`signInLog` (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`token` varchar(255) NOT NULL ,
`userFk` int(10) unsigned DEFAULT NULL,
`creationDate` timestamp NULL DEFAULT current_timestamp(),
`ip` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
KEY `userFk` (`userFk`),
CONSTRAINT `signInLog_ibfk_1` FOREIGN KEY (`userFk`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
);

View File

@ -0,0 +1,2 @@
ALTER TABLE account.sambaConfig
ADD userDn varchar(255) NOT NULL COMMENT 'Base DN for users without domain DN part';

View File

View File

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

View File

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

View File

@ -0,0 +1,152 @@
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;
CREATE OR REPLACE 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 <= vDateToAdvance
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,
dest.agencyModeFk,
origin.futureAgency,
origin.agencyModeFk futureAgencyModeFk,
dest.lines,
dest.liters,
origin.futureLines - origin.hasStock AS notMovableLines,
(origin.futureLines = origin.hasStock) AS isFullMovable,
dest.zoneFk,
origin.futureZoneFk,
origin.futureZoneName,
origin.classColor futureClassColor,
dest.classColor,
origin.clientFk futureClientFk,
origin.addressFk futureAddressFk,
origin.warehouseFk futureWarehouseFk,
origin.companyFk futureCompanyFk,
IFNULL(dest.nickname, origin.nickname) nickname,
dest.landed
FROM (
SELECT
s.ticketFk,
c.salesPersonFk workerFk,
t.shipped,
t.totalWithVat,
st.name futureState,
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,
t.clientFk,
t.nickname,
t.addressFk,
t.warehouseFk,
t.companyFk,
t.agencyModeFk
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
LEFT JOIN (
SELECT
t.id ticketFk,
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,
t.clientFk,
t.nickname,
t.addressFk,
t.zoneFk,
t.warehouseFk,
t.companyFk,
t.landed,
t.agencyModeFk
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;
DROP TEMPORARY TABLE tmp.stock;
END$$
DELIMITER ;

View File

@ -275,13 +275,13 @@ INSERT INTO `cplusInvoiceType472` VALUES (1,'F1 - Factura'),(2,'F2 - Factura sim
UNLOCK TABLES;
--
-- Dumping data for table `cplusInvoiceType477`
-- Dumping data for table `siiTypeInvoiceOut`
--
LOCK TABLES `cplusInvoiceType477` WRITE;
/*!40000 ALTER TABLE `cplusInvoiceType477` DISABLE KEYS */;
INSERT INTO `cplusInvoiceType477` VALUES (1,'F1 - Factura'),(2,'F2 - Factura simplificada (ticket)'),(3,'F3 - Factura emitida en sustitución de facturas simplificadas facturadas y declaradas'),(4,'F4 - Asiento resumen de facturas'),(5,'R1 - Factura rectificativa (Art. 80.1, 80.2 y error fundado en derecho)'),(6,'R2 - Factura rectificativa (Art. 80.3)'),(7,'R3 - Factura rectificativa (Art. 80.4)'),(8,'R4 - Factura rectificativa (Resto)'),(9,'R5 - Factura rectificativa en facturas simplificadas');
/*!40000 ALTER TABLE `cplusInvoiceType477` ENABLE KEYS */;
LOCK TABLES `siiTypeInvoiceOut` WRITE;
/*!40000 ALTER TABLE `siiTypeInvoiceOut` DISABLE KEYS */;
INSERT INTO `siiTypeInvoiceOut` VALUES (1,'F1 - Factura'),(2,'F2 - Factura simplificada (ticket)'),(3,'F3 - Factura emitida en sustitución de facturas simplificadas facturadas y declaradas'),(4,'F4 - Asiento resumen de facturas'),(5,'R1 - Factura rectificativa (Art. 80.1, 80.2 y error fundado en derecho)'),(6,'R2 - Factura rectificativa (Art. 80.3)'),(7,'R3 - Factura rectificativa (Art. 80.4)'),(8,'R4 - Factura rectificativa (Resto)'),(9,'R5 - Factura rectificativa en facturas simplificadas');
/*!40000 ALTER TABLE `siiTypeInvoiceOut` ENABLE KEYS */;
UNLOCK TABLES;
--

View File

@ -367,7 +367,7 @@ INSERT INTO `vn`.`client`(`id`,`name`,`fi`,`socialName`,`contact`,`street`,`city
(1105, 'Max Eisenhardt', '251628698', 'MAGNETO', 'Rogue', 'UNKNOWN WHEREABOUTS', 'Gotham', 46460, 1111111111, 222222222, 1, 'MaxEisenhardt@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 300, 8, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 18, 0, 1, 'florist','normal'),
(1106, 'DavidCharlesHaller', '53136686Q', 'LEGION', 'Charles Xavier', 'CITY OF NEW YORK, NEW YORK, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'DavidCharlesHaller@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 0, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 19, 0, 1, 'florist','normal'),
(1107, 'Hank Pym', '09854837G', 'ANT MAN', 'Hawk', 'ANTHILL, SAN FRANCISCO, CALIFORNIA', 'Gotham', 46460, 1111111111, 222222222, 1, 'HankPym@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, 19, 0, 1, 'florist','normal'),
(1108, 'Charles Xavier', '22641921P', 'PROFESSOR X', 'Beast', '3800 VICTORY PKWY, CINCINNATI, OH 45207, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'CharlesXavier@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 19, 0, 1, 'florist','normal'),
(1108, 'Charles Xavier', '22641921P', 'PROFESSOR X', 'Beast', '3800 VICTORY PKWY, CINCINNATI, OH 45207, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'CharlesXavier@mydomain.com', NULL, 0, 1234567890, 0, 5, 1, 300, 13, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 19, 0, 1, 'florist','normal'),
(1109, 'Bruce Banner', '16104829E', 'HULK', 'Black widow', 'SOMEWHERE IN NEW YORK', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceBanner@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, 9, 0, 1, 'florist','normal'),
(1110, 'Jessica Jones', '58282869H', 'JESSICA JONES', 'Luke Cage', 'NYCC 2015 POSTER', 'Gotham', 46460, 1111111111, 222222222, 1, 'JessicaJones@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, NULL, 0, 1, 'florist','normal'),
(1111, 'Missing', NULL, 'MISSING MAN', 'Anton', 'THE SPACE, UNIVERSE FAR AWAY', 'Gotham', 46460, 1111111111, 222222222, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, 0, 1, 0, NULL, 1, 0, NULL, 0, 1, 'others','normal'),
@ -405,7 +405,7 @@ INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `pr
(5, 'Max Eisenhardt', 'Unknown Whereabouts', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1105, 2, NULL, NULL, 0, 1),
(6, 'DavidCharlesHaller', 'Evil hideout', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1106, 2, NULL, NULL, 0, 1),
(7, 'Hank Pym', 'Anthill', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1107, 2, NULL, NULL, 0, 1),
(8, 'Charles Xavier', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 1),
(8, 'Charles Xavier', '3800 Victory Pkwy, Cincinnati, OH 45207, USA', 'Gotham', 46460, 5, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 1),
(9, 'Bruce Banner', 'Somewhere in New York', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1109, 2, NULL, NULL, 0, 1),
(10, 'Jessica Jones', 'NYCC 2015 Poster', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1110, 2, NULL, NULL, 0, 1),
(11, 'Missing', 'The space', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1111, 10, NULL, NULL, 0, 1),
@ -437,7 +437,7 @@ INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `pr
(125, 'The plastic cell', 'address 25', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1105, 2, NULL, NULL, 0, 0),
(126, 'Many places', 'address 26', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1106, 2, NULL, NULL, 0, 0),
(127, 'Your pocket', 'address 27', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1107, 2, NULL, NULL, 0, 0),
(128, 'Cerebro', 'address 28', 'Gotham', 46460, 1, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 0),
(128, 'Cerebro', 'address 28', 'Gotham', 46460, 5, 1111111111, 222222222, 1, 1108, 2, NULL, NULL, 0, 0),
(129, 'Luke Cages Bar', 'address 29', 'Gotham', 'EC170150', 1, 1111111111, 222222222, 1, 1110, 2, NULL, NULL, 0, 0),
(130, 'Non valid address', 'address 30', 'Gotham', 46460, 1, 1111111111, 222222222, 0, 1101, 2, NULL, NULL, 0, 0);
@ -470,22 +470,22 @@ CREATE TEMPORARY TABLE tmp.address
WHERE `defaultAddressFk` IS NULL;
DROP TEMPORARY TABLE tmp.address;
INSERT INTO `vn`.`clientCredit`(`id`, `clientFk`, `workerFk`, `amount`, `created`)
INSERT INTO `vn`.`clientCredit`(`clientFk`, `workerFk`, `amount`, `created`)
VALUES
(1 , 1101, 5, 300, DATE_ADD(util.VN_CURDATE(), INTERVAL -11 MONTH)),
(2 , 1101, 5, 900, DATE_ADD(util.VN_CURDATE(), INTERVAL -10 MONTH)),
(3 , 1101, 5, 800, DATE_ADD(util.VN_CURDATE(), INTERVAL -9 MONTH)),
(4 , 1101, 5, 700, DATE_ADD(util.VN_CURDATE(), INTERVAL -8 MONTH)),
(5 , 1101, 5, 600, DATE_ADD(util.VN_CURDATE(), INTERVAL -7 MONTH)),
(6 , 1101, 5, 500, DATE_ADD(util.VN_CURDATE(), INTERVAL -6 MONTH)),
(7 , 1101, 5, 400, DATE_ADD(util.VN_CURDATE(), INTERVAL -5 MONTH)),
(8 , 1101, 9, 300, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH)),
(9 , 1101, 9, 200, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH)),
(10, 1101, 9, 100, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH)),
(11, 1101, 9, 50 , DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)),
(12, 1102, 9, 800, util.VN_CURDATE()),
(14, 1104, 9, 90 , util.VN_CURDATE()),
(15, 1105, 9, 90 , util.VN_CURDATE());
(1101, 5, 300, DATE_ADD(util.VN_CURDATE(), INTERVAL -11 MONTH)),
(1101, 5, 900, DATE_ADD(util.VN_CURDATE(), INTERVAL -10 MONTH)),
(1101, 5, 800, DATE_ADD(util.VN_CURDATE(), INTERVAL -9 MONTH)),
(1101, 5, 700, DATE_ADD(util.VN_CURDATE(), INTERVAL -8 MONTH)),
(1101, 5, 600, DATE_ADD(util.VN_CURDATE(), INTERVAL -7 MONTH)),
(1101, 5, 500, DATE_ADD(util.VN_CURDATE(), INTERVAL -6 MONTH)),
(1101, 5, 400, DATE_ADD(util.VN_CURDATE(), INTERVAL -5 MONTH)),
(1101, 9, 300, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH)),
(1101, 9, 200, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH)),
(1101, 9, 100, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH)),
(1101, 9, 50 , DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)),
(1102, 9, 800, util.VN_CURDATE()),
(1104, 9, 90 , util.VN_CURDATE()),
(1105, 9, 90 , util.VN_CURDATE());
INSERT INTO `vn`.`clientCreditLimit`(`id`, `maxAmount`, `roleFk`)
VALUES
@ -604,7 +604,7 @@ INSERT INTO `vn`.`invoiceOutSerial` (`code`, `description`, `isTaxed`, `taxAreaF
INSERT INTO `vn`.`invoiceOut`(`id`, `serial`, `amount`, `issued`,`clientFk`, `created`, `companyFk`, `dued`, `booked`, `bankFk`, `hasPdf`)
VALUES
(1, 'T', 1014.24, util.VN_CURDATE(), 1101, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0),
(1, 'T', 1026.24, util.VN_CURDATE(), 1101, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0),
(2, 'T', 121.36, util.VN_CURDATE(), 1102, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0),
(3, 'T', 8.88, util.VN_CURDATE(), 1103, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0),
(4, 'T', 8.88, util.VN_CURDATE(), 1103, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0),
@ -2003,6 +2003,10 @@ UPDATE `vn`.`business` b
SET b.`departmentFk` = 43
WHERE b.id IN(18, 19);
UPDATE `vn`.`business` b
SET b.`started` = b.`started` - INTERVAL 100 DAY
WHERE b.id = 1107;
INSERT INTO `vn`.`workCenterHoliday` (`workCenterFk`, `days`, `year`)
VALUES
('1', '27.5', YEAR(util.VN_CURDATE())),
@ -2051,22 +2055,22 @@ INSERT INTO `vn`.`absenceType` (`id`, `name`, `rgb`, `code`, `holidayEntitlement
INSERT INTO `vn`.`calendar` (`businessFk`, `dayOffTypeFk`, `dated`)
VALUES
(1, 6, IF(MONTH(util.VN_CURDATE()) = 12 AND DAY(util.VN_CURDATE()) > 10, DATE_ADD(util.VN_CURDATE(), INTERVAL -10 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL 10 DAY))),
(1106, 1, IF(MONTH(util.VN_CURDATE()) = 12 AND DAY(util.VN_CURDATE()) > 10, DATE_ADD(util.VN_CURDATE(), INTERVAL -10 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL 10 DAY))),
(1106, 1, IF(MONTH(util.VN_CURDATE()) = 12 AND DAY(util.VN_CURDATE()) > 10, DATE_ADD(util.VN_CURDATE(), INTERVAL -11 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL 11 DAY))),
(1106, 1, IF(MONTH(util.VN_CURDATE()) = 12 AND DAY(util.VN_CURDATE()) > 10, DATE_ADD(util.VN_CURDATE(), INTERVAL -12 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL 12 DAY))),
(1106, 1, IF(MONTH(util.VN_CURDATE()) = 12 AND DAY(util.VN_CURDATE()) > 10, DATE_ADD(util.VN_CURDATE(), INTERVAL -20 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL 20 DAY))),
(1106, 2, IF(MONTH(util.VN_CURDATE()) >= 1 AND DAY(util.VN_CURDATE()) > 20, DATE_ADD(util.VN_CURDATE(), INTERVAL -13 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL 8 DAY))),
(1106, 1, IF(MONTH(util.VN_CURDATE()) >= 1 AND DAY(util.VN_CURDATE()) > 20, DATE_ADD(util.VN_CURDATE(), INTERVAL -14 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL 9 DAY))),
(1106, 2, IF(MONTH(util.VN_CURDATE()) >= 1 AND DAY(util.VN_CURDATE()) > 20, DATE_ADD(util.VN_CURDATE(), INTERVAL -15 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL 7 DAY))),
(1107, 1, IF(MONTH(util.VN_CURDATE()) = 12 AND DAY(util.VN_CURDATE()) > 10, DATE_ADD(util.VN_CURDATE(), INTERVAL -10 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL 10 DAY))),
(1107, 1, IF(MONTH(util.VN_CURDATE()) = 12 AND DAY(util.VN_CURDATE()) > 10, DATE_ADD(util.VN_CURDATE(), INTERVAL -11 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL 11 DAY))),
(1107, 1, IF(MONTH(util.VN_CURDATE()) = 12 AND DAY(util.VN_CURDATE()) > 10, DATE_ADD(util.VN_CURDATE(), INTERVAL -12 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL 12 DAY))),
(1107, 1, IF(MONTH(util.VN_CURDATE()) = 12 AND DAY(util.VN_CURDATE()) > 10, DATE_ADD(util.VN_CURDATE(), INTERVAL -20 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL 20 DAY))),
(1107, 2, IF(MONTH(util.VN_CURDATE()) >= 1 AND DAY(util.VN_CURDATE()) > 20, DATE_ADD(util.VN_CURDATE(), INTERVAL -13 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL 8 DAY))),
(1107, 1, IF(MONTH(util.VN_CURDATE()) >= 1 AND DAY(util.VN_CURDATE()) > 20, DATE_ADD(util.VN_CURDATE(), INTERVAL -14 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL 9 DAY))),
(1107, 2, IF(MONTH(util.VN_CURDATE()) >= 1 AND DAY(util.VN_CURDATE()) > 20, DATE_ADD(util.VN_CURDATE(), INTERVAL -15 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL 7 DAY))),
(1107, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL - 16 DAY));
(1, 6, IF(MONTH(util.VN_CURDATE()) = 12 AND DAY(util.VN_CURDATE()) > 10, util.VN_CURDATE() - INTERVAL 10 DAY, util.VN_CURDATE() + INTERVAL 10 DAY)),
(1106, 1, IF(MONTH(util.VN_CURDATE()) = 12 AND DAY(util.VN_CURDATE()) > 10, util.VN_CURDATE() - INTERVAL 10 DAY, util.VN_CURDATE() + INTERVAL 10 DAY)),
(1106, 1, IF(MONTH(util.VN_CURDATE()) = 12 AND DAY(util.VN_CURDATE()) > 10, util.VN_CURDATE() - INTERVAL 11 DAY, util.VN_CURDATE() + INTERVAL 11 DAY)),
(1106, 1, IF(MONTH(util.VN_CURDATE()) = 12 AND DAY(util.VN_CURDATE()) > 10, util.VN_CURDATE() - INTERVAL 12 DAY, util.VN_CURDATE() + INTERVAL 12 DAY)),
(1106, 1, IF(MONTH(util.VN_CURDATE()) = 12 AND DAY(util.VN_CURDATE()) > 10, util.VN_CURDATE() - INTERVAL 20 DAY, util.VN_CURDATE() + INTERVAL 20 DAY)),
(1106, 2, IF(MONTH(util.VN_CURDATE()) >= 1 AND DAY(util.VN_CURDATE()) > 20, util.VN_CURDATE() - INTERVAL 13 DAY, util.VN_CURDATE() + INTERVAL 8 DAY)),
(1106, 1, IF(MONTH(util.VN_CURDATE()) >= 1 AND DAY(util.VN_CURDATE()) > 20, util.VN_CURDATE() - INTERVAL 14 DAY, util.VN_CURDATE() + INTERVAL 9 DAY)),
(1106, 2, IF(MONTH(util.VN_CURDATE()) >= 1 AND DAY(util.VN_CURDATE()) > 20, util.VN_CURDATE() - INTERVAL 15 DAY, util.VN_CURDATE() + INTERVAL 7 DAY)),
(1107, 1, IF(MONTH(util.VN_CURDATE()) = 12 AND DAY(util.VN_CURDATE()) > 10, util.VN_CURDATE() - INTERVAL 10 DAY, util.VN_CURDATE() + INTERVAL 10 DAY)),
(1107, 1, IF(MONTH(util.VN_CURDATE()) = 12 AND DAY(util.VN_CURDATE()) > 10, util.VN_CURDATE() - INTERVAL 11 DAY, util.VN_CURDATE() + INTERVAL 11 DAY)),
(1107, 1, IF(MONTH(util.VN_CURDATE()) = 12 AND DAY(util.VN_CURDATE()) > 10, util.VN_CURDATE() - INTERVAL 12 DAY, util.VN_CURDATE() + INTERVAL 12 DAY)),
(1107, 1, IF(MONTH(util.VN_CURDATE()) = 12 AND DAY(util.VN_CURDATE()) > 10, util.VN_CURDATE() - INTERVAL 20 DAY, util.VN_CURDATE() + INTERVAL 20 DAY)),
(1107, 2, IF(MONTH(util.VN_CURDATE()) >= 1 AND DAY(util.VN_CURDATE()) > 20, util.VN_CURDATE() - INTERVAL 13 DAY, util.VN_CURDATE() + INTERVAL 8 DAY)),
(1107, 1, IF(MONTH(util.VN_CURDATE()) >= 1 AND DAY(util.VN_CURDATE()) > 20, util.VN_CURDATE() - INTERVAL 14 DAY, util.VN_CURDATE() + INTERVAL 9 DAY)),
(1107, 2, IF(MONTH(util.VN_CURDATE()) >= 1 AND DAY(util.VN_CURDATE()) > 20, util.VN_CURDATE() - INTERVAL 15 DAY, util.VN_CURDATE() + INTERVAL 7 DAY)),
(1107, 2, util.VN_CURDATE() - INTERVAL 16 DAY);
INSERT INTO `vn`.`smsConfig` (`id`, `uri`, `title`, `apiKey`)
VALUES
@ -2754,9 +2758,9 @@ INSERT INTO `vn`.`sectorCollectionSaleGroup` (`sectorCollectionFk`, `saleGroupFk
VALUES
(1, 1);
INSERT INTO `vn`.`workerTimeControlConfig` (`id`, `dayBreak`, `dayBreakDriver`, `shortWeekBreak`, `longWeekBreak`, `weekScope`, `mailPass`, `mailHost`, `mailSuccessFolder`, `mailErrorFolder`, `mailUser`, `minHoursToBreak`, `breakHours`, `hoursCompleteWeek`, `startNightlyHours`, `endNightlyHours`, `maxTimePerDay`, `breakTime`, `timeToBreakTime`, `dayMaxTime`, `shortWeekDays`, `longWeekDays`, `teleworkingStart`, `teleworkingStartBreakTime`)
INSERT INTO `vn`.`workerTimeControlConfig` (`id`, `dayBreak`, `dayBreakDriver`, `shortWeekBreak`, `longWeekBreak`, `weekScope`, `mailPass`, `mailHost`, `mailSuccessFolder`, `mailErrorFolder`, `mailUser`, `minHoursToBreak`, `breakHours`, `hoursCompleteWeek`, `startNightlyHours`, `endNightlyHours`, `maxTimePerDay`, `breakTime`, `timeToBreakTime`, `dayMaxTime`, `shortWeekDays`, `longWeekDays`, `teleworkingStart`, `teleworkingStartBreakTime`, `maxTimeToBreak`, `maxWorkShortCycle`, `maxWorkLongCycle`)
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, 28800, 32400);
(1, 43200, 32400, 129600, 259200, 1080000, '', 'imap.verdnatura.es', 'Leidos.exito', 'Leidos.error', 'timeControl', 5.00, 0.33, 40, '22:00:00', '06:00:00', 72000, 1200, 18000, 72000, 6, 13, 28800, 32400, 3600, 561600, 950400);
INSERT INTO `vn`.`host` (`id`, `code`, `description`, `warehouseFk`, `bankFk`)
VALUES
@ -2784,6 +2788,11 @@ INSERT INTO `util`.`notification` (`id`, `name`, `description`)
INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`)
VALUES
(1, 9),
(1, 1),
(2, 1),
(3, 9),
(4, 1),
(5, 9),
(6, 9);
INSERT INTO `util`.`notificationQueue` (`id`, `notificationFk`, `params`, `authorFk`, `status`, `created`)
@ -2796,6 +2805,8 @@ INSERT INTO `util`.`notificationSubscription` (`notificationFk`, `userFk`)
VALUES
(1, 1109),
(1, 1110),
(2, 1110),
(4, 1110),
(2, 1109),
(1, 9),
(1, 3),
@ -2876,7 +2887,9 @@ INSERT INTO `vn`.`report` (`id`, `name`, `paperSizeFk`, `method`)
INSERT INTO `vn`.`payDemDetail` (`id`, `detail`)
VALUES
(1, 1);
(1, 1),
(2, 20),
(7, 1);
INSERT INTO `vn`.`workerConfig` (`id`, `businessUpdated`, `roleFk`, `payMethodFk`, `businessTypeFk`)
VALUES
@ -2977,3 +2990,9 @@ INSERT INTO vn.XDiario (id, ASIEN, FECHA, SUBCTA, CONTRA, CONCEPTO, EURODEBE, EU
INSERT INTO `vn`.`mistakeType` (`id`, `description`)
VALUES
(1, 'Incorrect quantity');
INSERT INTO `vn`.`invoiceCorrectionType` (`id`, `description`)
VALUES
(1, 'Error in VAT calculation'),
(2, 'Error in sales details'),
(3, 'Error in customer data');

View File

@ -2352,6 +2352,90 @@ BEGIN
END IF;
END ;;
DELIMITER ;
DELIMITER ;;
CREATE DEFINER=`root`@`localhost` FUNCTION `account`.`user_hasRoutinePriv`(vType ENUM('PROCEDURE', 'FUNCTION'),
vChain VARCHAR(100),
vUserFk INT
) RETURNS tinyint(1)
READS SQL DATA
BEGIN
/**
* Search if the user has privileges on routines.
*
* @param vType procedure or function
* @param vChain string passed with this syntax dbName.tableName
* @param vUserFk user to ckeck
* @return vHasPrivilege
*/
DECLARE vHasPrivilege BOOL DEFAULT FALSE;
DECLARE vDb VARCHAR(50);
DECLARE vObject VARCHAR(50);
DECLARE vChainExists BOOL;
DECLARE vExecutePriv INT DEFAULT 262144;
-- 262144 = CONV(1000000000000000000, 2, 10)
-- 1000000000000000000 execution permission expressed in binary base
SET vDb = SUBSTRING_INDEX(vChain, '.', 1);
SET vChain = SUBSTRING(vChain, LENGTH(vDb) + 2);
SET vObject = SUBSTRING_INDEX(vChain, '.', 1);
SELECT COUNT(*) INTO vChainExists
FROM mysql.proc
WHERE db = vDb
AND `name` = vObject
AND `type` = vType
LIMIT 1;
IF NOT vChainExists THEN
RETURN FALSE;
END IF;
DROP TEMPORARY TABLE IF EXISTS tRole;
CREATE TEMPORARY TABLE tRole
(INDEX (`name`))
ENGINE = MEMORY
SELECT r.`name`
FROM user u
JOIN roleRole rr ON rr.role = u.role
JOIN `role` r ON r.id = rr.inheritsFrom
WHERE u.id = vUserFk;
SELECT TRUE INTO vHasPrivilege
FROM mysql.global_priv gp
JOIN tRole tr ON tr.name = gp.`User`
OR CONCAT('$', tr.name) = gp.`User`
WHERE JSON_VALUE(gp.Priv, '$.access') >= vExecutePriv
AND gp.Host = ''
LIMIT 1;
IF NOT vHasPrivilege THEN
SELECT TRUE INTO vHasPrivilege
FROM mysql.db db
JOIN tRole tr ON tr.name = db.`User`
WHERE db.Db = vDb
AND db.Execute_priv = 'Y';
END IF;
IF NOT vHasPrivilege THEN
SELECT TRUE INTO vHasPrivilege
FROM mysql.procs_priv pp
JOIN tRole tr ON tr.name = pp.`User`
WHERE pp.Db = vDb
AND pp.Routine_name = vObject
AND pp.Routine_type = vType
AND pp.Proc_priv = 'Execute'
LIMIT 1;
END IF;
DROP TEMPORARY TABLE tRole;
RETURN vHasPrivilege;
END ;;
DELIMITER ;
/*!50003 SET sql_mode = @saved_sql_mode */ ;
/*!50003 SET character_set_client = @saved_cs_client */ ;
/*!50003 SET character_set_results = @saved_cs_results */ ;
@ -25993,13 +26077,13 @@ CREATE TABLE `cplusInvoiceType472` (
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Table structure for table `cplusInvoiceType477`
-- Table structure for table `siiTypeInvoiceOut`
--
DROP TABLE IF EXISTS `cplusInvoiceType477`;
DROP TABLE IF EXISTS `siiTypeInvoiceOut`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `cplusInvoiceType477` (
CREATE TABLE `siiTypeInvoiceOut` (
`id` int(10) unsigned NOT NULL,
`description` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
PRIMARY KEY (`id`)
@ -29450,16 +29534,16 @@ CREATE TABLE `invoiceCorrection` (
`correctingFk` int(10) unsigned NOT NULL COMMENT 'Factura rectificativa',
`correctedFk` int(10) unsigned NOT NULL COMMENT 'Factura rectificada',
`cplusRectificationTypeFk` int(10) unsigned NOT NULL,
`cplusInvoiceType477Fk` int(10) unsigned NOT NULL,
`siiTypeInvoiceOutFk` int(10) unsigned NOT NULL,
`invoiceCorrectionTypeFk` int(11) NOT NULL DEFAULT 3,
PRIMARY KEY (`correctingFk`),
KEY `correctedFk_idx` (`correctedFk`),
KEY `invoiceCorrection_ibfk_1_idx` (`cplusRectificationTypeFk`),
KEY `cplusInvoiceTyoeFk_idx` (`cplusInvoiceType477Fk`),
KEY `cplusInvoiceTyoeFk_idx` (`siiTypeInvoiceOutFk`),
KEY `invoiceCorrectionTypeFk_idx` (`invoiceCorrectionTypeFk`),
CONSTRAINT `corrected_fk` FOREIGN KEY (`correctedFk`) REFERENCES `invoiceOut` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `correcting_fk` FOREIGN KEY (`correctingFk`) REFERENCES `invoiceOut` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `cplusInvoiceTyoeFk` FOREIGN KEY (`cplusInvoiceType477Fk`) REFERENCES `cplusInvoiceType477` (`id`) ON UPDATE CASCADE,
CONSTRAINT `cplusInvoiceTyoeFk` FOREIGN KEY (`siiTypeInvoiceOutFk`) REFERENCES `siiTypeInvoiceOut` (`id`) ON UPDATE CASCADE,
CONSTRAINT `invoiceCorrectionType_Fk33` FOREIGN KEY (`invoiceCorrectionTypeFk`) REFERENCES `invoiceCorrectionType` (`id`) ON UPDATE CASCADE,
CONSTRAINT `invoiceCorrection_ibfk_1` FOREIGN KEY (`cplusRectificationTypeFk`) REFERENCES `cplusRectificationType` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci COMMENT='Relacion entre las facturas rectificativas y las rectificadas.';
@ -30130,7 +30214,7 @@ CREATE TABLE `invoiceOut` (
`companyFk` int(10) unsigned NOT NULL DEFAULT 442,
`hasPdf` tinyint(3) unsigned NOT NULL DEFAULT 0,
`booked` date DEFAULT NULL,
`cplusInvoiceType477Fk` int(10) unsigned NOT NULL DEFAULT 1,
`siiTypeInvoiceOutFk` int(10) unsigned NOT NULL DEFAULT 1,
`cplusTaxBreakFk` int(10) unsigned NOT NULL DEFAULT 1,
`cplusSubjectOpFk` int(10) unsigned NOT NULL DEFAULT 1,
`cplusTrascendency477Fk` int(10) unsigned NOT NULL DEFAULT 1,
@ -30140,13 +30224,13 @@ CREATE TABLE `invoiceOut` (
KEY `Id_Cliente` (`clientFk`),
KEY `empresa_id` (`companyFk`),
KEY `Fecha` (`issued`),
KEY `Facturas_ibfk_2_idx` (`cplusInvoiceType477Fk`),
KEY `Facturas_ibfk_2_idx` (`siiTypeInvoiceOutFk`),
KEY `Facturas_ibfk_3_idx` (`cplusSubjectOpFk`),
KEY `Facturas_ibfk_4_idx` (`cplusTaxBreakFk`),
KEY `Facturas_ibfk_5_idx` (`cplusTrascendency477Fk`),
KEY `Facturas_idx_Vencimiento` (`dued`),
KEY `invoiceOut_serial` (`serial`),
CONSTRAINT `invoiceOut_ibfk_2` FOREIGN KEY (`cplusInvoiceType477Fk`) REFERENCES `cplusInvoiceType477` (`id`) ON UPDATE CASCADE,
CONSTRAINT `invoiceOut_ibfk_2` FOREIGN KEY (`siiTypeInvoiceOutFk`) REFERENCES `siiTypeInvoiceOut` (`id`) ON UPDATE CASCADE,
CONSTRAINT `invoiceOut_ibfk_3` FOREIGN KEY (`cplusSubjectOpFk`) REFERENCES `cplusSubjectOp` (`id`) ON UPDATE CASCADE,
CONSTRAINT `invoiceOut_ibfk_4` FOREIGN KEY (`cplusTaxBreakFk`) REFERENCES `cplusTaxBreak` (`id`) ON UPDATE CASCADE,
CONSTRAINT `invoiceOut_serial` FOREIGN KEY (`serial`) REFERENCES `invoiceOutSerial` (`code`),
@ -30308,7 +30392,7 @@ CREATE TABLE `invoiceOutSerial` (
`isTaxed` tinyint(1) NOT NULL DEFAULT 1,
`taxAreaFk` varchar(15) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL DEFAULT 'NATIONAL',
`isCEE` tinyint(1) NOT NULL DEFAULT 0,
`cplusInvoiceType477Fk` int(10) unsigned DEFAULT 1,
`siiTypeInvoiceOutFk` int(10) unsigned DEFAULT 1,
`footNotes` longtext DEFAULT NULL,
`isRefEditable` tinyint(4) NOT NULL DEFAULT 0,
`type` enum('global','quick') DEFAULT NULL,
@ -58288,7 +58372,7 @@ BEGIN
io.cplusTrascendency477Fk AS TIPOCLAVE,
io.cplusTaxBreakFk AS TIPOEXENCI,
io.cplusSubjectOpFk AS TIPONOSUJE,
io.cplusInvoiceType477Fk AS TIPOFACT,
io.siiTypeInvoiceOutFk AS TIPOFACT,
ic.cplusRectificationTypeFk AS TIPORECTIF,
io.companyFk,
RIGHT(io.ref, LENGTH(io.ref) - 1) AS invoiceNum,
@ -58868,7 +58952,7 @@ BEGIN
clientFk,
dued,
companyFk,
cplusInvoiceType477Fk
siiTypeInvoiceOutFk
)
SELECT
1,

View File

@ -46,7 +46,7 @@ TABLES=(
bookingPlanner
businessType
cplusInvoiceType472
cplusInvoiceType477
siiTypeInvoiceOut
cplusRectificationType
cplusSubjectOp
cplusTaxBreak

View File

@ -1,91 +0,0 @@
const app = require('vn-loopback/server/server');
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
describe('timeControl_calculateByUser()', () => {
it(`should return today's worked hours`, async() => {
let start = Date.vnNew();
start.setHours(0, 0, 0, 0);
start.setDate(start.getDate() - 1);
let end = Date.vnNew();
end.setHours(0, 0, 0, 0);
end.setDate(end.getDate() + 1);
let stmts = [];
let stmt;
let params = {
workerID: 1106,
start: start,
end: end
};
stmt = new ParameterizedSQL('CALL vn.timeControl_calculateByUser(?, ?, ?)', [
params.workerID,
params.start,
params.end
]);
stmts.push(stmt);
let tableIndex = stmts.push('SELECT * FROM tmp.timeControlCalculate') - 1;
let sql = ParameterizedSQL.join(stmts, ';');
let result = await app.models.Ticket.rawStmt(sql);
let [timeControlCalculateTable] = result[tableIndex];
expect(timeControlCalculateTable.timeWorkSeconds).toEqual(28200);
});
// #2261
xit(`should return the worked hours between last sunday and monday`, async() => {
let lastSunday = Date.vnNew();
let daysSinceSunday = lastSunday.getDay();
if (daysSinceSunday === 0) // this means today is sunday but you need the previous sunday :)
daysSinceSunday = 7;
lastSunday.setHours(23, 0, 0, 0);
lastSunday.setDate(lastSunday.getDate() - daysSinceSunday);
let monday = Date.vnNew();
let daysSinceMonday = daysSinceSunday - 1; // aiming for monday (today could be monday)
monday.setHours(7, 0, 0, 0);
monday.setDate(monday.getDate() - daysSinceMonday);
let stmts = [];
let stmt;
stmts.push('START TRANSACTION');
const workerID = 1108;
stmt = new ParameterizedSQL(`
INSERT INTO vn.workerTimeControl(userFk, timed, manual, direction)
VALUES
(?, ?, 1, 'in'),
(?, ?, 1, 'out')
`, [
workerID,
lastSunday,
workerID,
monday
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.timeControl_calculateByUser(?, ?, ?)', [
workerID,
lastSunday,
monday
]);
stmts.push(stmt);
let tableIndex = stmts.push('SELECT * FROM tmp.timeControlCalculate') - 1;
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
let result = await app.models.Ticket.rawStmt(sql);
let [timeControlCalculateTable] = result[tableIndex];
expect(timeControlCalculateTable.timeWorkSeconds).toEqual(30000);
});
});

View File

@ -1,580 +0,0 @@
const app = require('vn-loopback/server/server');
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
// #2261 xdescribe dbtest workerTimeControl_check()
xdescribe('worker workerTimeControl_check()', () => {
it(`should throw an error if the worker can't sign on that tablet`, async() => {
let stmts = [];
let stmt;
const workerId = 1110;
const tabletId = 2;
let err;
stmts.push('START TRANSACTION');
try {
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
await app.models.Worker.rawStmt(sql);
} catch (e) {
err = e;
}
expect(err.sqlMessage).toEqual('No perteneces a este departamento.');
});
it('should check that the worker can sign on that tablet', async() => {
let stmts = [];
let stmt;
const workerId = 1110;
const tabletId = 1;
let err;
stmts.push('START TRANSACTION');
try {
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
await app.models.Worker.rawStmt(sql);
} catch (e) {
err = e;
}
expect(err).not.toBeDefined();
});
it('should throw an error if the worker with a special category has not finished the 9h break', async() => {
const workerId = 1110;
const tabletId = 1;
let stmts = [];
let stmt;
let sql;
let error;
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES
(?,TIMESTAMPADD(HOUR,-17,NOW()),0,"in"),
(?,TIMESTAMPADD(SECOND,-32399,NOW()),0,"out")`, [
workerId,
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
sql = ParameterizedSQL.join(stmts, ';');
try {
await app.models.Worker.rawStmt(sql);
} catch (e) {
await app.models.Worker.rawSql('ROLLBACK');
error = e;
}
expect(error.sqlMessage).toEqual('Descansos 9 h');
});
it('should check f the worker with a special category has finished the 9h break', async() => {
const workerId = 1110;
const tabletId = 1;
let stmts = [];
let stmt;
let err;
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES
(?,TIMESTAMPADD(HOUR,-17,NOW()),0,"in"),
(?,TIMESTAMPADD(SECOND,-32401,NOW()),0,"out")`, [
workerId,
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
try {
await app.models.Worker.rawStmt(sql);
} catch (e) {
await app.models.Worker.rawSql('ROLLBACK');
err = e;
}
expect(err).not.toBeDefined();
});
it('should throw an error if the worker has not finished the 12h break', async() => {
const workerId = 1109;
const tabletId = 1;
let stmts = [];
let stmt;
let sql;
let error;
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES
(?,TIMESTAMPADD(HOUR,-20,NOW()),0,"in"),
(?,TIMESTAMPADD(SECOND,-43199,NOW()),0,"out")`, [
workerId,
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
sql = ParameterizedSQL.join(stmts, ';');
try {
await app.models.Worker.rawStmt(sql);
} catch (e) {
await app.models.Worker.rawSql('ROLLBACK');
error = e;
}
expect(error.sqlMessage).toEqual('Descansos 12 h');
});
it('should throw an error if the worker has finished the 12h break', async() => {
const workerId = 1109;
const tabletId = 1;
let stmts = [];
let stmt;
let err;
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES
(?,TIMESTAMPADD(HOUR,-20,NOW()),0,"in"),
(?,TIMESTAMPADD(SECOND,-43201,NOW()),0,"out")`, [
workerId,
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
try {
await app.models.Worker.rawStmt(sql);
} catch (e) {
await app.models.Worker.rawSql('ROLLBACK');
err = e;
}
expect(err).not.toBeDefined();
});
it('should throw an error if the worker has odd entry records', async() => {
const workerId = 1109;
const tabletId = 1;
let stmts = [];
let stmt;
let err;
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES
(?,TIMESTAMPADD(HOUR,-24,NOW()),0,"in")`, [
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
try {
await app.models.Worker.rawStmt(sql);
} catch (e) {
await app.models.Worker.rawSql('ROLLBACK');
err = e;
}
expect(err.sqlMessage).toEqual('Dias con fichadas impares');
});
it('should throw an error if the worker try to sign on a holiday day', async() => {
const workerId = 1109;
const tabletId = 1;
let stmts = [];
let stmt;
let err;
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES
(?,TIMESTAMPADD(HOUR,-24,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-20,NOW()),0,"out")`, [
workerId,
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
try {
await app.models.Worker.rawStmt(sql);
} catch (e) {
await app.models.Worker.rawSql('ROLLBACK');
err = e;
}
expect(err.sqlMessage).toEqual('Holidays');
});
it('should throw an error if the worker try to sign with your contract ended', async() => {
const workerId = 1109;
const tabletId = 1;
let stmts = [];
let stmt;
let err;
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`UPDATE vn.business SET ended = DATE_ADD(CURDATE(), INTERVAL -1 DAY) WHERE id = ?`, [
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES
(?,TIMESTAMPADD(HOUR,-24,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-20,NOW()),0,"out")`, [
workerId,
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
try {
await app.models.Worker.rawStmt(sql);
} catch (e) {
await app.models.Worker.rawSql('ROLLBACK');
err = e;
}
expect(err.sqlMessage).toEqual('No hay un contrato en vigor');
});
it('should throw an error if the worker has not finished the 36h weekly break', async() => {
const workerId = 1109;
const tabletId = 1;
let stmts = [];
let stmt;
stmts.push('SET @warn := NULL');
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES
(?,TIMESTAMPADD(HOUR,-24,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-16,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-48,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-40,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-72,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-64,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-96,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-88,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-120,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-112,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-144,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-136,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-168,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-160,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-192,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-184,NOW()),0,"out")`, [
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
let warningMessageIndex = stmts.push('SELECT @warn AS warning') - 1;
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
let result = await app.models.Worker.rawStmt(sql);
expect(result[warningMessageIndex][0].warning).toEqual('Descansos 36 h');
});
it('should check if the worker has finished the 36h weekly break', async() => {
const workerId = 1109;
const tabletId = 1;
let stmts = [];
let stmt;
stmts.push('SET @warn := NULL');
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES
(?,TIMESTAMPADD(HOUR,-24,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-16,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-48,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-40,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-72,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-64,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-96,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-88,NOW()),0,"out")`, [
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
let warningMessageIndex = stmts.push('SELECT @warn AS warning') - 1;
let sql = ParameterizedSQL.join(stmts, ';');
let result = await app.models.Worker.rawStmt(sql);
expect(result[warningMessageIndex][0].warning).toBe(null);
});
it('should throw an error if the worker has not finished the 72h biweekly break', async() => {
const workerId = 1109;
const tabletId = 1;
let stmts = [];
let stmt;
let err;
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES
(?,TIMESTAMPADD(HOUR,-24,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-16,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-48,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-40,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-72,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-64,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-96,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-88,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-120,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-112,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-144,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-136,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-168,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-160,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-192,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-184,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-216,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-208,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-240,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-232,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-264,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-256,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-289,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-280,NOW()),0,"out")`, [
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
stmts.push('SELECT @warn AS warning') - 1;
let sql = ParameterizedSQL.join(stmts, ';');
try {
await app.models.Worker.rawStmt(sql);
} catch (e) {
await app.models.Worker.rawSql('ROLLBACK');
err = e;
}
expect(err.sqlMessage).toEqual('Descansos 72 h');
});
it('should check if the worker has finished the 72h biweekly break', async() => {
const workerId = 1109;
const tabletId = 1;
let stmts = [];
let stmt;
let err;
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction)
VALUES
(?,TIMESTAMPADD(HOUR,-24,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-16,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-48,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-40,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-72,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-64,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-96,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-88,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-120,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-112,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-144,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-136,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-168,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-160,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-192,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-184,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-216,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-208,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-240,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-232,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-264,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-256,NOW()),0,"out"),
(?,TIMESTAMPADD(HOUR,-288,NOW()),0,"in"),
(?,TIMESTAMPADD(HOUR,-280,NOW()),0,"out")`, [
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId,
workerId
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('CALL vn.workerTimeControl_check(?, ?, NULL)', [
workerId,
tabletId
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
stmts.push('SELECT @warn AS warning') - 1;
let sql = ParameterizedSQL.join(stmts, ';');
try {
await app.models.Worker.rawStmt(sql);
} catch (e) {
await app.models.Worker.rawSql('ROLLBACK');
err = e;
}
expect(err).not.toBeDefined();
});
});

View File

@ -722,7 +722,7 @@ export default {
isFullMovable: 'vn-check[ng-model="filter.isFullMovable"]',
warehouseFk: 'vn-autocomplete[label="Warehouse"]',
tableButtonSearch: 'vn-button[vn-tooltip="Search"]',
moveButton: 'vn-button[vn-tooltip="Advance tickets"]',
moveButton: 'vn-button[vn-tooltip="Advance tickets with negatives"]',
acceptButton: '.vn-confirm.shown button[response="accept"]',
firstCheck: 'tbody > tr:nth-child(2) > td > vn-check',
tableId: 'vn-textfield[name="id"]',

View File

@ -212,7 +212,7 @@ describe('Ticket Edit sale path', () => {
it('should log in as salesAssistant and navigate to ticket sales', async() => {
await page.loginAndModule('salesAssistant', 'ticket');
await page.accessToSearchResult('17');
await page.accessToSearchResult('15');
await page.accessToSection('ticket.card.sale');
});
@ -316,7 +316,7 @@ describe('Ticket Edit sale path', () => {
});
it('should confirm the transfered quantity is the correct one', async() => {
const result = await page.waitToGetProperty(selectors.ticketSales.secondSaleQuantityCell, 'innerText');
const result = await page.waitToGetProperty(selectors.ticketSales.firstSaleQuantityCell, 'innerText');
expect(result).toContain('20');
});
@ -370,7 +370,7 @@ describe('Ticket Edit sale path', () => {
await page.waitToClick(selectors.ticketSales.moveToNewTicketButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain(`You can't create a ticket for a inactive client`);
expect(message.text).toContain(`You can't create a ticket for an inactive client`);
await page.closePopup();
});

View File

@ -0,0 +1,28 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.execute = async(ctx, type, query, params, options) => {
const userId = ctx.req.accessToken.userId;
const models = Self.app.models;
params = params ?? [];
const myOptions = {userId: ctx.req.accessToken.userId};
if (typeof options == 'object')
Object.assign(myOptions, options);
const chain = query.split(' ')[1];
const [canExecute] = await models.ProcsPriv.rawSql(
'SELECT account.user_hasRoutinePriv(?,?,?)',
[type, chain, userId],
myOptions);
if (!Object.values(canExecute)[0]) throw new UserError(`You don't have enough privileges`, 'ACCESS_DENIED');
const argString = params.map(() => '?').join(',');
const response = await models.ProcsPriv.rawSql(query + `(${argString})`, params, myOptions);
if (!Array.isArray(response)) return;
return response[0];
};
};

View File

@ -0,0 +1,41 @@
module.exports = Self => {
Self.remoteMethodCtx('executeFunc', {
description: 'Return result of function',
accessType: 'EXECUTE',
accepts: [
{
arg: 'routine',
type: 'string',
description: 'The routine name',
required: true,
http: {source: 'path'}
},
{
arg: 'schema',
type: 'string',
description: 'The routine schema',
required: true,
},
{
arg: 'params',
type: ['any'],
description: 'The params array',
},
],
returns: {
type: 'any',
root: true
},
http: {
path: `/:routine/execute-func`,
verb: 'POST'
}
});
Self.executeFunc = async(ctx, routine, schema, params, options) => {
const query = `SELECT ${schema}.${routine}`;
const response = await Self.execute(ctx, 'FUNCTION', query, params, options);
return Object.values(response)[0];
};
};

View File

@ -0,0 +1,39 @@
module.exports = Self => {
Self.remoteMethodCtx('executeProc', {
description: 'Return result of procedure',
accessType: 'EXECUTE',
accepts: [
{
arg: 'routine',
type: 'string',
description: 'The routine name',
required: true,
http: {source: 'path'}
},
{
arg: 'schema',
type: 'string',
description: 'The routine schema',
required: true,
},
{
arg: 'params',
type: ['any'],
description: 'The params array',
},
],
returns: {
type: 'any',
root: true
},
http: {
path: `/:routine/execute-proc`,
verb: 'POST'
}
});
Self.executeProc = async(ctx, routine, schema, params, options) => {
const query = `CALL ${schema}.${routine}`;
return Self.execute(ctx, 'PROCEDURE', query, params, options);
};
};

View File

@ -0,0 +1,161 @@
const models = require('vn-loopback/server/server').models;
describe('Application execute()/executeProc()/executeFunc()', () => {
const userWithoutPrivileges = 1;
const userWithPrivileges = 9;
const userWithInheritedPrivileges = 120;
let tx;
function getCtx(userId) {
return {
req: {
accessToken: {userId},
headers: {origin: 'http://localhost'}
}
};
}
beforeEach(async() => {
tx = await models.Application.beginTransaction({});
const options = {transaction: tx};
await models.Application.rawSql(`
CREATE OR REPLACE PROCEDURE vn.myProcedure(vMyParam INT)
BEGIN
SELECT vMyParam myParam, t.*
FROM ticket t
LIMIT 2;
END
`, null, options);
await models.Application.rawSql(`
CREATE OR REPLACE FUNCTION bs.myFunction(vMyParam INT) RETURNS int(11)
BEGIN
RETURN vMyParam;
END
`, null, options);
await models.Application.rawSql(`
GRANT EXECUTE ON PROCEDURE vn.myProcedure TO developer;
GRANT EXECUTE ON FUNCTION bs.myFunction TO developer;
`, null, options);
});
it('should throw error when execute procedure and not have privileges', async() => {
const ctx = getCtx(userWithoutPrivileges);
let error;
try {
const options = {transaction: tx};
await models.Application.execute(
ctx,
'PROCEDURE',
'CALL vn.myProcedure',
[1],
options
);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error.message).toEqual(`You don't have enough privileges`);
});
it('should execute procedure and get data', async() => {
const ctx = getCtx(userWithPrivileges);
try {
const options = {transaction: tx};
const response = await models.Application.execute(
ctx,
'PROCEDURE',
'CALL vn.myProcedure',
[1],
options
);
expect(response.length).toEqual(2);
expect(response[0].myParam).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
describe('Application executeProc()', () => {
it('should execute procedure and get data (executeProc)', async() => {
const ctx = getCtx(userWithPrivileges);
try {
const options = {transaction: tx};
const response = await models.Application.executeProc(
ctx,
'myProcedure',
'vn',
[1],
options
);
expect(response.length).toEqual(2);
expect(response[0].myParam).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});
describe('Application executeFunc()', () => {
it('should execute function and get data', async() => {
const ctx = getCtx(userWithPrivileges);
try {
const options = {transaction: tx};
const response = await models.Application.executeFunc(
ctx,
'myFunction',
'bs',
[1],
options
);
expect(response).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should execute function and get data with user with inherited privileges', async() => {
const ctx = getCtx(userWithInheritedPrivileges);
try {
const options = {transaction: tx};
const response = await models.Application.executeFunc(
ctx,
'myFunction',
'bs',
[1],
options
);
expect(response).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});
});

View File

@ -2,4 +2,7 @@
module.exports = function(Self) {
require('../methods/application/status')(Self);
require('../methods/application/post')(Self);
require('../methods/application/execute')(Self);
require('../methods/application/executeProc')(Self);
require('../methods/application/executeFunc')(Self);
};

View File

@ -0,0 +1,44 @@
{
"name": "ProcsPriv",
"base": "VnModel",
"options": {
"mysql": {
"table": "mysql.procs_priv"
}
},
"properties": {
"name": {
"id": 1,
"type": "string",
"mysql": {
"columnName": "Routine_name"
}
},
"schema": {
"id": 3,
"type": "string",
"mysql": {
"columnName": "Db"
}
},
"role": {
"type": "string",
"mysql": {
"columnName": "user"
}
},
"type": {
"id": 2,
"type": "string",
"mysql": {
"columnName": "Routine_type"
}
},
"host": {
"type": "string",
"mysql": {
"columnName": "Host"
}
}
}
}

View File

@ -23,7 +23,7 @@
"Agency cannot be blank": "Agency cannot be blank",
"The IBAN does not have the correct format": "The IBAN does not have the correct format",
"You can't make changes on the basic data of an confirmed order or with rows": "You can't make changes on the basic data of an confirmed order or with rows",
"You can't create a ticket for a inactive client": "You can't create a ticket for a inactive client",
"You can't create a ticket for an inactive client": "You can't create a ticket for an inactive client",
"Worker cannot be blank": "Worker cannot be blank",
"You must delete the claim id %d first": "You must delete the claim id %d first",
"You don't have enough privileges": "You don't have enough privileges",
@ -188,7 +188,14 @@
"The ticket doesn't exist.": "The ticket doesn't exist.",
"The sales do not exists": "The sales do not exists",
"Ticket without Route": "Ticket without route",
"Select a different client": "Select a different client",
"Fill all the fields": "Fill all the fields",
"Error while generating PDF": "Error while generating PDF",
"Can't invoice to future": "Can't invoice to future",
"This ticket is already invoiced": "This ticket is already invoiced",
"Negative basis of tickets: 23": "Negative basis of tickets: 23",
"Booking completed": "Booking complete",
"The ticket is in preparation": "The ticket [{{ticketId}}]({{{ticketUrl}}}) of the sales person {{salesPersonId}} is in preparation",
"You can only add negative amounts in refund tickets": "You can only add negative amounts in refund tickets"
"You can only add negative amounts in refund tickets": "You can only add negative amounts in refund tickets",
"Try again": "Try again"
}

View File

@ -5,10 +5,10 @@
"The default consignee can not be unchecked": "No se puede desmarcar el consignatario predeterminado",
"Unable to default a disabled consignee": "No se puede poner predeterminado un consignatario desactivado",
"Can't be blank": "No puede estar en blanco",
"Invalid TIN": "NIF/CIF invalido",
"Invalid TIN": "NIF/CIF inválido",
"TIN must be unique": "El NIF/CIF debe ser único",
"A client with that Web User name already exists": "Ya existe un cliente con ese Usuario Web",
"Is invalid": "Is invalid",
"Is invalid": "Es inválido",
"Quantity cannot be zero": "La cantidad no puede ser cero",
"Enter an integer different to zero": "Introduce un entero distinto de cero",
"Package cannot be blank": "El embalaje no puede estar en blanco",
@ -55,17 +55,17 @@
"You must delete the claim id %d first": "Antes debes borrar la reclamación %d",
"You don't have enough privileges": "No tienes suficientes permisos",
"Cannot check Equalization Tax in this NIF/CIF": "No se puede marcar RE en este NIF/CIF",
"You can't make changes on the basic data of an confirmed order or with rows": "No puedes cambiar los datos basicos de una orden con artículos",
"INVALID_USER_NAME": "El nombre de usuario solo debe contener letras minúsculas o, a partir del segundo carácter, números o subguiones, no esta permitido el uso de la letra ñ",
"You can't make changes on the basic data of an confirmed order or with rows": "No puedes cambiar los datos básicos de una orden con artículos",
"INVALID_USER_NAME": "El nombre de usuario solo debe contener letras minúsculas o, a partir del segundo carácter, números o subguiones, no está permitido el uso de la letra ñ",
"You can't create a ticket for a frozen client": "No puedes crear un ticket para un cliente congelado",
"You can't create a ticket for a inactive client": "No puedes crear un ticket para un cliente inactivo",
"You can't create a ticket for an inactive client": "No puedes crear un ticket para un cliente inactivo",
"Tag value cannot be blank": "El valor del tag no puede quedar en blanco",
"ORDER_EMPTY": "Cesta vacía",
"You don't have enough privileges to do that": "No tienes permisos para cambiar esto",
"NO SE PUEDE DESACTIVAR EL CONSIGNAT": "NO SE PUEDE DESACTIVAR EL CONSIGNAT",
"Error. El NIF/CIF está repetido": "Error. El NIF/CIF está repetido",
"Street cannot be empty": "Dirección no puede estar en blanco",
"City cannot be empty": "Cuidad no puede estar en blanco",
"City cannot be empty": "Ciudad no puede estar en blanco",
"Code cannot be blank": "Código no puede estar en blanco",
"You cannot remove this department": "No puedes eliminar este departamento",
"The extension must be unique": "La extensión debe ser unica",
@ -102,8 +102,8 @@
"You can't delete a confirmed order": "No puedes borrar un pedido confirmado",
"The social name has an invalid format": "El nombre fiscal tiene un formato incorrecto",
"Invalid quantity": "Cantidad invalida",
"This postal code is not valid": "This postal code is not valid",
"is invalid": "is invalid",
"This postal code is not valid": "Este código postal no es válido",
"is invalid": "es inválido",
"The postcode doesn't exist. Please enter a correct one": "El código postal no existe. Por favor, introduce uno correcto",
"The department name can't be repeated": "El nombre del departamento no puede repetirse",
"This phone already exists": "Este teléfono ya existe",
@ -112,8 +112,8 @@
"You cannot delete a ticket that part of it is being prepared": "No puedes eliminar un ticket en el que una parte que está siendo preparada",
"You must delete all the buy requests first": "Debes eliminar todas las peticiones de compra primero",
"You should specify a date": "Debes especificar una fecha",
"You should specify at least a start or end date": "Debes especificar al menos una fecha de inicio o de fín",
"Start date should be lower than end date": "La fecha de inicio debe ser menor que la fecha de fín",
"You should specify at least a start or end date": "Debes especificar al menos una fecha de inicio o de fin",
"Start date should be lower than end date": "La fecha de inicio debe ser menor que la fecha de fin",
"You should mark at least one week day": "Debes marcar al menos un día de la semana",
"Swift / BIC can't be empty": "Swift / BIC no puede estar vacío",
"Customs agent is required for a non UEE member": "El agente de aduanas es requerido para los clientes extracomunitarios",
@ -144,15 +144,15 @@
"Unable to clone this travel": "No ha sido posible clonar este travel",
"This thermograph id already exists": "La id del termógrafo ya existe",
"Choose a date range or days forward": "Selecciona un rango de fechas o días en adelante",
"ORDER_ALREADY_CONFIRMED": "ORDER_ALREADY_CONFIRMED",
"ORDER_ALREADY_CONFIRMED": "ORDEN YA CONFIRMADA",
"Invalid password": "Invalid password",
"Password does not meet requirements": "La contraseña no cumple los requisitos",
"Role already assigned": "Role already assigned",
"Invalid role name": "Invalid role name",
"Role name must be written in camelCase": "Role name must be written in camelCase",
"Email already exists": "Email already exists",
"User already exists": "User already exists",
"Absence change notification on the labour calendar": "Notificacion de cambio de ausencia en el calendario laboral",
"Role already assigned": "Rol ya asignado",
"Invalid role name": "Nombre de rol no válido",
"Role name must be written in camelCase": "El nombre del rol debe escribirse en camelCase",
"Email already exists": "El correo ya existe",
"User already exists": "El/La usuario/a ya existe",
"Absence change notification on the labour calendar": "Notificación de cambio de ausencia en el calendario laboral",
"Record of hours week": "Registro de horas semana {{week}} año {{year}} ",
"Created absence": "El empleado <strong>{{author}}</strong> ha añadido una ausencia de tipo '{{absenceType}}' a <a href='{{{workerUrl}}}'><strong>{{employee}}</strong></a> para el día {{dated}}.",
"Deleted absence": "El empleado <strong>{{author}}</strong> ha eliminado una ausencia de tipo '{{absenceType}}' a <a href='{{{workerUrl}}}'><strong>{{employee}}</strong></a> del día {{dated}}.",
@ -224,7 +224,7 @@
"date in the future": "Fecha en el futuro",
"reference duplicated": "Referencia duplicada",
"This ticket is already a refund": "Este ticket ya es un abono",
"isWithoutNegatives": "isWithoutNegatives",
"isWithoutNegatives": "Sin negativos",
"routeFk": "routeFk",
"Can't change the password of another worker": "No se puede cambiar la contraseña de otro trabajador",
"No hay un contrato en vigor": "No hay un contrato en vigor",
@ -317,10 +317,15 @@
"The ticket doesn't exist.": "No existe el ticket.",
"Social name should be uppercase": "La razón social debe ir en mayúscula",
"Street should be uppercase": "La dirección fiscal debe ir en mayúscula",
"The response is not a PDF": "La respuesta no es un PDF",
"Ticket without Route": "Ticket sin ruta",
"Select a different client": "Seleccione un cliente distinto",
"Fill all the fields": "Rellene todos los campos",
"The response is not a PDF": "La respuesta no es un PDF",
"Booking completed": "Reserva completada",
"The ticket is in preparation": "El ticket [{{ticketId}}]({{{ticketUrl}}}) del comercial {{salesPersonId}} está en preparación",
"The amount cannot be less than the minimum": "La cantidad no puede ser menor que la cantidad mímina",
"quantityLessThanMin": "La cantidad no puede ser menor que la cantidad mímina"
"Incoterms data for consignee is missing": "Faltan los datos de los Incoterms para el consignatario",
"The notification subscription of this worker cant be modified": "La subscripción a la notificación de este trabajador no puede ser modificada",
"User disabled": "Usuario desactivado",
"The amount cannot be less than the minimum": "La cantidad no puede ser menor que la cantidad mínima",
"quantityLessThanMin": "La cantidad no puede ser menor que la cantidad mínima"
}

View File

@ -49,5 +49,13 @@
},
"Container": {
"dataSource": "vn"
},
"ProcsPriv": {
"dataSource": "vn",
"options": {
"mysql": {
"table": "mysql.procs_priv"
}
}
}
}
}

View File

@ -1,3 +1,4 @@
const ForbiddenError = require('vn-loopback/util/forbiddenError');
module.exports = Self => {
Self.remoteMethod('sync', {
@ -32,9 +33,13 @@ module.exports = Self => {
const models = Self.app.models;
const user = await models.VnUser.findOne({
fields: ['id'],
fields: ['id', 'password'],
where: {name: userName}
}, myOptions);
if (user && password && !await user.hasPassword(password))
throw new ForbiddenError('Wrong password');
const isSync = !await models.UserSync.exists(userName, myOptions);
if (!force && isSync && user) return;
@ -42,4 +47,3 @@ module.exports = Self => {
await models.UserSync.destroyById(userName, myOptions);
};
};

View File

@ -35,6 +35,9 @@
"SambaConfig": {
"dataSource": "vn"
},
"SignInLog": {
"dataSource": "vn"
},
"Sip": {
"dataSource": "vn"
},

View File

@ -5,7 +5,7 @@ const crypto = require('crypto');
const nthash = require('smbhash').nthash;
module.exports = Self => {
const shouldSync = process.env.NODE_ENV === 'production';
const shouldSync = process.env.NODE_ENV !== 'test';
Self.getSynchronizer = async function() {
return await Self.findOne({
@ -140,6 +140,7 @@ module.exports = Self => {
try {
if (shouldSync)
await client.del(dn);
// eslint-disable-next-line no-console
console.log(` -> User '${userName}' removed from LDAP`);
} catch (e) {
if (e.name !== 'NoSuchObjectError') throw e;

View File

@ -27,8 +27,7 @@ module.exports = Self => {
const [row] = await Self.rawSql(
`SELECT COUNT(*) AS nRows
FROM mysql.user
WHERE User = ?
AND Host = ?`,
WHERE User = ? AND Host = ?`,
[mysqlUser, this.userHost]
);
let userExists = row.nRows > 0;
@ -38,8 +37,7 @@ module.exports = Self => {
const [row] = await Self.rawSql(
`SELECT Priv AS priv
FROM mysql.global_priv
WHERE User = ?
AND Host = ?`,
WHERE User = ? AND Host = ?`,
[mysqlUser, this.userHost]
);
const priv = row && JSON.parse(row.priv);
@ -88,10 +86,18 @@ module.exports = Self => {
else
throw err;
}
await Self.rawSql('GRANT ? TO ?@?',
[role, mysqlUser, this.userHost]);
if (role) {
const [row] = await Self.rawSql(
`SELECT COUNT(*) AS nRows
FROM mysql.user
WHERE User = ? AND Host = ''`,
[role]
);
const roleExists = row.nRows > 0;
if (roleExists) {
await Self.rawSql('GRANT ? TO ?@?',
[role, mysqlUser, this.userHost]);
await Self.rawSql('SET DEFAULT ROLE ? FOR ?@?',
[role, mysqlUser, this.userHost]);
} else {

View File

@ -1,6 +1,6 @@
const ldap = require('../util/ldapjs-extra');
const ssh = require('node-ssh');
const execFile = require('child_process').execFile;
/**
* Summary of userAccountControl flags:
@ -11,6 +11,8 @@ const UserAccountControlFlags = {
};
module.exports = Self => {
const shouldSync = process.env.NODE_ENV !== 'test';
Self.getSynchronizer = async function() {
return await Self.findOne({
fields: [
@ -19,6 +21,7 @@ module.exports = Self => {
'adController',
'adUser',
'adPassword',
'userDn',
'verifyCert'
]
});
@ -26,88 +29,123 @@ module.exports = Self => {
Object.assign(Self.prototype, {
async init() {
let sshClient = new ssh.NodeSSH();
await sshClient.connect({
host: this.adController,
username: this.adUser,
password: this.adPassword
});
const baseDn = this.adDomain
.split('.')
.map(part => `dc=${part}`)
.join(',');
const bindDn = `cn=${this.adUser},cn=Users,${baseDn}`;
let adUser = `cn=${this.adUser},${this.usersDn()}`;
let adClient = ldap.createClient({
const adClient = ldap.createClient({
url: `ldaps://${this.adController}:636`,
tlsOptions: {rejectUnauthorized: this.verifyCert}
});
await adClient.bind(adUser, this.adPassword);
await adClient.bind(bindDn, this.adPassword);
Object.assign(this, {
sshClient,
adClient
adClient,
fullUsersDn: `${this.userDn},${baseDn}`,
bindDn
});
},
async deinit() {
await this.sshClient.dispose();
await this.adClient.unbind();
},
usersDn() {
let dnBase = this.adDomain
.split('.')
.map(part => `dc=${part}`)
.join(',');
return `cn=Users,${dnBase}`;
async sambaTool(command, args = []) {
let authArgs = [
'--URL', `ldaps://${this.adController}`,
'--simple-bind-dn', this.bindDn,
'--password', this.adPassword
];
if (!this.verifyCert)
authArgs.push('--option', 'tls verify peer = no_check');
const allArgs = [command].concat(
args, authArgs
);
if (!shouldSync) return;
return await new Promise((resolve, reject) => {
execFile('samba-tool', allArgs, (err, stdout, stderr) => {
if (err)
reject(err);
else
resolve({stdout, stderr});
});
});
},
async syncUser(userName, info, password) {
let {sshClient} = this;
let sambaUser = await this.adClient.searchOne(this.usersDn(), {
async getAdUser(userName) {
const sambaUser = await this.adClient.searchOne(this.fullUsersDn, {
scope: 'sub',
attributes: ['userAccountControl'],
attributes: [
'dn',
'userAccountControl',
'uidNumber',
'accountExpires',
'mail'
],
filter: `(&(objectClass=user)(sAMAccountName=${userName}))`
});
let isEnabled = sambaUser
&& !(sambaUser.userAccountControl & UserAccountControlFlags.ACCOUNTDISABLE);
if (process.env.NODE_ENV === 'test')
return;
if (sambaUser) {
for (const intProp of ['uidNumber', 'userAccountControl']) {
if (sambaUser[intProp] != null)
sambaUser[intProp] = parseInt(sambaUser[intProp]);
}
}
return sambaUser;
},
async syncUser(userName, info, password) {
let sambaUser = await this.getAdUser(userName);
let entry;
if (info.hasAccount) {
if (!sambaUser) {
await sshClient.exec('samba-tool user create', [
userName,
'--uid-number', `${info.uidNumber}`,
'--mail-address', info.corporateMail,
await this.sambaTool('user', [
'create', userName,
'--userou', this.userDn,
'--random-password'
]);
await sshClient.exec('samba-tool user setexpiry', [
userName,
'--noexpiry'
]);
await sshClient.exec('mkhomedir_helper', [
userName,
'0027'
]);
}
if (!isEnabled) {
await sshClient.exec('samba-tool user enable', [
userName
]);
sambaUser = await this.getAdUser(userName);
}
if (password) {
await sshClient.exec('samba-tool user setpassword', [
userName,
await this.sambaTool('user', [
'setpassword', userName,
'--newpassword', password
]);
}
} else if (isEnabled) {
await sshClient.exec('samba-tool user disable', [
userName
]);
entry = {
userAccountControl: sambaUser.userAccountControl
& ~UserAccountControlFlags.ACCOUNTDISABLE,
uidNumber: info.uidNumber,
accountExpires: 0,
mail: info.corporateMail
};
} else if (sambaUser) {
entry = {
userAccountControl: sambaUser.userAccountControl
| UserAccountControlFlags.ACCOUNTDISABLE
};
// eslint-disable-next-line no-console
console.log(` -> User '${userName}' disabled on Samba`);
}
if (sambaUser && entry) {
const changes = [];
for (const prop in entry) {
if (sambaUser[prop] == entry[prop]) continue;
changes.push(new ldap.Change({
operation: 'replace',
modification: {
[prop]: entry[prop]
}
}));
}
if (changes.length && shouldSync)
await this.adClient.modify(sambaUser.dn, changes);
}
},
/**
@ -117,14 +155,15 @@ module.exports = Self => {
*/
async getUsers(usersToSync) {
const LDAP_MATCHING_RULE_BIT_AND = '1.2.840.113556.1.4.803';
let filter = `!(userAccountControl:${LDAP_MATCHING_RULE_BIT_AND}:=${UserAccountControlFlags.ACCOUNTDISABLE})`;
const filter = `!(userAccountControl:${LDAP_MATCHING_RULE_BIT_AND}`
+ `:=${UserAccountControlFlags.ACCOUNTDISABLE})`;
let opts = {
const opts = {
scope: 'sub',
attributes: ['sAMAccountName'],
filter: `(&(objectClass=user)(${filter}))`
};
await this.adClient.searchForeach(this.usersDn(), opts,
await this.adClient.searchForeach(this.fullUsersDn, opts,
o => usersToSync.add(o.sAMAccountName));
}
});

View File

@ -28,6 +28,10 @@
"adPassword": {
"type": "string"
},
"userDn": {
"type": "string",
"required": true
},
"verifyCert": {
"type": "boolean"
}

View File

@ -0,0 +1,41 @@
{
"name": "SignInLog",
"base": "VnModel",
"options": {
"mysql": {
"table": "account.signInLog"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"description": "Identifier"
},
"token": {
"required": true,
"type": "string",
"description": "Token's user"
},
"creationDate": {
"type": "date"
},
"userFk": {
"required": true,
"type": "number"
},
"ip": {
"type": "string"
}
},
"relations": {
"user": {
"type": "belongsTo",
"model": "VnUser",
"foreignKey": "userFk"
}
},
"scope": {
"order": ["creationDate DESC", "id DESC"]
}
}

View File

@ -12,40 +12,40 @@
<vn-card class="vn-pa-lg" vn-focus>
<vn-vertical>
<vn-textfield
label="Homedir base"
label="Homedir base"
ng-model="$ctrl.config.homedir"
rule="AccountConfig">
</vn-textfield>
<vn-textfield
label="Shell"
label="Shell"
ng-model="$ctrl.config.shell"
rule="AccountConfig">
</vn-textfield>
<vn-input-number
label="User and role base id"
label="User and role base id"
ng-model="$ctrl.config.idBase"
rule="AccountConfig">
</vn-input-number>
<vn-horizontal>
<vn-input-number
label="Min"
label="Min"
ng-model="$ctrl.config.min"
rule="AccountConfig">
</vn-input-number>
<vn-input-number
label="Max"
label="Max"
ng-model="$ctrl.config.max"
rule="AccountConfig">
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-input-number
label="Warn"
label="Warn"
ng-model="$ctrl.config.warn"
rule="AccountConfig">
</vn-input-number>
<vn-input-number
label="Inact"
label="Inact"
ng-model="$ctrl.config.inact"
rule="AccountConfig">
</vn-input-number>
@ -61,10 +61,6 @@
label="Synchronize all"
ng-click="$ctrl.onSynchronizeAll()">
</vn-button>
<vn-button
label="Synchronize user"
ng-click="syncUser.show()">
</vn-button>
<vn-button
label="Synchronize roles"
ng-click="$ctrl.onSynchronizeRoles()">
@ -77,25 +73,3 @@
</vn-button>
</vn-button-bar>
</form>
<vn-dialog
vn-id="syncUser"
on-accept="$ctrl.onUserSync()"
on-close="$ctrl.onSyncClose()">
<tpl-body>
<vn-textfield
label="Username"
ng-model="$ctrl.syncUser"
vn-focus>
</vn-textfield>
<vn-textfield
label="Password"
ng-model="$ctrl.syncPassword"
type="password"
info="If password is not specified, just user attributes are synchronized">
</vn-textfield>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Synchronize</button>
</tpl-buttons>
</vn-dialog>

View File

@ -1,6 +1,5 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import UserError from 'core/lib/user-error';
export default class Controller extends Section {
onSynchronizeAll() {
@ -8,27 +7,10 @@ export default class Controller extends Section {
this.$http.patch(`Accounts/syncAll`);
}
onUserSync() {
if (!this.syncUser)
throw new UserError('Please enter the username');
let params = {
password: this.syncPassword,
force: true
};
return this.$http.patch(`Accounts/${this.syncUser}/sync`, params)
.then(() => this.vnApp.showSuccess(this.$t('User synchronized!')));
}
onSynchronizeRoles() {
this.$http.patch(`RoleInherits/sync`)
.then(() => this.vnApp.showSuccess(this.$t('Roles synchronized!')));
}
onSyncClose() {
this.syncUser = '';
this.syncPassword = '';
}
}
ngModule.component('vnAccountAccounts', {

View File

@ -3,7 +3,6 @@ Homedir base: Directorio base para carpetas de usuario
Shell: Intérprete de línea de comandos
User and role base id: Id base usuarios y roles
Synchronize all: Sincronizar todo
Synchronize user: Sincronizar usuario
Synchronize roles: Sincronizar roles
If password is not specified, just user attributes are synchronized: >-
Si la contraseña no se especifica solo se sincronizarán lo atributos del usuario
@ -12,5 +11,4 @@ Users synchronized!: ¡Usuarios sincronizados!
Username: Nombre de usuario
Synchronize: Sincronizar
Please enter the username: Por favor introduce el nombre de usuario
User synchronized!: ¡Usuario sincronizado!
Roles synchronized!: ¡Roles sincronizados!

View File

@ -67,6 +67,15 @@
translate>
Deactivate user
</vn-item>
<vn-item
ng-if="$ctrl.user.active"
ng-click="syncUser.show()"
name="synchronizeUser"
vn-acl="it"
vn-acl-action="remove"
translate>
Synchronize
</vn-item>
</slot-menu>
<slot-body>
<div class="attributes">
@ -153,6 +162,32 @@
<button response="accept" translate>Change password</button>
</tpl-buttons>
</vn-dialog>
<vn-dialog
vn-id="syncUser"
on-accept="$ctrl.onSync()"
on-close="$ctrl.onSyncClose()">
<tpl-title ng-translate>
Do you want to synchronize user?
</tpl-title>
<tpl-body>
<vn-check
label="Synchronize password"
ng-model="$ctrl.shouldSyncPassword"
info="If password is not specified, just user attributes are synchronized"
vn-focus>
</vn-check>
<vn-textfield
label="Password"
ng-model="$ctrl.syncPassword"
type="password"
ng-if="$ctrl.shouldSyncPassword">
</vn-textfield>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Synchronize</button>
</tpl-buttons>
</vn-dialog>
<vn-popup vn-id="summary">
<vn-user-summary user="$ctrl.user"></vn-user-summary>
</vn-popup>

View File

@ -120,6 +120,20 @@ class Controller extends Descriptor {
this.vnApp.showSuccess(this.$t(message));
});
}
onSync() {
const params = {force: true};
if (this.shouldSyncPassword)
params.password = this.syncPassword;
return this.$http.patch(`Accounts/${this.user.name}/sync`, params)
.then(() => this.vnApp.showSuccess(this.$t('User synchronized!')));
}
onSyncClose() {
this.shouldSyncPassword = false;
this.syncPassword = undefined;
}
}
ngModule.component('vnUserDescriptor', {

View File

@ -22,6 +22,10 @@ Old password: Contraseña antigua
New password: Nueva contraseña
Repeat password: Repetir contraseña
Password changed succesfully!: ¡Contraseña modificada correctamente!
Synchronize: Sincronizar
Do you want to synchronize user?: ¿Quieres sincronizar el usuario?
Synchronize password: Sincronizar contraseña
User synchronized!: ¡Usuario sincronizado!
Role changed succesfully!: ¡Rol modificado correctamente!
Password requirements: >
La contraseña debe tener al menos {{ length }} caracteres de longitud,

View File

@ -12,7 +12,7 @@
<vn-card class="vn-pa-lg" vn-focus>
<vn-vertical>
<vn-check
label="Enable synchronization"
label="Enable synchronization"
ng-model="watcher.hasData">
</vn-check>
</vn-vertical>
@ -20,28 +20,33 @@
ng-if="watcher.hasData"
class="vn-mt-md">
<vn-textfield
label="AD domain"
label="AD domain"
ng-model="$ctrl.config.adDomain"
rule="SambaConfig">
</vn-textfield>
<vn-textfield
label="Domain controller"
label="Domain controller"
ng-model="$ctrl.config.adController"
rule="SambaConfig">
</vn-textfield>
<vn-textfield
label="AD user"
label="AD user"
ng-model="$ctrl.config.adUser"
rule="SambaConfig">
</vn-textfield>
<vn-textfield
label="AD password"
label="AD password"
ng-model="$ctrl.config.adPassword"
type="password"
rule="SambaConfig">
</vn-textfield>
<vn-textfield
label="User DN (without domain part)"
ng-model="$ctrl.config.userDn"
rule="SambaConfig">
</vn-textfield>
<vn-check
label="Verify certificate"
label="Verify certificate"
ng-model="$ctrl.config.verifyCert">
</vn-check>
</vn-vertical>
@ -63,4 +68,4 @@
ng-click="watcher.loadOriginalData()">
</vn-button>
</vn-button-bar>
</form>
</form>

View File

@ -3,6 +3,7 @@ Domain controller: Controlador de dominio
AD domain: Dominio AD
AD user: Usuario AD
AD password: Contraseña AD
User DN (without domain part): DN usuarios (sin la parte del dominio)
Verify certificate: Verificar certificado
Test connection: Probar conexión
Samba connection established!: ¡Conexión con Samba establecida!

View File

@ -5,6 +5,9 @@
"AddressObservation": {
"dataSource": "vn"
},
"AddressShortage": {
"dataSource": "vn"
},
"BankEntity": {
"dataSource": "vn"
},

View File

@ -0,0 +1,22 @@
{
"name": "AddressShortage",
"base": "VnModel",
"options": {
"mysql": {
"table": "addressShortage"
}
},
"properties": {
"addressFk": {
"type": "number",
"id": true
}
},
"relations": {
"address": {
"type": "belongsTo",
"model": "Address",
"foreignKey": "addressFk"
}
}
}

View File

@ -17,6 +17,9 @@
},
"maxCreditRows": {
"type": "number"
},
"defaultCredit": {
"type": "number"
}
}
}

View File

@ -450,14 +450,14 @@ module.exports = Self => {
if (lastCredit && lastCredit.amount == 0) {
const zeroCreditEditor =
await models.ACL.checkAccessAcl(accessToken, 'Client', 'zeroCreditEditor', 'WRITE');
await models.ACL.checkAccessAcl(accessToken, 'Client', 'zeroCreditEditor', 'WRITE');
const lastCreditIsNotEditable =
await models.ACL.checkAccessAcl(
{req: {accessToken: {userId: lastCredit.workerFk}}},
'Client',
'zeroCreditEditor',
'WRITE'
);
await models.ACL.checkAccessAcl(
{req: {accessToken: {userId: lastCredit.workerFk}}},
'Client',
'zeroCreditEditor',
'WRITE'
);
if (lastCreditIsNotEditable && !zeroCreditEditor)
throw new UserError(`You can't change the credit set to zero from a financialBoss`);
@ -483,12 +483,6 @@ module.exports = Self => {
if (userRequiredRoles <= 0)
throw new UserError(`You don't have enough privileges to set this credit amount`);
}
await models.ClientCredit.create({
amount: changes.credit,
clientFk: finalState.id,
workerFk: userId
}, ctx.options);
};
Self.changeCreditManagement = async function changeCreditManagement(ctx, finalState, changes) {

View File

@ -158,7 +158,7 @@
},
"user": {
"type": "belongsTo",
"model": "Account",
"model": "VnUser",
"foreignKey": "id"
},
"payMethod": {

View File

@ -62,13 +62,13 @@ describe('Client Model', () => {
const options = {transaction: tx};
const ctx = {options};
// Set credit to zero by a financialBoss
const financialBoss = await models.VnUser.findOne({
where: {name: 'financialBoss'}
}, options);
ctx.options.accessToken = {userId: financialBoss.id};
await models.Client.changeCredit(ctx, instance, {credit: 0});
const testClient = await models.Client.findById(instance.id, options);
await testClient.updateAttributes({credit: 0}, ctx.options);
const salesAssistant = await models.VnUser.findOne({
where: {name: 'salesAssistant'}

View File

@ -0,0 +1,37 @@
module.exports = Self => {
Self.remoteMethodCtx('new', {
description: 'Creates a new invoiceIn due day',
accessType: 'WRITE',
accepts: {
arg: 'id',
type: 'number',
required: true,
description: 'The invoiceIn id',
},
http: {
path: '/new',
verb: 'POST'
}
});
Self.new = async(ctx, id, options) => {
let tx;
const myOptions = {userId: ctx.req.accessToken.userId};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
await Self.rawSql(`CALL vn.invoiceInDueDay_calculate(?)`, [id], myOptions);
if (tx) await tx.commit();
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -0,0 +1,45 @@
const LoopBackContext = require('loopback-context');
const models = require('vn-loopback/server/server').models;
describe('invoiceInDueDay new()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should correctly create a new due day', async() => {
const userId = 9;
const invoiceInFk = 6;
const ctx = {
req: {
accessToken: {userId: userId},
}
};
const tx = await models.InvoiceIn.beginTransaction({});
const options = {transaction: tx};
try {
await models.InvoiceInDueDay.destroyAll({
invoiceInFk
}, options);
await models.InvoiceInDueDay.new(ctx, invoiceInFk, options);
const result = await models.InvoiceInDueDay.find({where: {invoiceInFk}}, options);
expect(result).toBeDefined();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -0,0 +1,3 @@
module.exports = Self => {
require('../methods/invoice-in-due-day/new')(Self);
};

View File

@ -23,7 +23,7 @@ module.exports = Self => {
}
});
Self.makePdfAndNotify = async function(ctx, id, printerFk) {
Self.makePdfAndNotify = async function(ctx, id, printerFk, options) {
const models = Self.app.models;
options = typeof options == 'object'

View File

@ -3,7 +3,7 @@ const LoopBackContext = require('loopback-context');
describe('InvoiceOut refund()', () => {
const userId = 5;
const ctx = {req: {accessToken: userId}};
const ctx = {req: {accessToken: userId}, args: {}};
const withWarehouse = true;
const activeCtx = {
accessToken: {userId: userId},

View File

@ -0,0 +1,68 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('InvoiceOut tranferInvoice()', () => {
const activeCtx = {
accessToken: {userId: 5},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
const ctx = {req: activeCtx};
beforeEach(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should return the id of the created issued invoice', async() => {
const tx = await models.InvoiceOut.beginTransaction({});
const options = {transaction: tx};
const args = {
id: '1',
ref: 'T4444444',
newClientFk: 1,
cplusRectificationId: 1,
siiTypeInvoiceOutId: 1,
invoiceCorrectionTypeId: 1
};
ctx.args = args;
try {
const result = await models.InvoiceOut.transferInvoice(
ctx,
options);
expect(result).toBeDefined();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should throw an UserError when it is the same client', async() => {
const tx = await models.InvoiceOut.beginTransaction({});
const options = {transaction: tx};
const args = {
id: '1',
ref: 'T1111111',
newClientFk: 1101,
cplusRectificationId: 1,
siiTypeInvoiceOutId: 1,
invoiceCorrectionTypeId: 1
};
ctx.args = args;
try {
await models.InvoiceOut.transferInvoice(
ctx,
options);
} catch (e) {
expect(e.message).toBe(`Select a different client`);
await tx.rollback();
}
});
});

View File

@ -0,0 +1,111 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('transferInvoice', {
description: 'Transfer an issued invoice to another client',
accessType: 'WRITE',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'Issued invoice id'
},
{
arg: 'ref',
type: 'string',
required: true
},
{
arg: 'newClientFk',
type: 'number',
required: true
},
{
arg: 'cplusRectificationId',
type: 'number',
required: true
},
{
arg: 'siiTypeInvoiceOutId',
type: 'number',
required: true
},
{
arg: 'invoiceCorrectionTypeId',
type: 'number',
required: true
},
],
returns: {
type: 'boolean',
root: true
},
http: {
path: '/transferInvoice',
verb: 'post'
}
});
Self.transferInvoice = async(ctx, options) => {
const models = Self.app.models;
const myOptions = {userId: ctx.req.accessToken.userId};
const args = ctx.args;
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
const {clientFk} = await models.InvoiceOut.findById(args.id);
if (clientFk == args.newClientFk)
throw new UserError(`Select a different client`);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const filterRef = {where: {refFk: args.ref}};
const tickets = await models.Ticket.find(filterRef, myOptions);
const ticketsIds = tickets.map(ticket => ticket.id);
await models.Ticket.refund(ctx, ticketsIds, null, myOptions);
const filterTicket = {where: {ticketFk: {inq: ticketsIds}}};
const services = await models.TicketService.find(filterTicket, myOptions);
const servicesIds = services.map(service => service.id);
const sales = await models.Sale.find(filterTicket, myOptions);
const salesIds = sales.map(sale => sale.id);
const clonedTickets = await models.Sale.clone(ctx, salesIds, servicesIds, null, false, false, myOptions);
const clonedTicketIds = [];
for (const clonedTicket of clonedTickets) {
await clonedTicket.updateAttribute('clientFk', args.newClientFk, myOptions);
clonedTicketIds.push(clonedTicket.id);
}
const invoiceIds = await models.Ticket.invoiceTickets(ctx, clonedTicketIds, myOptions);
const [invoiceId] = invoiceIds;
await models.InvoiceCorrection.create({
correctingFk: invoiceId,
correctedFk: args.id,
cplusRectificationTypeFk: args.cplusRectificationId,
siiTypeInvoiceOutFk: args.siiTypeInvoiceOutId,
invoiceCorrectionTypeFk: args.invoiceCorrectionTypeId
}, myOptions);
if (tx) {
await tx.commit();
await models.InvoiceOut.makePdfAndNotify(ctx, invoiceId, null);
}
return invoiceId;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -31,5 +31,17 @@
},
"ZipConfig": {
"dataSource": "vn"
},
"CplusRectificationType": {
"dataSource": "vn"
},
"InvoiceCorrectionType": {
"dataSource": "vn"
},
"InvoiceCorrection": {
"dataSource": "vn"
},
"SiiTypeInvoiceOut": {
"dataSource": "vn"
}
}

View File

@ -0,0 +1,19 @@
{
"name": "CplusRectificationType",
"base": "VnModel",
"options": {
"mysql": {
"table": "cplusRectificationType"
}
},
"properties": {
"id": {
"id": true,
"type": "number",
"description": "Identifier"
},
"description": {
"type": "string"
}
}
}

View File

@ -0,0 +1,19 @@
{
"name": "InvoiceCorrectionType",
"base": "VnModel",
"options": {
"mysql": {
"table": "invoiceCorrectionType"
}
},
"properties": {
"id": {
"id": true,
"type": "number",
"description": "Identifier"
},
"description": {
"type": "string"
}
}
}

View File

@ -0,0 +1,28 @@
{
"name": "InvoiceCorrection",
"base": "VnModel",
"options": {
"mysql": {
"table": "invoiceCorrection"
}
},
"properties": {
"correctingFk": {
"id": true,
"type": "number",
"description": "Identifier"
},
"correctedFk": {
"type": "number"
},
"cplusRectificationTypeFk": {
"type": "number"
},
"siiTypeInvoiceOutFk": {
"type": "number"
},
"invoiceCorrectionTypeFk": {
"type": "number"
}
}
}

View File

@ -23,6 +23,7 @@ module.exports = Self => {
require('../methods/invoiceOut/getInvoiceDate')(Self);
require('../methods/invoiceOut/negativeBases')(Self);
require('../methods/invoiceOut/negativeBasesCsv')(Self);
require('../methods/invoiceOut/transferInvoice')(Self);
Self.filePath = async function(id, options) {
const fields = ['ref', 'issued'];

View File

@ -0,0 +1,19 @@
{
"name": "SiiTypeInvoiceOut",
"base": "VnModel",
"options": {
"mysql": {
"table": "siiTypeInvoiceOut"
}
},
"properties": {
"id": {
"id": true,
"type": "number",
"description": "Identifier"
},
"description": {
"type": "string"
}
}
}

View File

@ -1,3 +1,19 @@
<vn-crud-model
auto-load="true"
url="CplusRectificationTypes"
data="cplusRectificationTypes"
order="description">
</vn-crud-model>
<vn-crud-model
auto-load="true"
url="SiiTypeInvoiceOuts"
data="siiTypeInvoiceOut">
</vn-crud-model>
<vn-crud-model
auto-load="true"
url="InvoiceCorrectionTypes"
data="invoiceCorrectionTypes">
</vn-crud-model>
<vn-icon-button
icon="more_vert"
@ -5,6 +21,13 @@
</vn-icon-button>
<vn-menu vn-id="menu">
<vn-list>
<vn-item
vn-acl="administrative"
vn-acl-action="remove"
ng-click="transferInvoice.show()"
translate>
Transfer invoice to...
</vn-item>
<vn-item class="dropdown"
vn-click-stop="showInvoiceMenu.show($event, 'left')"
name="showInvoicePdf"
@ -157,3 +180,71 @@
<button response="accept" translate>Confirm</button>
</tpl-buttons>
</vn-dialog>
<vn-dialog
vn-id="transferInvoice"
title="transferInvoice"
on-accept="$ctrl.transferInvoice()">
<tpl-body>
<section class="transferInvoice">
<vn-horizontal>
<vn-autocomplete
vn-one
vn-id="client"
required="true"
url="Clients"
label="Client"
show-field="name"
value-field="id"
search-function="{or: [{id: $search}, {name: {like: '%'+ $search +'%'}}]}"
ng-model="$ctrl.invoiceOut.client.id"
initial-data="$ctrl.invoiceOut.client.id"
order="id">
<tpl-item>
#{{id}} - {{::name}}
</tpl-item>
</vn-autocomplete>
<vn-autocomplete
vn-one
vn-id="cplusRectificationType"
required="true"
data="cplusRectificationTypes"
show-field="description"
value-field="id"
ng-model="$ctrl.cplusRectificationType"
search-function="{or: [{id: $search}, {description: {like: '%'+ $search +'%'}}]}"
label="Cplus Type">
<tpl-item>
{{::description}}
</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
vn-id="cplusInvoiceType"
data="siiTypeInvoiceOut"
show-field="description"
value-field="id"
required="true"
ng-model="$ctrl.siiTypeInvoiceOut"
search-function="{or: [{id: $search}, {description: {like: '%'+ $search +'%'}}]}"
label="Class">
</vn-autocomplete>
<vn-autocomplete
vn-one
vn-id="invoiceCorrectionType"
data="invoiceCorrectionTypes"
ng-model="$ctrl.invoiceCorrectionType"
show-field="description"
value-field="id"
required="true"
label="Type">
</vn-autocomplete>
</vn-horizontal>
</section>
</tpl-body>
<tpl-buttons>
<button response="accept" translate>Transfer client</button>
</tpl-buttons>
</vn-dialog>

View File

@ -125,6 +125,22 @@ class Controller extends Section {
this.$state.go('ticket.card.sale', {id: refundTicket.id});
});
}
transferInvoice() {
const params = {
id: this.invoiceOut.id,
ref: this.invoiceOut.ref,
newClientFk: this.invoiceOut.client.id,
cplusRectificationId: this.cplusRectificationType,
siiTypeInvoiceOutId: this.siiTypeInvoiceOut,
invoiceCorrectionTypeId: this.invoiceCorrectionType
};
this.$http.post(`InvoiceOuts/transferInvoice`, params).then(res => {
const invoiceId = res.data;
this.vnApp.showSuccess(this.$t('Invoice trasfered!'));
this.$state.go('invoiceOut.card.summary', {id: invoiceId});
});
}
}
Controller.$inject = ['$element', '$scope', 'vnReport', 'vnEmail'];

View File

@ -1 +1,3 @@
The following refund tickets have been created: "The following refund tickets have been created: {{ticketIds}}"
Transfer invoice to...: Transfer invoice to...
Cplus Type: Cplus Type

View File

@ -21,3 +21,5 @@ The invoice PDF document has been regenerated: El documento PDF de la factura ha
The email can't be empty: El correo no puede estar vacío
The following refund tickets have been created: "Se han creado los siguientes tickets de abono: {{ticketIds}}"
Refund...: Abono...
Transfer invoice to...: Transferir factura a...
Cplus Type: Cplus Tipo

View File

@ -21,4 +21,10 @@ vn-invoice-out-descriptor-menu {
font-size: 1.75rem;
}
}
}
}
@media screen and (min-width: 1000px) {
.transferInvoice {
min-width: $width-md;
}
}

View File

@ -7,7 +7,7 @@ export default class Controller extends Section {
super($element, $);
this.vnReport = vnReport;
const now = new Date();
const now = Date.vnNew();
const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
const lastDayOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0);
this.params = {

View File

@ -20,8 +20,7 @@ module.exports = Self => {
},
{
arg: 'addressFk',
type: 'Number',
required: true,
type: 'Any',
description: 'The address id'
}],
http: {

View File

@ -1,6 +1,6 @@
{
"name": "ItemShelving",
"base": "VnModel",
"base": "Loggable",
"options": {
"mysql": {
"table": "itemShelving"

View File

@ -0,0 +1,10 @@
name: saleGroup
columns:
id: id
created: created
userFk: user
parkingFk: parking
sectorFk: sector
ticketFk: ticket
editorFk: editor

View File

@ -0,0 +1,10 @@
name: saleGroup
columns:
id: id
created: creado
userFk: usuario
parkingFk: parking
sectorFk: sector
ticketFk: ticket
editorFk: editor

View File

@ -1,7 +1,7 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethod('addExpeditionState', {
Self.remoteMethodCtx('addExpeditionState', {
description: 'Update an expedition state',
accessType: 'WRITE',
accepts: [
@ -12,18 +12,15 @@ module.exports = Self => {
description: 'Array of objects containing expeditionFk and stateCode'
}
],
returns: {
type: 'boolean',
root: true
},
http: {
path: `/addExpeditionState`,
verb: 'post'
}
});
Self.addExpeditionState = async(expeditions, options) => {
Self.addExpeditionState = async(ctx, expeditions, options) => {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
let tx;
const myOptions = {};
if (typeof options == 'object')
@ -51,6 +48,7 @@ module.exports = Self => {
await models.ExpeditionState.create({
expeditionFk: expedition.expeditionFk,
typeFk,
userFk: userId,
}, myOptions);
}

View File

@ -1,9 +1,9 @@
const models = require('vn-loopback/server/server').models;
describe('expeditionState addExpeditionState()', () => {
const ctx = {req: {accessToken: {userId: 9}}};
it('should update the expedition states', async() => {
const tx = await models.ExpeditionState.beginTransaction({});
try {
const options = {transaction: tx};
const payload = [
@ -13,7 +13,7 @@ describe('expeditionState addExpeditionState()', () => {
},
];
await models.ExpeditionState.addExpeditionState(payload, options);
await models.ExpeditionState.addExpeditionState(ctx, payload, options);
const expeditionState = await models.ExpeditionState.findOne({
where: {id: 5}
@ -39,7 +39,7 @@ describe('expeditionState addExpeditionState()', () => {
stateCode: 'DUMMY'
}
];
await models.ExpeditionState.addExpeditionState(payload, options);
await models.ExpeditionState.addExpeditionState(ctx, payload, options);
await tx.rollback();
} catch (e) {
@ -62,7 +62,7 @@ describe('expeditionState addExpeditionState()', () => {
}
];
await models.ExpeditionState.addExpeditionState(payload, options);
await models.ExpeditionState.addExpeditionState(ctx, payload, options);
await tx.rollback();
} catch (e) {

View File

@ -0,0 +1,122 @@
module.exports = Self => {
Self.clone = async(ctx, salesIds, servicesIds, withWarehouse, group, negative, options) => {
const models = Self.app.models;
const myOptions = {};
let tx;
const newTickets = [];
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const salesFilter = {
where: {id: {inq: salesIds}},
include: {
relation: 'components',
scope: {
fields: ['saleFk', 'componentFk', 'value']
}
}
};
const sales = await models.Sale.find(salesFilter, myOptions);
let ticketsIds = [...new Set(sales.map(sale => sale.ticketFk))];
const mappedTickets = new Map();
if (group) ticketsIds = [ticketsIds[0]];
for (let ticketId of ticketsIds) {
const newTicket = await createTicket(
ctx,
ticketId,
withWarehouse,
negative,
myOptions
);
newTickets.push(newTicket);
mappedTickets.set(ticketId, newTicket.id);
}
for (const sale of sales) {
const newTicketId = mappedTickets.get(sale.ticketFk);
const createdSale = await models.Sale.create({
ticketFk: newTicketId,
itemFk: sale.itemFk,
quantity: negative ? - sale.quantity : sale.quantity,
concept: sale.concept,
price: sale.price,
discount: sale.discount,
}, myOptions);
const components = sale.components();
for (const component of components)
component.saleFk = createdSale.id;
await models.SaleComponent.create(components, myOptions);
}
if (servicesIds && servicesIds.length) {
const servicesFilter = {
where: {id: {inq: servicesIds}}
};
const services = await models.TicketService.find(servicesFilter, myOptions);
for (const service of services) {
const newTicketId = mappedTickets.get(service.ticketFk);
await models.TicketService.create({
description: service.description,
quantity: negative ? - service.quantity : service.quantity,
price: service.price,
taxClassFk: service.taxClassFk,
ticketFk: newTicketId,
ticketServiceTypeFk: service.ticketServiceTypeFk,
}, myOptions);
}
}
if (tx) await tx.commit();
return newTickets;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
async function createTicket(
ctx,
ticketId,
withWarehouse,
negative,
myOptions
) {
const models = Self.app.models;
const now = Date.vnNew();
const ticket = await models.Ticket.findById(ticketId, null, myOptions);
ctx.args.clientId = ticket.clientFk;
ctx.args.shipped = now;
ctx.args.landed = now;
ctx.args.warehouseId = withWarehouse ? ticket.warehouseFk : null;
ctx.args.companyId = ticket.companyFk;
ctx.args.addressId = ticket.addressFk;
const newTicket = await models.Ticket.new(ctx, myOptions);
if (negative) {
await models.TicketRefund.create({
originalTicketFk: ticketId,
refundTicketFk: newTicket.id
}, myOptions);
}
return newTicket;
}
};
};

View File

@ -5,7 +5,8 @@ module.exports = Self => {
accepts: [
{
arg: 'salesIds',
type: ['number']
type: ['number'],
required: true
},
{
arg: 'servicesIds',
@ -40,122 +41,23 @@ module.exports = Self => {
myOptions.transaction = tx;
}
let refundTicket = null;
try {
const refundAgencyMode = await models.AgencyMode.findOne({
include: {
relation: 'zones',
scope: {
limit: 1,
field: ['id', 'name']
}
},
where: {code: 'refund'}
}, myOptions);
const refoundZoneId = refundAgencyMode.zones()[0].id;
if (salesIds.length) {
const salesFilter = {
where: {id: {inq: salesIds}},
include: {
relation: 'components',
scope: {
fields: ['saleFk', 'componentFk', 'value']
}
}
};
const sales = await models.Sale.find(salesFilter, myOptions);
const ticketsIds = [...new Set(sales.map(sale => sale.ticketFk))];
const now = Date.vnNew();
const [firstTicketId] = ticketsIds;
// eslint-disable-next-line max-len
refundTicket = await createTicketRefund(firstTicketId, now, refundAgencyMode, refoundZoneId, withWarehouse, myOptions);
for (const sale of sales) {
const createdSale = await models.Sale.create({
ticketFk: refundTicket.id,
itemFk: sale.itemFk,
quantity: - sale.quantity,
concept: sale.concept,
price: sale.price,
discount: sale.discount,
}, myOptions);
const components = sale.components();
for (const component of components)
component.saleFk = createdSale.id;
await models.SaleComponent.create(components, myOptions);
}
}
if (!refundTicket) {
const servicesFilter = {
where: {id: {inq: servicesIds}}
};
const services = await models.TicketService.find(servicesFilter, myOptions);
const firstTicketId = services[0].ticketFk;
const now = Date.vnNew();
// eslint-disable-next-line max-len
refundTicket = await createTicketRefund(firstTicketId, now, refundAgencyMode, refoundZoneId, withWarehouse, myOptions);
}
if (servicesIds && servicesIds.length > 0) {
const servicesFilter = {
where: {id: {inq: servicesIds}}
};
const services = await models.TicketService.find(servicesFilter, myOptions);
for (const service of services) {
await models.TicketService.create({
description: service.description,
quantity: - service.quantity,
price: service.price,
taxClassFk: service.taxClassFk,
ticketFk: refundTicket.id,
ticketServiceTypeFk: service.ticketServiceTypeFk,
}, myOptions);
}
}
const query = `CALL vn.ticket_recalc(?, NULL)`;
await Self.rawSql(query, [refundTicket.id], myOptions);
const refundsTicket = await models.Sale.clone(
ctx,
salesIds,
servicesIds,
withWarehouse,
false,
true,
myOptions
);
if (tx) await tx.commit();
return refundTicket;
return refundsTicket[0];
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
async function createTicketRefund(ticketId, now, refundAgencyMode, refoundZoneId, withWarehouse, myOptions) {
const models = Self.app.models;
const filter = {include: {relation: 'address'}};
const ticket = await models.Ticket.findById(ticketId, filter, myOptions);
const refundTicket = await models.Ticket.create({
clientFk: ticket.clientFk,
shipped: now,
addressFk: ticket.address().id,
agencyModeFk: refundAgencyMode.id,
nickname: ticket.address().nickname,
warehouseFk: withWarehouse ? ticket.warehouseFk : null,
companyFk: ticket.companyFk,
landed: now,
zoneFk: refoundZoneId
}, myOptions);
await models.TicketRefund.create({
refundTicketFk: refundTicket.id,
originalTicketFk: ticket.id,
}, myOptions);
return refundTicket;
}
};

View File

@ -3,7 +3,7 @@ const LoopBackContext = require('loopback-context');
describe('Sale refund()', () => {
const userId = 5;
const ctx = {req: {accessToken: userId}};
const ctx = {req: {accessToken: userId}, args: {}};
const activeCtx = {
accessToken: {userId},
};
@ -40,6 +40,7 @@ describe('Sale refund()', () => {
try {
const options = {transaction: tx};
const ticketsBefore = await models.Ticket.find({}, options);
const ticket = await models.Sale.refund(ctx, salesIds, servicesIds, withWarehouse, options);
@ -61,12 +62,13 @@ describe('Sale refund()', () => {
}
]
}, options);
const ticketsAfter = await models.Ticket.find({}, options);
const salesLength = refundedTicket.ticketSales().length;
const componentsLength = refundedTicket.ticketSales()[0].components().length;
expect(refundedTicket).toBeDefined();
expect(salesLength).toEqual(2);
expect(salesLength).toEqual(1);
expect(ticketsBefore.length).toEqual(ticketsAfter.length - 2);
expect(componentsLength).toEqual(4);
await tx.rollback();

View File

@ -64,7 +64,10 @@ module.exports = Self => {
const sale = await models.Sale.findById(id, filter, myOptions);
const oldQuantity = sale.quantity;
const result = await sale.updateAttributes({quantity: newQuantity}, myOptions);
const result = await sale.updateAttributes({
quantity: newQuantity,
originalQuantity: newQuantity
}, myOptions);
const salesPerson = sale.ticket().client().salesPersonUser();
if (salesPerson) {

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