diff --git a/.vscode/settings.json b/.vscode/settings.json
index 05d23f3bb..899dfc788 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -10,5 +10,9 @@
"eslint.format.enable": true,
"[javascript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
- }
+ },
+ "cSpell.words": [
+ "salix",
+ "fdescribe"
+ ]
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 29d270a3e..08a62f044 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,17 @@ 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
diff --git a/README.md b/README.md
index 59f5dbcf7..b420bc44f 100644
--- a/README.md
+++ b/README.md
@@ -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/)
diff --git a/back/methods/notification/getList.js b/back/methods/notification/getList.js
new file mode 100644
index 000000000..3881f0f63
--- /dev/null
+++ b/back/methods/notification/getList.js
@@ -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()]
+ };
+ };
+};
diff --git a/back/methods/notification/specs/getList.spec.js b/back/methods/notification/specs/getList.spec.js
new file mode 100644
index 000000000..52ac497a5
--- /dev/null
+++ b/back/methods/notification/specs/getList.spec.js
@@ -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);
+ });
+});
diff --git a/back/methods/vn-user/sign-in.js b/back/methods/vn-user/sign-in.js
index b9e0d2f70..25f708b8e 100644
--- a/back/methods/vn-user/sign-in.js
+++ b/back/methods/vn-user/sign-in.js
@@ -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({
+ id: validateLogin.token,
+ userFk: vnUser.id,
+ ip: ctx.req.ip
+ });
+ return validateLogin;
};
Self.passExpired = async vnUser => {
diff --git a/back/models/notificationSubscription.js b/back/models/notificationSubscription.js
index f1b2811fa..8efb83e7d 100644
--- a/back/models/notificationSubscription.js
+++ b/back/models/notificationSubscription.js
@@ -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;
+ };
};
diff --git a/back/models/specs/notificationSubscription.spec.js b/back/models/specs/notificationSubscription.spec.js
index c7f37abed..c2adcbc59 100644
--- a/back/models/specs/notificationSubscription.spec.js
+++ b/back/models/specs/notificationSubscription.spec.js
@@ -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();
});
});
diff --git a/back/models/vn-user.js b/back/models/vn-user.js
index de5bf7b63..5845c2192 100644
--- a/back/models/vn-user.js
+++ b/back/models/vn-user.js
@@ -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);
@@ -121,10 +126,16 @@ module.exports = function(Self) {
});
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();
+
+ if (userToken.username.toLowerCase() !== user.toLowerCase()) {
+ console.error('ERROR!!! - Signin with other user', userToken, user);
+ throw new UserError('Try again');
+ }
+
try {
await Self.app.models.Account.sync(userToken.name, password);
} catch (err) {
@@ -220,8 +231,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
};
diff --git a/db/.archive/225201/00-invoiceOut_new.sql b/db/.archive/225201/00-invoiceOut_new.sql
index 4c60b50bc..8e23fb43b 100644
--- a/db/.archive/225201/00-invoiceOut_new.sql
+++ b/db/.archive/225201/00-invoiceOut_new.sql
@@ -74,7 +74,7 @@ BEGIN
clientFk,
dued,
companyFk,
- cplusInvoiceType477Fk
+ siiTypeInvoiceOutFk
)
SELECT
1,
diff --git a/db/.archive/231001/02-invoiceOut_new.sql b/db/.archive/231001/02-invoiceOut_new.sql
index d2b96eff7..d570dfb72 100644
--- a/db/.archive/231001/02-invoiceOut_new.sql
+++ b/db/.archive/231001/02-invoiceOut_new.sql
@@ -96,7 +96,7 @@ BEGIN
clientFk,
dued,
companyFk,
- cplusInvoiceType477Fk
+ siiTypeInvoiceOutFk
)
SELECT
1,
diff --git a/db/.archive/232001/00-invoiceOut_new.sql b/db/.archive/232001/00-invoiceOut_new.sql
index b4fc5c824..b497dffda 100644
--- a/db/.archive/232001/00-invoiceOut_new.sql
+++ b/db/.archive/232001/00-invoiceOut_new.sql
@@ -96,7 +96,7 @@ BEGIN
clientFk,
dued,
companyFk,
- cplusInvoiceType477Fk
+ siiTypeInvoiceOutFk
)
SELECT
1,
diff --git a/db/changes/234201/00-packagingFkviews.sql b/db/changes/234201/00-packagingFkviews.sql
index abc7dc004..49d41c26c 100644
--- a/db/changes/234201/00-packagingFkviews.sql
+++ b/db/changes/234201/00-packagingFkviews.sql
@@ -1,3 +1,5 @@
+CREATE SCHEMA IF NOT EXISTS `vn2008`;
+
CREATE OR REPLACE DEFINER=`root`@`localhost`
SQL SECURITY DEFINER
VIEW `vn`.`awbVolume`
diff --git a/db/changes/234601/00-ACLticketTrackingState.sql b/db/changes/234601/00-ACLticketTrackingState.sql
index 0f7bd4f44..ca6dce0c9 100644
--- a/db/changes/234601/00-ACLticketTrackingState.sql
+++ b/db/changes/234601/00-ACLticketTrackingState.sql
@@ -2,11 +2,3 @@ UPDATE `salix`.`ACL`
SET `property` = 'state',
`model` = 'Ticket'
WHERE `property` = 'changeState';
-
-REVOKE INSERT, UPDATE, DELETE ON `vn`.`ticketTracking` FROM 'productionboss'@;
-REVOKE INSERT, UPDATE, DELETE ON `vn`.`ticketTracking` FROM 'productionAssi'@;
-REVOKE INSERT, UPDATE, DELETE ON `vn`.`ticketTracking` FROM 'hr'@;
-REVOKE INSERT, UPDATE, DELETE ON `vn`.`ticketTracking` FROM 'salesPerson'@;
-REVOKE INSERT, UPDATE, DELETE ON `vn`.`ticketTracking` FROM 'deliveryPerson'@;
-REVOKE INSERT, UPDATE, DELETE ON `vn`.`ticketTracking` FROM 'employee'@;
-REVOKE EXECUTE ON `vn`.`ticket_setState` FROM 'employee'@;
diff --git a/db/changes/234601/00-addressShortage.sql b/db/changes/234601/00-addressShortage.sql
new file mode 100644
index 000000000..57c07d480
--- /dev/null
+++ b/db/changes/234601/00-addressShortage.sql
@@ -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 ;
diff --git a/db/changes/233601/00-createClaimReader.sql b/db/changes/234601/00-claimViewerAcl.sql
similarity index 86%
rename from db/changes/233601/00-createClaimReader.sql
rename to db/changes/234601/00-claimViewerAcl.sql
index e913c0ed9..17d8d4ce0 100644
--- a/db/changes/233601/00-createClaimReader.sql
+++ b/db/changes/234601/00-claimViewerAcl.sql
@@ -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');
\ No newline at end of file
+INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalId`)
+ VALUES ('Claim','getSummary','READ','ALLOW','ROLE','claimViewer');
diff --git a/db/changes/234601/00-clientAfterUpdate.sql b/db/changes/234601/00-clientAfterUpdate.sql
new file mode 100644
index 000000000..c6483813f
--- /dev/null
+++ b/db/changes/234601/00-clientAfterUpdate.sql
@@ -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 ;
+
diff --git a/db/changes/234601/00-transferInvoice.sql b/db/changes/234601/00-transferInvoice.sql
index 7a9890ae4..d9ae39464 100644
--- a/db/changes/234601/00-transferInvoice.sql
+++ b/db/changes/234601/00-transferInvoice.sql
@@ -1,6 +1,6 @@
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('CplusRectificationType', '*', 'READ', 'ALLOW', 'ROLE', 'administrative'),
- ('CplusInvoiceType477', '*', 'READ', 'ALLOW', 'ROLE', 'administrative'),
+ ('SiiTypeInvoiceOut', '*', 'READ', 'ALLOW', 'ROLE', 'administrative'),
('InvoiceCorrectionType', '*', 'READ', 'ALLOW', 'ROLE', 'administrative'),
('InvoiceOut', 'transferInvoice', 'WRITE', 'ALLOW', 'ROLE', 'administrative');
diff --git a/db/changes/234602/00-roleSync.sql b/db/changes/234602/00-roleSync.sql
new file mode 100644
index 000000000..7ce277748
--- /dev/null
+++ b/db/changes/234602/00-roleSync.sql
@@ -0,0 +1 @@
+CALL `account`.`role_sync`();
diff --git a/db/changes/234603/00-createSignInLogTable.sql b/db/changes/234603/00-createSignInLogTable.sql
new file mode 100644
index 000000000..977de4646
--- /dev/null
+++ b/db/changes/234603/00-createSignInLogTable.sql
@@ -0,0 +1,19 @@
+
+
+--
+-- Table structure for table `signInLog`
+--
+
+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` varchar(10) 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,
+ PRIMARY KEY (`id`),
+ KEY `userFk` (`userFk`),
+ CONSTRAINT `signInLog_ibfk_1` FOREIGN KEY (`userFk`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+);
+
diff --git a/db/changes/234801/.gitkeep b/db/changes/234801/.gitkeep
new file mode 100644
index 000000000..e69de29bb
diff --git a/db/changes/234801/00-ACL_executeRoutine.sql b/db/changes/234801/00-ACL_executeRoutine.sql
new file mode 100644
index 000000000..cfe7018e9
--- /dev/null
+++ b/db/changes/234801/00-ACL_executeRoutine.sql
@@ -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');
diff --git a/db/changes/234801/00-notificationSubscription.sql b/db/changes/234801/00-notificationSubscription.sql
new file mode 100644
index 000000000..ef081437d
--- /dev/null
+++ b/db/changes/234801/00-notificationSubscription.sql
@@ -0,0 +1,3 @@
+INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
+ VALUES
+ ('NotificationSubscription', 'getList', 'READ', 'ALLOW', 'ROLE', 'employee');
diff --git a/db/changes/234801/00-ticket_canAdvance_update.sql b/db/changes/234801/00-ticket_canAdvance_update.sql
new file mode 100644
index 000000000..afe0a4dc6
--- /dev/null
+++ b/db/changes/234801/00-ticket_canAdvance_update.sql
@@ -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 ;
diff --git a/db/dump/dumpedFixtures.sql b/db/dump/dumpedFixtures.sql
index 2e1511b59..43eba7f24 100644
--- a/db/dump/dumpedFixtures.sql
+++ b/db/dump/dumpedFixtures.sql
@@ -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;
--
diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql
index 9bc3e102a..eb0bea394 100644
--- a/db/dump/fixtures.sql
+++ b/db/dump/fixtures.sql
@@ -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
@@ -2788,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`)
@@ -2800,6 +2805,8 @@ INSERT INTO `util`.`notificationSubscription` (`notificationFk`, `userFk`)
VALUES
(1, 1109),
(1, 1110),
+ (2, 1110),
+ (4, 1110),
(2, 1109),
(1, 9),
(1, 3),
diff --git a/db/dump/structure.sql b/db/dump/structure.sql
index b242821fc..3d6156580 100644
--- a/db/dump/structure.sql
+++ b/db/dump/structure.sql
@@ -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,
diff --git a/db/export-data.sh b/db/export-data.sh
index 7d346b235..a516992d3 100755
--- a/db/export-data.sh
+++ b/db/export-data.sh
@@ -46,7 +46,7 @@ TABLES=(
bookingPlanner
businessType
cplusInvoiceType472
- cplusInvoiceType477
+ siiTypeInvoiceOut
cplusRectificationType
cplusSubjectOp
cplusTaxBreak
diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js
index cec0545a0..b5dbd42ac 100644
--- a/e2e/helpers/selectors.js
+++ b/e2e/helpers/selectors.js
@@ -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"]',
diff --git a/loopback/common/methods/application/execute.js b/loopback/common/methods/application/execute.js
new file mode 100644
index 000000000..a468dcd70
--- /dev/null
+++ b/loopback/common/methods/application/execute.js
@@ -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];
+ };
+};
diff --git a/loopback/common/methods/application/executeFunc.js b/loopback/common/methods/application/executeFunc.js
new file mode 100644
index 000000000..a42fdae67
--- /dev/null
+++ b/loopback/common/methods/application/executeFunc.js
@@ -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];
+ };
+};
diff --git a/loopback/common/methods/application/executeProc.js b/loopback/common/methods/application/executeProc.js
new file mode 100644
index 000000000..a8825da0f
--- /dev/null
+++ b/loopback/common/methods/application/executeProc.js
@@ -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);
+ };
+};
diff --git a/loopback/common/methods/application/spec/execute.spec.js b/loopback/common/methods/application/spec/execute.spec.js
new file mode 100644
index 000000000..1a0a8ace9
--- /dev/null
+++ b/loopback/common/methods/application/spec/execute.spec.js
@@ -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;
+ }
+ });
+ });
+});
diff --git a/loopback/common/models/application.js b/loopback/common/models/application.js
index 5e767fdc1..ac8ae78f0 100644
--- a/loopback/common/models/application.js
+++ b/loopback/common/models/application.js
@@ -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);
};
diff --git a/loopback/common/models/procs-priv.json b/loopback/common/models/procs-priv.json
new file mode 100644
index 000000000..25221d586
--- /dev/null
+++ b/loopback/common/models/procs-priv.json
@@ -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"
+ }
+ }
+ }
+}
diff --git a/loopback/locale/en.json b/loopback/locale/en.json
index 26650175d..949136459 100644
--- a/loopback/locale/en.json
+++ b/loopback/locale/en.json
@@ -196,6 +196,6 @@
"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"
}
-
diff --git a/loopback/locale/es.json b/loopback/locale/es.json
index 3cc9a9627..a4de6f997 100644
--- a/loopback/locale/es.json
+++ b/loopback/locale/es.json
@@ -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",
@@ -321,9 +321,11 @@
"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",
- "Ticket without Route": "Ticket sin ruta",
"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"
}
diff --git a/loopback/server/model-config.json b/loopback/server/model-config.json
index 52b539f60..33ef3797d 100644
--- a/loopback/server/model-config.json
+++ b/loopback/server/model-config.json
@@ -49,5 +49,13 @@
},
"Container": {
"dataSource": "vn"
+ },
+ "ProcsPriv": {
+ "dataSource": "vn",
+ "options": {
+ "mysql": {
+ "table": "mysql.procs_priv"
+ }
+ }
}
-}
\ No newline at end of file
+}
diff --git a/modules/account/back/model-config.json b/modules/account/back/model-config.json
index a4eb9fa57..b4bd6dbaf 100644
--- a/modules/account/back/model-config.json
+++ b/modules/account/back/model-config.json
@@ -35,6 +35,9 @@
"SambaConfig": {
"dataSource": "vn"
},
+ "SignInLog": {
+ "dataSource": "vn"
+ },
"Sip": {
"dataSource": "vn"
},
diff --git a/modules/account/back/models/sign_in-log.json b/modules/account/back/models/sign_in-log.json
new file mode 100644
index 000000000..44575b013
--- /dev/null
+++ b/modules/account/back/models/sign_in-log.json
@@ -0,0 +1,34 @@
+{
+ "name": "SignInLog",
+ "base": "VnModel",
+ "options": {
+ "mysql": {
+ "table": "account.signInLog"
+ }
+ },
+ "properties": {
+ "id": {
+ "id": true,
+ "type": "string"
+ },
+ "creationDate": {
+ "type": "date"
+ },
+ "userFk": {
+ "type": "number"
+ },
+ "ip": {
+ "type": "string"
+ }
+ },
+ "relations": {
+ "user": {
+ "type": "belongsTo",
+ "model": "VnUser",
+ "foreignKey": "userFk"
+ }
+ },
+ "scope": {
+ "order": ["creationDate DESC", "id DESC"]
+ }
+}
diff --git a/modules/client/back/model-config.json b/modules/client/back/model-config.json
index 296b5e6a7..0cc5df9a2 100644
--- a/modules/client/back/model-config.json
+++ b/modules/client/back/model-config.json
@@ -5,6 +5,9 @@
"AddressObservation": {
"dataSource": "vn"
},
+ "AddressShortage": {
+ "dataSource": "vn"
+ },
"BankEntity": {
"dataSource": "vn"
},
diff --git a/modules/client/back/models/addressShortage.json b/modules/client/back/models/addressShortage.json
new file mode 100644
index 000000000..1ae8d986c
--- /dev/null
+++ b/modules/client/back/models/addressShortage.json
@@ -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"
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/client/back/models/client-config.json b/modules/client/back/models/client-config.json
index 90d47333d..6c5eae7d7 100644
--- a/modules/client/back/models/client-config.json
+++ b/modules/client/back/models/client-config.json
@@ -17,6 +17,9 @@
},
"maxCreditRows": {
"type": "number"
+ },
+ "defaultCredit": {
+ "type": "number"
}
}
}
\ No newline at end of file
diff --git a/modules/client/back/models/client.js b/modules/client/back/models/client.js
index e16e884cc..72b702779 100644
--- a/modules/client/back/models/client.js
+++ b/modules/client/back/models/client.js
@@ -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) {
diff --git a/modules/client/back/models/client.json b/modules/client/back/models/client.json
index 66a67ec2e..f32915bb5 100644
--- a/modules/client/back/models/client.json
+++ b/modules/client/back/models/client.json
@@ -158,7 +158,7 @@
},
"user": {
"type": "belongsTo",
- "model": "Account",
+ "model": "VnUser",
"foreignKey": "id"
},
"payMethod": {
diff --git a/modules/client/back/models/specs/client.spec.js b/modules/client/back/models/specs/client.spec.js
index 201d14bb4..bf134fbf9 100644
--- a/modules/client/back/models/specs/client.spec.js
+++ b/modules/client/back/models/specs/client.spec.js
@@ -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'}
diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/transferinvoice.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/transferinvoice.spec.js
index 04f6df299..800a4ea83 100644
--- a/modules/invoiceOut/back/methods/invoiceOut/specs/transferinvoice.spec.js
+++ b/modules/invoiceOut/back/methods/invoiceOut/specs/transferinvoice.spec.js
@@ -27,7 +27,7 @@ describe('InvoiceOut tranferInvoice()', () => {
ref: 'T4444444',
newClientFk: 1,
cplusRectificationId: 1,
- cplusInvoiceType477Id: 1,
+ siiTypeInvoiceOutId: 1,
invoiceCorrectionTypeId: 1
};
ctx.args = args;
@@ -52,7 +52,7 @@ describe('InvoiceOut tranferInvoice()', () => {
ref: 'T1111111',
newClientFk: 1101,
cplusRectificationId: 1,
- cplusInvoiceType477Id: 1,
+ siiTypeInvoiceOutId: 1,
invoiceCorrectionTypeId: 1
};
ctx.args = args;
diff --git a/modules/invoiceOut/back/methods/invoiceOut/transferInvoice.js b/modules/invoiceOut/back/methods/invoiceOut/transferInvoice.js
index 8a0609b8d..dde535c99 100644
--- a/modules/invoiceOut/back/methods/invoiceOut/transferInvoice.js
+++ b/modules/invoiceOut/back/methods/invoiceOut/transferInvoice.js
@@ -27,7 +27,7 @@ module.exports = Self => {
required: true
},
{
- arg: 'cplusInvoiceType477Id',
+ arg: 'siiTypeInvoiceOutId',
type: 'number',
required: true
},
@@ -93,7 +93,7 @@ module.exports = Self => {
correctingFk: invoiceId,
correctedFk: args.id,
cplusRectificationTypeFk: args.cplusRectificationId,
- cplusInvoiceType477Fk: args.cplusInvoiceType477Id,
+ siiTypeInvoiceOutFk: args.siiTypeInvoiceOutId,
invoiceCorrectionTypeFk: args.invoiceCorrectionTypeId
}, myOptions);
diff --git a/modules/invoiceOut/back/model-config.json b/modules/invoiceOut/back/model-config.json
index 23246893b..9c7512429 100644
--- a/modules/invoiceOut/back/model-config.json
+++ b/modules/invoiceOut/back/model-config.json
@@ -41,7 +41,7 @@
"InvoiceCorrection": {
"dataSource": "vn"
},
- "CplusInvoiceType477": {
+ "SiiTypeInvoiceOut": {
"dataSource": "vn"
}
}
diff --git a/modules/invoiceOut/back/models/invoice-correction.json b/modules/invoiceOut/back/models/invoice-correction.json
index 48bd172a6..43e4f07ef 100644
--- a/modules/invoiceOut/back/models/invoice-correction.json
+++ b/modules/invoiceOut/back/models/invoice-correction.json
@@ -18,11 +18,11 @@
"cplusRectificationTypeFk": {
"type": "number"
},
- "cplusInvoiceType477Fk": {
+ "siiTypeInvoiceOutFk": {
"type": "number"
},
"invoiceCorrectionTypeFk": {
"type": "number"
}
}
-}
\ No newline at end of file
+}
diff --git a/modules/invoiceOut/back/models/cplus-invoice-type-477.json b/modules/invoiceOut/back/models/sii-type-invoice-out.json
similarity index 78%
rename from modules/invoiceOut/back/models/cplus-invoice-type-477.json
rename to modules/invoiceOut/back/models/sii-type-invoice-out.json
index 840a9a7e4..17b312617 100644
--- a/modules/invoiceOut/back/models/cplus-invoice-type-477.json
+++ b/modules/invoiceOut/back/models/sii-type-invoice-out.json
@@ -1,9 +1,9 @@
{
- "name": "CplusInvoiceType477",
+ "name": "SiiTypeInvoiceOut",
"base": "VnModel",
"options": {
"mysql": {
- "table": "cplusInvoiceType477"
+ "table": "siiTypeInvoiceOut"
}
},
"properties": {
@@ -16,4 +16,4 @@
"type": "string"
}
}
-}
\ No newline at end of file
+}
diff --git a/modules/invoiceOut/front/descriptor-menu/index.html b/modules/invoiceOut/front/descriptor-menu/index.html
index 7d465f4ea..0052f0c03 100644
--- a/modules/invoiceOut/front/descriptor-menu/index.html
+++ b/modules/invoiceOut/front/descriptor-menu/index.html
@@ -6,8 +6,8 @@
+ url="SiiTypeInvoiceOuts"
+ data="siiTypeInvoiceOut">
Transfer invoice to...
@@ -223,15 +223,15 @@
-
-
\ No newline at end of file
+
diff --git a/modules/invoiceOut/front/descriptor-menu/index.js b/modules/invoiceOut/front/descriptor-menu/index.js
index 7d2644158..d3862a753 100644
--- a/modules/invoiceOut/front/descriptor-menu/index.js
+++ b/modules/invoiceOut/front/descriptor-menu/index.js
@@ -132,7 +132,7 @@ class Controller extends Section {
ref: this.invoiceOut.ref,
newClientFk: this.invoiceOut.client.id,
cplusRectificationId: this.cplusRectificationType,
- cplusInvoiceType477Id: this.cplusInvoiceType477,
+ siiTypeInvoiceOutId: this.siiTypeInvoiceOut,
invoiceCorrectionTypeId: this.invoiceCorrectionType
};
this.$http.post(`InvoiceOuts/transferInvoice`, params).then(res => {
diff --git a/modules/invoiceOut/front/negative-bases/index.js b/modules/invoiceOut/front/negative-bases/index.js
index f60668b20..7ce610513 100644
--- a/modules/invoiceOut/front/negative-bases/index.js
+++ b/modules/invoiceOut/front/negative-bases/index.js
@@ -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 = {
diff --git a/modules/item/back/methods/item/setVisibleDiscard.js b/modules/item/back/methods/item/setVisibleDiscard.js
index 08cc507c3..16cef6baf 100644
--- a/modules/item/back/methods/item/setVisibleDiscard.js
+++ b/modules/item/back/methods/item/setVisibleDiscard.js
@@ -20,8 +20,7 @@ module.exports = Self => {
},
{
arg: 'addressFk',
- type: 'Number',
- required: true,
+ type: 'Any',
description: 'The address id'
}],
http: {
diff --git a/modules/item/back/models/item-shelving.json b/modules/item/back/models/item-shelving.json
index 61d05539e..bb1a141c4 100644
--- a/modules/item/back/models/item-shelving.json
+++ b/modules/item/back/models/item-shelving.json
@@ -1,6 +1,6 @@
{
"name": "ItemShelving",
- "base": "VnModel",
+ "base": "Loggable",
"options": {
"mysql": {
"table": "itemShelving"
diff --git a/modules/ticket/back/methods/sale/updateQuantity.js b/modules/ticket/back/methods/sale/updateQuantity.js
index 1a497da24..610826283 100644
--- a/modules/ticket/back/methods/sale/updateQuantity.js
+++ b/modules/ticket/back/methods/sale/updateQuantity.js
@@ -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) {
diff --git a/modules/ticket/back/methods/ticket/closeAll.js b/modules/ticket/back/methods/ticket/closeAll.js
index 660c16893..46c45aa92 100644
--- a/modules/ticket/back/methods/ticket/closeAll.js
+++ b/modules/ticket/back/methods/ticket/closeAll.js
@@ -32,31 +32,30 @@ module.exports = Self => {
throw new UserError('You cannot close tickets for today');
const tickets = await Self.rawSql(`
- SELECT
- t.id,
- t.clientFk,
- t.companyFk,
- c.name clientName,
- c.email recipient,
- c.salesPersonFk,
- c.isToBeMailed,
- c.hasToInvoice,
- co.hasDailyInvoice,
- eu.email salesPersonEmail
- FROM ticket t
- JOIN agencyMode am ON am.id = t.agencyModeFk
- JOIN warehouse wh ON wh.id = t.warehouseFk AND wh.hasComission
- JOIN ticketState ts ON ts.ticketFk = t.id
- JOIN alertLevel al ON al.id = ts.alertLevel
- JOIN client c ON c.id = t.clientFk
- JOIN province p ON p.id = c.provinceFk
- JOIN country co ON co.id = p.countryFk
- LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk
- WHERE (al.code = 'PACKED' OR (am.code = 'refund' AND al.code != 'delivered'))
- AND DATE(t.shipped) BETWEEN DATE_ADD(?, INTERVAL -2 DAY)
- AND util.dayEnd(?)
- AND t.refFk IS NULL
- GROUP BY t.id
+ SELECT t.id,
+ t.clientFk,
+ t.companyFk,
+ c.name clientName,
+ c.email recipient,
+ c.salesPersonFk,
+ c.isToBeMailed,
+ c.hasToInvoice,
+ co.hasDailyInvoice,
+ eu.email salesPersonEmail,
+ t.addressFk
+ FROM ticket t
+ JOIN agencyMode am ON am.id = t.agencyModeFk
+ JOIN warehouse wh ON wh.id = t.warehouseFk AND wh.hasComission
+ JOIN ticketState ts ON ts.ticketFk = t.id
+ JOIN alertLevel al ON al.id = ts.alertLevel
+ JOIN client c ON c.id = t.clientFk
+ JOIN province p ON p.id = c.provinceFk
+ JOIN country co ON co.id = p.countryFk
+ LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk
+ WHERE (al.code = 'PACKED' OR (am.code = 'refund' AND al.code != 'delivered'))
+ AND DATE(t.shipped) BETWEEN DATE_ADD(?, INTERVAL -2 DAY) AND util.dayEnd(?)
+ AND t.refFk IS NULL
+ GROUP BY t.id
`, [toDate, toDate]);
await closure(ctx, Self, tickets);
diff --git a/modules/ticket/back/methods/ticket/closure.js b/modules/ticket/back/methods/ticket/closure.js
index 9f9aec9bd..1d04679d3 100644
--- a/modules/ticket/back/methods/ticket/closure.js
+++ b/modules/ticket/back/methods/ticket/closure.js
@@ -5,177 +5,178 @@ const config = require('vn-print/core/config');
const storage = require('vn-print/core/storage');
module.exports = async function(ctx, Self, tickets, reqArgs = {}) {
- const userId = ctx.req.accessToken.userId;
- if (tickets.length == 0) return;
+ const userId = ctx.req.accessToken.userId;
+ if (tickets.length == 0) return;
- const failedtickets = [];
- for (const ticket of tickets) {
- try {
- await Self.rawSql(`CALL vn.ticket_closeByTicket(?)`, [ticket.id], {userId});
+ const failedtickets = [];
+ for (const ticket of tickets) {
+ try {
+ await Self.rawSql(`CALL vn.ticket_closeByTicket(?)`, [ticket.id], {userId});
- const [invoiceOut] = await Self.rawSql(`
- SELECT io.id, io.ref, io.serial, cny.code companyCode, io.issued
- FROM ticket t
- JOIN invoiceOut io ON io.ref = t.refFk
- JOIN company cny ON cny.id = io.companyFk
- WHERE t.id = ?
- `, [ticket.id]);
+ const [invoiceOut] = await Self.rawSql(`
+ SELECT io.id, io.ref, io.serial, cny.code companyCode, io.issued
+ FROM ticket t
+ JOIN invoiceOut io ON io.ref = t.refFk
+ JOIN company cny ON cny.id = io.companyFk
+ WHERE t.id = ?
+ `, [ticket.id]);
- const mailOptions = {
- overrideAttachments: true,
- attachments: []
- };
+ const mailOptions = {
+ overrideAttachments: true,
+ attachments: []
+ };
- const isToBeMailed = ticket.recipient && ticket.salesPersonFk && ticket.isToBeMailed;
+ const isToBeMailed = ticket.recipient && ticket.salesPersonFk && ticket.isToBeMailed;
- if (invoiceOut) {
- const args = {
- reference: invoiceOut.ref,
- recipientId: ticket.clientFk,
- recipient: ticket.recipient,
- replyTo: ticket.salesPersonEmail
- };
+ if (invoiceOut) {
+ const args = {
+ reference: invoiceOut.ref,
+ recipientId: ticket.clientFk,
+ recipient: ticket.recipient,
+ replyTo: ticket.salesPersonEmail
+ };
- const invoiceReport = new Report('invoice', args);
- const stream = await invoiceReport.toPdfStream();
+ const invoiceReport = new Report('invoice', args);
+ const stream = await invoiceReport.toPdfStream();
- const issued = invoiceOut.issued;
- const year = issued.getFullYear().toString();
- const month = (issued.getMonth() + 1).toString();
- const day = issued.getDate().toString();
+ const issued = invoiceOut.issued;
+ const year = issued.getFullYear().toString();
+ const month = (issued.getMonth() + 1).toString();
+ const day = issued.getDate().toString();
- const fileName = `${year}${invoiceOut.ref}.pdf`;
+ const fileName = `${year}${invoiceOut.ref}.pdf`;
- // Store invoice
- await storage.write(stream, {
- type: 'invoice',
- path: `${year}/${month}/${day}`,
- fileName: fileName
- });
+ // Store invoice
+ await storage.write(stream, {
+ type: 'invoice',
+ path: `${year}/${month}/${day}`,
+ fileName: fileName
+ });
- await Self.rawSql('UPDATE invoiceOut SET hasPdf = true WHERE id = ?', [invoiceOut.id], {userId});
+ await Self.rawSql('UPDATE invoiceOut SET hasPdf = true WHERE id = ?', [invoiceOut.id], {userId});
- if (isToBeMailed) {
- const invoiceAttachment = {
- filename: fileName,
- content: stream
- };
+ if (isToBeMailed) {
+ const invoiceAttachment = {
+ filename: fileName,
+ content: stream
+ };
- if (invoiceOut.serial == 'E' && invoiceOut.companyCode == 'VNL') {
- const exportation = new Report('exportation', args);
- const stream = await exportation.toPdfStream();
- const fileName = `CITES-${invoiceOut.ref}.pdf`;
+ if (invoiceOut.serial == 'E' && invoiceOut.companyCode == 'VNL') {
+ const exportation = new Report('exportation', args);
+ const stream = await exportation.toPdfStream();
+ const fileName = `CITES-${invoiceOut.ref}.pdf`;
- mailOptions.attachments.push({
- filename: fileName,
- content: stream
- });
- }
+ mailOptions.attachments.push({
+ filename: fileName,
+ content: stream
+ });
+ }
- mailOptions.attachments.push(invoiceAttachment);
+ mailOptions.attachments.push(invoiceAttachment);
- const email = new Email('invoice', args);
- await email.send(mailOptions);
- }
- } else if (isToBeMailed) {
- const args = {
- id: ticket.id,
- recipientId: ticket.clientFk,
- recipient: ticket.recipient,
- replyTo: ticket.salesPersonEmail
- };
+ const email = new Email('invoice', args);
+ await email.send(mailOptions);
+ }
+ } else if (isToBeMailed) {
+ const args = {
+ id: ticket.id,
+ recipientId: ticket.clientFk,
+ recipient: ticket.recipient,
+ replyTo: ticket.salesPersonEmail
+ };
- const email = new Email('delivery-note-link', args);
- await email.send();
- }
+ const email = new Email('delivery-note-link', args);
+ await email.send();
+ }
- // Incoterms authorization
- const [{firstOrder}] = await Self.rawSql(`
- SELECT COUNT(*) as firstOrder
- FROM ticket t
- JOIN client c ON c.id = t.clientFk
- WHERE t.clientFk = ?
- AND NOT t.isDeleted
- AND c.isVies
- `, [ticket.clientFk]);
+ // Incoterms authorization
+ const [{firstOrder}] = await Self.rawSql(`
+ SELECT COUNT(*) as firstOrder
+ FROM ticket t
+ JOIN client c ON c.id = t.clientFk
+ WHERE t.clientFk = ?
+ AND NOT t.isDeleted
+ AND c.isVies
+ `, [ticket.clientFk]);
- if (firstOrder == 1) {
- const args = {
- id: ticket.clientFk,
- companyId: ticket.companyFk,
- recipientId: ticket.clientFk,
- recipient: ticket.recipient,
- replyTo: ticket.salesPersonEmail
- };
+ if (firstOrder == 1) {
+ const args = {
+ id: ticket.clientFk,
+ companyId: ticket.companyFk,
+ recipientId: ticket.clientFk,
+ recipient: ticket.recipient,
+ replyTo: ticket.salesPersonEmail,
+ addressId: ticket.addressFk
+ };
- const email = new Email('incoterms-authorization', args);
- await email.send();
+ const email = new Email('incoterms-authorization', args);
+ await email.send();
- const [sample] = await Self.rawSql(
- `SELECT id
- FROM sample
- WHERE code = 'incoterms-authorization'
- `);
+ const [sample] = await Self.rawSql(
+ `SELECT id
+ FROM sample
+ WHERE code = 'incoterms-authorization'
+ `);
- await Self.rawSql(`
- INSERT INTO clientSample (clientFk, typeFk, companyFk) VALUES(?, ?, ?)
- `, [ticket.clientFk, sample.id, ticket.companyFk], {userId});
- };
- } catch (error) {
- // Domain not found
- if (error.responseCode == 450)
- return invalidEmail(ticket);
+ await Self.rawSql(`
+ INSERT INTO clientSample (clientFk, typeFk, companyFk) VALUES(?, ?, ?)
+ `, [ticket.clientFk, sample.id, ticket.companyFk], {userId});
+ }
+ } catch (error) {
+ // Domain not found
+ if (error.responseCode == 450)
+ return invalidEmail(ticket);
- // Save tickets on a list of failed ids
- failedtickets.push({
- id: ticket.id,
- stacktrace: error
- });
- }
- }
+ // Save tickets on a list of failed ids
+ failedtickets.push({
+ id: ticket.id,
+ stacktrace: error
+ });
+ }
+ }
- // Send email with failed tickets
- if (failedtickets.length > 0) {
- let body = 'This following tickets have failed:
';
+ // Send email with failed tickets
+ if (failedtickets.length > 0) {
+ let body = 'This following tickets have failed:
';
- for (const ticket of failedtickets) {
- body += `Ticket: ${ticket.id}
- ${ticket.stacktrace}
`;
- }
+ for (const ticket of failedtickets) {
+ body += `Ticket: ${ticket.id}
+ ${ticket.stacktrace}
`;
+ }
- smtp.send({
- to: config.app.reportEmail,
- subject: '[API] Nightly ticket closure report',
- html: body
- });
- }
+ smtp.send({
+ to: config.app.reportEmail,
+ subject: '[API] Nightly ticket closure report',
+ html: body
+ });
+ }
- async function invalidEmail(ticket) {
- await Self.rawSql(`UPDATE client SET email = NULL WHERE id = ?`, [
- ticket.clientFk
- ], {userId});
+ async function invalidEmail(ticket) {
+ await Self.rawSql(`UPDATE client SET email = NULL WHERE id = ?`, [
+ ticket.clientFk
+ ], {userId});
- const oldInstance = `{"email": "${ticket.recipient}"}`;
- const newInstance = `{"email": ""}`;
- await Self.rawSql(`
- INSERT INTO clientLog (originFk, userFk, action, changedModel, oldInstance, newInstance)
- VALUES (?, NULL, 'UPDATE', 'Client', ?, ?)`, [
- ticket.clientFk,
- oldInstance,
- newInstance
- ], {userId});
+ const oldInstance = `{"email": "${ticket.recipient}"}`;
+ const newInstance = `{"email": ""}`;
+ await Self.rawSql(`
+ INSERT INTO clientLog (originFk, userFk, action, changedModel, oldInstance, newInstance)
+ VALUES (?, NULL, 'UPDATE', 'Client', ?, ?)`, [
+ ticket.clientFk,
+ oldInstance,
+ newInstance
+ ], {userId});
- const body = `No se ha podido enviar el albarán ${ticket.id}
- al cliente ${ticket.clientFk} - ${ticket.clientName}
- porque la dirección de email "${ticket.recipient}" no es correcta
- o no está disponible.
- Para evitar que se repita este error, se ha eliminado la dirección de email de la ficha del cliente.
- Actualiza la dirección de email con una correcta.`;
+ const body = `No se ha podido enviar el albarán ${ticket.id}
+ al cliente ${ticket.clientFk} - ${ticket.clientName}
+ porque la dirección de email "${ticket.recipient}" no es correcta
+ o no está disponible.