diff --git a/.vscode/settings.json b/.vscode/settings.json index 3d98138e2..03479d27a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,27 +11,12 @@ "[javascript]": { "editor.defaultFormatter": "dbaeumer.vscode-eslint" }, + "[json]": { + "editor.defaultFormatter": "vscode.json-language-features" + }, "cSpell.words": [ "salix", - "fdescribe" - ], - "rest-client.previewOption": "body", - "rest-client.environmentVariables": { - "$shared": { - "version": "v1", - "prodToken": "verdnatura", - "nonProdToken": "verdnatura" - }, - "local": { - "version": "v2", - "host": "http://localhost:3000", - "token": "{{$shared nonProdToken}}", - "secretKey": "devSecret" - }, - "production": { - "host": "example.com", - "token": "{{$shared prodToken}}", - "secretKey" : "prodSecret" - } - } + "fdescribe", + "Loggable" + ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index dfdc563fb..69e93a309 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,20 @@ 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). -## [2352.01] - 2023-12-28 +## [2404.01] - 2024-01-25 + +### Added +### Changed +### Fixed + + +## [2402.01] - 2024-01-11 + +### Added +### Changed +### Fixed + +## [2400.01] - 2024-01-04 ### Added ### Changed @@ -13,9 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [2350.01] - 2023-12-14 -### Added -### Changed -### Fixed +### Características Añadidas 🆕 +- **Tickets → Expediciones:** Añadido soporte para Viaexpress ## [2348.01] - 2023-11-30 diff --git a/back/methods/dms/removeFile.js b/back/methods/dms/removeFile.js index a9ff36883..dc55b4d38 100644 --- a/back/methods/dms/removeFile.js +++ b/back/methods/dms/removeFile.js @@ -22,8 +22,8 @@ module.exports = Self => { Self.removeFile = async(ctx, id, options) => { const models = Self.app.models; - let tx; const myOptions = {}; + let tx; if (typeof options == 'object') Object.assign(myOptions, options); diff --git a/back/methods/docuware/specs/upload.spec.js b/back/methods/docuware/specs/upload.spec.js index 3b8c55a50..866499b66 100644 --- a/back/methods/docuware/specs/upload.spec.js +++ b/back/methods/docuware/specs/upload.spec.js @@ -24,15 +24,40 @@ describe('docuware upload()', () => { }); it('should try upload file', async() => { + const tx = await models.Docuware.beginTransaction({}); spyOn(ticketModel, 'deliveryNotePdf').and.returnValue(new Promise(resolve => resolve({}))); let error; try { - await models.Docuware.upload(ctx, ticketIds, fileCabinetName); + const options = {transaction: tx}; + const user = await models.UserConfig.findById(userId, null, options); + await user.updateAttribute('tabletFk', 'Tablet1', options); + await models.Docuware.upload(ctx, ticketIds, fileCabinetName, options); + + await tx.rollback(); } catch (e) { - error = e.message; + error = e; + await tx.rollback(); } - expect(error).toEqual('Action not allowed on the test environment'); + expect(error.message).toEqual('Action not allowed on the test environment'); + }); + + it('should throw error when not have tablet assigned', async() => { + const tx = await models.Docuware.beginTransaction({}); + spyOn(ticketModel, 'deliveryNotePdf').and.returnValue(new Promise(resolve => resolve({}))); + + let error; + try { + const options = {transaction: tx}; + await models.Docuware.upload(ctx, ticketIds, fileCabinetName, options); + + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toEqual('This user does not have an assigned tablet'); }); }); diff --git a/back/methods/docuware/upload.js b/back/methods/docuware/upload.js index 7055bf8d5..27be72295 100644 --- a/back/methods/docuware/upload.js +++ b/back/methods/docuware/upload.js @@ -29,12 +29,24 @@ module.exports = Self => { } }); - Self.upload = async function(ctx, ticketIds, fileCabinet) { + Self.upload = async function(ctx, ticketIds, fileCabinet, options) { delete ctx.args.ticketIds; const models = Self.app.models; const action = 'store'; - const options = await Self.getOptions(); + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const userConfig = await models.UserConfig.findById(ctx.req.accessToken.userId, { + fields: ['tabletFk'] + }, myOptions); + + if (!userConfig?.tabletFk) + throw new UserError('This user does not have an assigned tablet'); + + const docuwareOptions = await Self.getOptions(); const fileCabinetId = await Self.getFileCabinet(fileCabinet); const dialogId = await Self.getDialog(fileCabinet, action, fileCabinetId); @@ -45,7 +57,7 @@ module.exports = Self => { const deliveryNote = await models.Ticket.deliveryNotePdf(ctx, { id, type: 'deliveryNote' - }); + }, myOptions); // get ticket data const ticket = await models.Ticket.findById(id, { include: [{ @@ -54,7 +66,7 @@ module.exports = Self => { fields: ['id', 'name', 'fi'] } }] - }); + }, myOptions); // upload file const templateJson = { @@ -102,7 +114,7 @@ module.exports = Self => { { 'FieldName': 'FILTRO_TABLET', 'ItemElementName': 'string', - 'Item': 'Tablet1', + 'Item': userConfig.tabletFk, } ] }; @@ -116,11 +128,11 @@ module.exports = Self => { const deleteJson = { 'Field': [{'FieldName': 'ESTADO', 'Item': 'Pendiente eliminar', 'ItemElementName': 'String'}] }; - const deleteUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents/${docuwareFile.id}/Fields`; - await axios.put(deleteUri, deleteJson, options.headers); + const deleteUri = `${docuwareOptions.url}/FileCabinets/${fileCabinetId}/Documents/${docuwareFile.id}/Fields`; + await axios.put(deleteUri, deleteJson, docuwareOptions.headers); } - const uploadUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents?StoreDialogId=${dialogId}`; + const uploadUri = `${docuwareOptions.url}/FileCabinets/${fileCabinetId}/Documents?StoreDialogId=${dialogId}`; const FormData = require('form-data'); const data = new FormData(); @@ -130,7 +142,7 @@ module.exports = Self => { headers: { 'Content-Type': 'multipart/form-data', 'X-File-ModifiedDate': Date.vnNew(), - 'Cookie': options.headers.headers.Cookie, + 'Cookie': docuwareOptions.headers.headers.Cookie, ...data.getHeaders() }, }; @@ -141,11 +153,11 @@ module.exports = Self => { const $t = ctx.req.__; const message = $t('Failed to upload delivery note', {id}); if (uploaded.length) - await models.TicketTracking.setDelivered(ctx, uploaded); + await models.TicketTracking.setDelivered(ctx, uploaded, myOptions); throw new UserError(message); } uploaded.push(id); } - return models.TicketTracking.setDelivered(ctx, ticketIds); + return models.TicketTracking.setDelivered(ctx, ticketIds, myOptions); }; }; diff --git a/back/methods/vn-user/privileges.js b/back/methods/vn-user/privileges.js index 08cfaaae8..9f936c29b 100644 --- a/back/methods/vn-user/privileges.js +++ b/back/methods/vn-user/privileges.js @@ -68,7 +68,7 @@ module.exports = Self => { userToUpdate.hasGrant = hasGrant; if (roleFk) { - const role = await models.Role.findById(roleFk, {fields: ['name']}, myOptions); + const role = await models.VnRole.findById(roleFk, {fields: ['name']}, myOptions); const hasRole = await Self.hasRole(userId, role.name, myOptions); if (!hasRole) diff --git a/back/methods/vn-user/renew-token.js b/back/methods/vn-user/renew-token.js index 9850267d6..d00085d8a 100644 --- a/back/methods/vn-user/renew-token.js +++ b/back/methods/vn-user/renew-token.js @@ -1,4 +1,4 @@ -const UserError = require('vn-loopback/util/user-error'); +const {models} = require('vn-loopback/server/server'); module.exports = Self => { Self.remoteMethodCtx('renewToken', { @@ -16,20 +16,31 @@ module.exports = Self => { }); Self.renewToken = async function(ctx) { - const models = Self.app.models; - const token = ctx.req.accessToken; + const {accessToken: token} = ctx.req; - const now = new Date(); + // Check if current token is valid + + const {renewPeriod, courtesyTime} = await models.AccessTokenConfig.findOne({ + fields: ['renewPeriod', 'courtesyTime'] + }); + const now = Date.now(); const differenceMilliseconds = now - token.created; const differenceSeconds = Math.floor(differenceMilliseconds / 1000); + const isNotExceeded = differenceSeconds < renewPeriod - courtesyTime; + if (isNotExceeded) + return token; - const fields = ['renewPeriod', 'courtesyTime']; - const accessTokenConfig = await models.AccessTokenConfig.findOne({fields}); + // Schedule to remove current token + setTimeout(async() => { + try { + await Self.logout(token.id); + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + } + }, courtesyTime * 1000); - if (differenceSeconds < accessTokenConfig.renewPeriod - accessTokenConfig.courtesyTime) - throw new UserError(`The renew period has not been exceeded`, 'periodNotExceeded'); - - await Self.logout(token.id); + // Create new accessToken const user = await Self.findById(token.userId); const accessToken = await user.createAccessToken(); diff --git a/back/methods/vn-user/specs/privileges.spec.js b/back/methods/vn-user/specs/privileges.spec.js index 3d25eecf9..04d9c09ff 100644 --- a/back/methods/vn-user/specs/privileges.spec.js +++ b/back/methods/vn-user/specs/privileges.spec.js @@ -70,7 +70,7 @@ describe('VnUser privileges()', () => { const tx = await models.VnUser.beginTransaction({}); const options = {transaction: tx}; - const agency = await models.Role.findOne({ + const agency = await models.VnRole.findOne({ where: { name: 'agency' } diff --git a/back/methods/vn-user/specs/renew-token.spec.js b/back/methods/vn-user/specs/renew-token.spec.js new file mode 100644 index 000000000..8d9bbf11c --- /dev/null +++ b/back/methods/vn-user/specs/renew-token.spec.js @@ -0,0 +1,50 @@ +const {models} = require('vn-loopback/server/server'); +describe('Renew Token', () => { + const startingTime = Date.now(); + let ctx = null; + beforeAll(async() => { + const unAuthCtx = { + req: { + headers: {}, + connection: { + remoteAddress: '127.0.0.1' + }, + getLocale: () => 'en' + }, + args: {} + }; + let login = await models.VnUser.signIn(unAuthCtx, 'salesAssistant', 'nightmare'); + let accessToken = await models.AccessToken.findById(login.token); + ctx = {req: {accessToken: accessToken}}; + }); + + beforeEach(() => { + jasmine.clock().install(); + jasmine.clock().mockDate(new Date(startingTime)); + }); + + afterEach(() => { + jasmine.clock().uninstall(); + }); + + it('should renew token', async() => { + const mockDate = new Date(startingTime + 26600000); + jasmine.clock().mockDate(mockDate); + const {id} = await models.VnUser.renewToken(ctx); + + expect(id).not.toEqual(ctx.req.accessToken.id); + }); + + it('NOT should renew', async() => { + let error; + let response; + try { + response = await models.VnUser.renewToken(ctx); + } catch (e) { + error = e; + } + + expect(error).toBeUndefined(); + expect(response.id).toEqual(ctx.req.accessToken.id); + }); +}); diff --git a/back/methods/vn-user/specs/sign-in.spec.js b/back/methods/vn-user/specs/sign-in.spec.js index 1c4b4af51..a14dd301e 100644 --- a/back/methods/vn-user/specs/sign-in.spec.js +++ b/back/methods/vn-user/specs/sign-in.spec.js @@ -20,10 +20,7 @@ describe('VnUser Sign-in()', () => { 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(signInLog[0].owner).toEqual(true); - expect(login.token).toBeDefined(); + expect(signInLog.length).toEqual(0); await VnUser.logout(ctx.req.accessToken.id); }); diff --git a/back/methods/vn-user/validate-token.js b/back/methods/vn-user/validate-token.js deleted file mode 100644 index 7bccfe0b1..000000000 --- a/back/methods/vn-user/validate-token.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = Self => { - Self.remoteMethod('validateToken', { - description: 'Validates the current logged user token', - returns: { - type: 'Boolean', - root: true - }, - http: { - path: `/validateToken`, - verb: 'GET' - } - }); - - Self.validateToken = async function() { - return true; - }; -}; diff --git a/back/model-config.json b/back/model-config.json index ebc0e321b..27a94498c 100644 --- a/back/model-config.json +++ b/back/model-config.json @@ -139,9 +139,6 @@ "Warehouse": { "dataSource": "vn" }, - "VnUser": { - "dataSource": "vn" - }, "OsTicket": { "dataSource": "osticket" }, @@ -156,6 +153,12 @@ }, "ViaexpressConfig": { "dataSource": "vn" + }, + "VnUser": { + "dataSource": "vn" + }, + "VnRole": { + "dataSource": "vn" } } diff --git a/back/models/dms-type.json b/back/models/dms-type.json index de3d564b4..8d7195132 100644 --- a/back/models/dms-type.json +++ b/back/models/dms-type.json @@ -29,12 +29,12 @@ "relations": { "readRole": { "type": "belongsTo", - "model": "Role", + "model": "VnRole", "foreignKey": "readRoleFk" }, "writeRole": { "type": "belongsTo", - "model": "Role", + "model": "VnRole", "foreignKey": "writeRoleFk" } }, diff --git a/back/models/docuwareTablet.json b/back/models/docuwareTablet.json new file mode 100644 index 000000000..dbbf62f56 --- /dev/null +++ b/back/models/docuwareTablet.json @@ -0,0 +1,17 @@ +{ + "name": "docuwareTablet", + "base": "VnModel", + "options": { + "mysql": { + "table": "docuwareTablet" + } + }, + "properties": { + "tablet": { + "type": "string" + }, + "description": { + "type": "string" + } + } +} diff --git a/back/models/image-collection.json b/back/models/image-collection.json index 186ab0208..ae0e0adcd 100644 --- a/back/models/image-collection.json +++ b/back/models/image-collection.json @@ -46,12 +46,12 @@ }, "readRole": { "type": "belongsTo", - "model": "Role", + "model": "VnRole", "foreignKey": "readRoleFk" }, "writeRole": { "type": "belongsTo", - "model": "Role", + "model": "VnRole", "foreignKey": "writeRoleFk" } }, @@ -64,4 +64,3 @@ } ] } - \ No newline at end of file diff --git a/back/models/notificationAcl.json b/back/models/notificationAcl.json index a20187961..9ab85530f 100644 --- a/back/models/notificationAcl.json +++ b/back/models/notificationAcl.json @@ -24,8 +24,8 @@ }, "role": { "type": "belongsTo", - "model": "Role", + "model": "VnRole", "foreignKey": "roleFk" } } -} \ No newline at end of file +} diff --git a/back/models/specs/mailAliasAccount.spec.js b/back/models/specs/mailAliasAccount.spec.js new file mode 100644 index 000000000..c13cc7ae8 --- /dev/null +++ b/back/models/specs/mailAliasAccount.spec.js @@ -0,0 +1,73 @@ +const models = require('vn-loopback/server/server').models; + +describe('loopback model MailAliasAccount', () => { + it('should fail to add a mail Alias if the worker doesnt have ACLs', async() => { + const tx = await models.MailAliasAccount.beginTransaction({}); + let error; + + try { + const options = {transaction: tx, accessToken: {userId: 57}}; + await models.MailAliasAccount.create({mailAlias: 2, account: 5}, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toEqual('The alias cant be modified'); + }); + + it('should add a mail Alias', async() => { + const tx = await models.MailAliasAccount.beginTransaction({}); + let error; + + try { + const options = {transaction: tx, accessToken: {userId: 9}}; + await models.MailAliasAccount.create({mailAlias: 2, account: 5}, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error).toBeUndefined(); + }); + + it('should add a mail Alias of an inherit role', async() => { + const tx = await models.MailAliasAccount.beginTransaction({}); + let error; + + try { + const options = {transaction: tx, accessToken: {userId: 9}}; + await models.MailAliasAccount.create({mailAlias: 3, account: 5}, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error).toBeUndefined(); + }); + + it('should delete a mail Alias', async() => { + const tx = await models.MailAliasAccount.beginTransaction({}); + let error; + + try { + const options = {transaction: tx, accessToken: {userId: 1}}; + const mailAclId = 2; + await models.MailAliasAccount.destroyAll({id: mailAclId}, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error).toBeUndefined(); + }); +}); + diff --git a/back/models/user-config.json b/back/models/user-config.json index 52125dc01..5c5df1b9e 100644 --- a/back/models/user-config.json +++ b/back/models/user-config.json @@ -26,6 +26,9 @@ }, "darkMode": { "type": "boolean" + }, + "tabletFk": { + "type": "string" } }, "relations": { @@ -43,6 +46,11 @@ "type": "belongsTo", "model": "VnUser", "foreignKey": "userFk" - } + }, + "Tablet": { + "type": "belongsTo", + "model": "docuwareTablet", + "foreignKey": "tabletFk" + } } } diff --git a/back/models/vn-role.json b/back/models/vn-role.json new file mode 100644 index 000000000..c7d7e172b --- /dev/null +++ b/back/models/vn-role.json @@ -0,0 +1,13 @@ +{ + "name": "VnRole", + "base": "Role", + "validateUpsert": true, + "options": { + "mysql": { + "table": "account.role" + } + }, + "mixins": { + "Loggable": true + } +} diff --git a/back/models/vn-user.js b/back/models/vn-user.js index e14cd30ea..b1d09f0c0 100644 --- a/back/models/vn-user.js +++ b/back/models/vn-user.js @@ -10,7 +10,6 @@ module.exports = function(Self) { require('../methods/vn-user/sign-in')(Self); require('../methods/vn-user/acl')(Self); require('../methods/vn-user/recover-password')(Self); - require('../methods/vn-user/validate-token')(Self); require('../methods/vn-user/privileges')(Self); require('../methods/vn-user/validate-auth')(Self); require('../methods/vn-user/renew-token')(Self); @@ -135,15 +134,16 @@ module.exports = function(Self) { Self.signInValidate = async(user, userToken, token, ctx) => { const [[key, value]] = Object.entries(Self.userUses(user)); const isOwner = Self.rawSql(`SELECT ? = ? `, [userToken[key], value]); - await Self.app.models.SignInLog.create({ - userName: user, - token: token.id, - userFk: userToken.id, - ip: ctx.req.ip, - owner: isOwner - }); - if (!isOwner) - throw new UserError('Try again'); + if (!isOwner) { + await Self.app.models.SignInLog.create({ + userName: user, + token: token.id, + userFk: userToken.id, + ip: ctx.req.ip, + owner: isOwner + }); + throw new UserError('Try again'); + } }; /** diff --git a/back/models/vn-user.json b/back/models/vn-user.json index 0f6daff5a..639603643 100644 --- a/back/models/vn-user.json +++ b/back/models/vn-user.json @@ -7,6 +7,9 @@ "table": "account.user" } }, + "mixins": { + "Loggable": true + }, "resetPasswordTokenTTL": "604800", "properties": { "id": { @@ -63,7 +66,7 @@ "relations": { "role": { "type": "belongsTo", - "model": "Role", + "model": "VnRole", "foreignKey": "roleFk" }, "roles": { @@ -95,34 +98,30 @@ "principalType": "ROLE", "principalId": "$everyone", "permission": "ALLOW" - }, - { - "property": "recoverPassword", - "accessType": "EXECUTE", - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "ALLOW" - }, - { - "property": "validateToken", - "accessType": "EXECUTE", - "principalType": "ROLE", - "principalId": "$authenticated", - "permission": "ALLOW" - }, - { - "property": "validateAuth", + }, { + "property": "recoverPassword", "accessType": "EXECUTE", "principalType": "ROLE", "principalId": "$everyone", "permission": "ALLOW" - }, - { + }, { + "property": "validateAuth", + "accessType": "EXECUTE", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + }, { "property": "privileges", "accessType": "*", "principalType": "ROLE", "principalId": "$authenticated", "permission": "ALLOW" + }, { + "property": "renewToken", + "accessType": "WRITE", + "principalType": "ROLE", + "principalId": "$authenticated", + "permission": "ALLOW" } ], "scopes": { diff --git a/back/tests.js b/back/tests.js index 97e548d33..efade4d7d 100644 --- a/back/tests.js +++ b/back/tests.js @@ -7,6 +7,10 @@ process.on('warning', warning => { console.log(warning.stack); }); +process.on('SIGUSR2', async() => { + if (container) await container.rm(); +}); + process.on('exit', async function() { if (container) await container.rm(); }); diff --git a/db/changes/234601/00-updateCourtesyTime.sql b/db/changes/234601/00-updateCourtesyTime.sql new file mode 100644 index 000000000..4751b2e03 --- /dev/null +++ b/db/changes/234601/00-updateCourtesyTime.sql @@ -0,0 +1,4 @@ +-- Auto-generated SQL script #202311061003 +UPDATE salix.accessTokenConfig + SET courtesyTime=60 + WHERE id=1; diff --git a/db/changes/235001/00-silexToSalix.sql b/db/changes/235001/00-silexToSalix.sql new file mode 100644 index 000000000..bad430ac2 --- /dev/null +++ b/db/changes/235001/00-silexToSalix.sql @@ -0,0 +1,46 @@ + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`delivery_beforeInsert` + BEFORE INSERT ON `delivery` + FOR EACH ROW +BEGIN + + IF (NEW.longitude IS NOT NULL AND NEW.latitude IS NOT NULL AND NEW.ticketFK IS NOT NULL) + THEN + UPDATE address + SET longitude = NEW.longitude, + latitude = NEW.latitude + WHERE id IN ( + SELECT addressFK + FROM ticket + WHERE id = NEW.ticketFk + ); + END IF; + +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`delivery_beforeUpdate` + BEFORE UPDATE ON `delivery` + FOR EACH ROW +BEGIN + +IF (NEW.longitude IS NOT NULL AND NEW.latitude IS NOT NULL AND NEW.ticketFK IS NOT NULL) + THEN + UPDATE address + SET longitude = NEW.longitude, + latitude = NEW.latitude + WHERE id IN ( + SELECT addressFK + FROM ticket + WHERE id = NEW.ticketFk + ); + END IF; + +END$$ +DELIMITER ; + + +ALTER TABLE `vn`.`address` MODIFY COLUMN longitude decimal(11,7) DEFAULT NULL NULL COMMENT 'Indica la última longitud proporcionada por tabla delivery'; +ALTER TABLE `vn`.`address` MODIFY COLUMN latitude decimal(11,7) DEFAULT NULL NULL COMMENT 'Indica la última latitud proporcionada por tabla delivery'; diff --git a/db/changes/235001/00-updateACL_Role_VnRole.sql b/db/changes/235001/00-updateACL_Role_VnRole.sql new file mode 100644 index 000000000..b08a44138 --- /dev/null +++ b/db/changes/235001/00-updateACL_Role_VnRole.sql @@ -0,0 +1,6 @@ +INSERT INTO salix.ACL (model,property,accessType,permission,principalType,principalId) VALUES + ('VnRole','*','READ','ALLOW','ROLE','employee'), + ('VnRole','*','WRITE','ALLOW','ROLE','it'); + +DELETE FROM`salix`.`ACL` WHERE model='Role'; + diff --git a/db/changes/235201/00-aclsMails.sql b/db/changes/235201/00-aclsMails.sql new file mode 100644 index 000000000..5cfea4030 --- /dev/null +++ b/db/changes/235201/00-aclsMails.sql @@ -0,0 +1,8 @@ +-- Definición de la tabla mailAliasACL + +CREATE OR REPLACE TABLE `account`.`mailAliasAcl` ( + `mailAliasFk` int(10) unsigned NOT NULL, + `roleFk` int(10) unsigned NOT NULL, + FOREIGN KEY (`mailAliasFk`) REFERENCES `account`.`mailAlias` (`id`), + FOREIGN KEY (`roleFk`) REFERENCES `account`.`role` (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; diff --git a/db/changes/240001/00-alterTable.sql b/db/changes/240001/00-alterTable.sql new file mode 100644 index 000000000..b6974b715 --- /dev/null +++ b/db/changes/240001/00-alterTable.sql @@ -0,0 +1 @@ +ALTER TABLE `vn`.`ticketTracking` CHANGE `workerFk` `userFk` int(10) unsigned DEFAULT NULL NULL; \ No newline at end of file diff --git a/db/changes/240001/00-clientCreditLimitToRoleCreditLimit.sql b/db/changes/240001/00-clientCreditLimitToRoleCreditLimit.sql new file mode 100644 index 000000000..2bc0f830d --- /dev/null +++ b/db/changes/240001/00-clientCreditLimitToRoleCreditLimit.sql @@ -0,0 +1,4 @@ +RENAME TABLE `vn`.`clientCreditLimit` TO `vn`.`roleCreditLimit`; +ALTER TABLE `vn`.`roleCreditLimit` DROP FOREIGN KEY `clientCreditLimit_FK`; +ALTER TABLE `vn`.`roleCreditLimit` ADD CONSTRAINT `roleCreditLimit_FK` FOREIGN KEY (`roleFk`) REFERENCES `account`.`role`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + diff --git a/db/changes/240001/00-fixInvoiceCorrectionConstraintsName.sql b/db/changes/240001/00-fixInvoiceCorrectionConstraintsName.sql new file mode 100644 index 000000000..426afea90 --- /dev/null +++ b/db/changes/240001/00-fixInvoiceCorrectionConstraintsName.sql @@ -0,0 +1,7 @@ +ALTER TABLE `vn`.`invoiceCorrection` DROP FOREIGN KEY `cplusInvoiceTyoeFk`; +ALTER TABLE `vn`.`invoiceCorrection` DROP FOREIGN KEY `invoiceCorrectionType_Fk33`; +ALTER TABLE `vn`.`invoiceCorrection` DROP FOREIGN KEY `invoiceCorrection_ibfk_1`; + +ALTER TABLE `vn`.`invoiceCorrection` ADD CONSTRAINT `siiTypeInvoiceOut_FK` FOREIGN KEY (`siiTypeInvoiceOutFk`) REFERENCES `vn`.`siiTypeInvoiceOut`(id) ON UPDATE CASCADE; +ALTER TABLE `vn`.`invoiceCorrection` ADD CONSTRAINT `invoiceCorrectionType_FK` FOREIGN KEY (`invoiceCorrectionTypeFk`) REFERENCES `vn`.`invoiceCorrectionType`(id) ON UPDATE CASCADE; +ALTER TABLE `vn`.`invoiceCorrection` ADD CONSTRAINT `cplusRectificationType_FK` FOREIGN KEY (`cplusRectificationTypeFk`) REFERENCES `vn`.`cplusRectificationType`(id) ON UPDATE CASCADE; diff --git a/db/changes/240001/00-getTaxBases.sql b/db/changes/240001/00-getTaxBases.sql new file mode 100644 index 000000000..8bd1b745a --- /dev/null +++ b/db/changes/240001/00-getTaxBases.sql @@ -0,0 +1,33 @@ +DELIMITER $$ +$$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`getTaxBases`() +BEGIN +/** +* Calcula y devuelve en número de bases imponibles postivas y negativas +* Requiere la tabla temporal tmp.ticketToInvoice(id) +* +* returns tmp.taxBases +*/ + + CREATE OR REPLACE TEMPORARY TABLE tmp.ticket + (KEY (ticketFk)) + ENGINE = MEMORY + SELECT id ticketFk + FROM tmp.ticketToInvoice; + + CALL ticket_getTax(NULL); + + DROP TEMPORARY TABLE IF EXISTS tmp.taxBases; + CREATE TEMPORARY TABLE tmp.taxBases + ENGINE = MEMORY + SELECT + SUM(taxableBase > 0) as positive, + SUM(taxableBase < 0) as negative + FROM( + SELECT SUM(taxableBase) taxableBase + FROM tmp.ticketTax + GROUP BY pgcFk + ) t; + +END$$ +DELIMITER ; diff --git a/db/changes/240001/00-truncate-where-signInLog.sql b/db/changes/240001/00-truncate-where-signInLog.sql new file mode 100644 index 000000000..93d80d716 --- /dev/null +++ b/db/changes/240001/00-truncate-where-signInLog.sql @@ -0,0 +1 @@ +DELETE FROM `account`.`signInLog` where owner <> FALSE diff --git a/db/changes/240001/01-newHasAnyPositiveBase.sql b/db/changes/240001/01-newHasAnyPositiveBase.sql new file mode 100644 index 000000000..c4edfaed0 --- /dev/null +++ b/db/changes/240001/01-newHasAnyPositiveBase.sql @@ -0,0 +1,30 @@ +DELIMITER $$ +$$ +CREATE OR REPLACE DEFINER=`root`@`localhost` FUNCTION `vn`.`hasAnyPositiveBase`() RETURNS tinyint(1) + DETERMINISTIC +BEGIN + +/** +* Calcula si existe alguna base imponible positiva +* Requiere la tabla temporal tmp.ticketToInvoice(id) para getTaxBases() +* +* returns BOOLEAN +*/ + + DECLARE hasAnyPositiveBase BOOLEAN; + + CALL getTaxBases(); + + SELECT positive INTO hasAnyPositiveBase + FROM tmp.taxBases + LIMIT 1; + + DROP TEMPORARY TABLE + tmp.ticketTax, + tmp.ticket, + tmp.taxBases; + + RETURN hasAnyPositiveBase; + +END$$ +DELIMITER ; diff --git a/db/changes/240001/01-procedures.sql b/db/changes/240001/01-procedures.sql new file mode 100644 index 000000000..3777708d5 --- /dev/null +++ b/db/changes/240001/01-procedures.sql @@ -0,0 +1,1129 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `hedera`.`order_confirmWithUser`(vSelf INT, vUserId INT) +BEGIN +/** + * Confirms an order, creating each of its tickets on the corresponding + * date, store and user. + * + * @param vSelf The order identifier + * @param vUser The user identifier + */ + DECLARE vOk BOOL; + DECLARE vDone BOOL DEFAULT FALSE; + DECLARE vWarehouse INT; + DECLARE vShipment DATE; + DECLARE vTicket INT; + DECLARE vNotes VARCHAR(255); + DECLARE vItem INT; + DECLARE vConcept VARCHAR(30); + DECLARE vAmount INT; + DECLARE vPrice DECIMAL(10,2); + DECLARE vSale INT; + DECLARE vRate INT; + DECLARE vRowId INT; + DECLARE vPriceFixed DECIMAL(10,2); + DECLARE vDelivery DATE; + DECLARE vAddress INT; + DECLARE vIsConfirmed BOOL; + DECLARE vClientId INT; + DECLARE vCompanyId INT; + DECLARE vAgencyModeId INT; + DECLARE TICKET_FREE INT DEFAULT 2; + DECLARE vCalc INT; + DECLARE vIsLogifloraItem BOOL; + DECLARE vOldQuantity INT; + DECLARE vNewQuantity INT; + DECLARE vIsTaxDataChecked BOOL; + + DECLARE cDates CURSOR FOR + SELECT zgs.shipped, r.warehouse_id + FROM `order` o + JOIN order_row r ON r.order_id = o.id + LEFT JOIN tmp.zoneGetShipped zgs ON zgs.warehouseFk = r.warehouse_id + WHERE o.id = vSelf AND r.amount != 0 + GROUP BY r.warehouse_id; + + DECLARE cRows CURSOR FOR + SELECT r.id, r.item_id, i.name, r.amount, r.price, r.rate, i.isFloramondo + FROM order_row r + JOIN vn.item i ON i.id = r.item_id + WHERE r.amount != 0 + AND r.warehouse_id = vWarehouse + AND r.order_id = vSelf + ORDER BY r.rate DESC; + + DECLARE CONTINUE HANDLER FOR NOT FOUND + SET vDone = TRUE; + + DECLARE EXIT HANDLER FOR SQLEXCEPTION + BEGIN + ROLLBACK; + RESIGNAL; + END; + + -- Carga los datos del pedido + SELECT o.date_send, o.address_id, o.note, a.clientFk, + o.company_id, o.agency_id, c.isTaxDataChecked + INTO vDelivery, vAddress, vNotes, vClientId, + vCompanyId, vAgencyModeId, vIsTaxDataChecked + FROM hedera.`order` o + JOIN vn.address a ON a.id = o.address_id + JOIN vn.client c ON c.id = a.clientFk + WHERE o.id = vSelf; + + -- Verifica si el cliente tiene los datos comprobados + IF NOT vIsTaxDataChecked THEN + CALL util.throw ('clientNotVerified'); + END IF; + + -- Carga las fechas de salida de cada almacen + CALL vn.zone_getShipped (vDelivery, vAddress, vAgencyModeId, FALSE); + + -- Trabajador que realiza la accion + IF vUserId IS NULL THEN + SELECT employeeFk INTO vUserId FROM orderConfig; + END IF; + + START TRANSACTION; + + CALL order_checkEditable(vSelf); + + -- Check order is not empty + + SELECT COUNT(*) > 0 INTO vOk + FROM order_row WHERE order_id = vSelf AND amount > 0; + + IF NOT vOk THEN + CALL util.throw ('ORDER_EMPTY'); + END IF; + + -- Crea los tickets del pedido + + OPEN cDates; + + lDates: + LOOP + SET vTicket = NULL; + SET vDone = FALSE; + FETCH cDates INTO vShipment, vWarehouse; + + IF vDone THEN + LEAVE lDates; + END IF; + + -- Busca un ticket existente que coincida con los parametros + WITH tPrevia AS + (SELECT DISTINCT s.ticketFk + FROM vn.sale s + JOIN vn.saleGroupDetail sgd ON sgd.saleFk = s.id + JOIN vn.ticket t ON t.id = s.ticketFk + WHERE t.shipped BETWEEN vShipment AND util.dayend(vShipment) + ) + SELECT t.id INTO vTicket + FROM vn.ticket t + LEFT JOIN tPrevia tp ON tp.ticketFk = t.id + LEFT JOIN vn.ticketState tls on tls.ticket = t.id + JOIN hedera.`order` o + ON o.address_id = t.addressFk + AND vWarehouse = t.warehouseFk + AND o.date_send = t.landed + AND DATE(t.shipped) = vShipment + WHERE o.id = vSelf + AND t.refFk IS NULL + AND tp.ticketFk IS NULL + AND IFNULL(tls.alertLevel,0) = 0 + LIMIT 1; + + -- Crea el ticket en el caso de no existir uno adecuado + IF vTicket IS NULL + THEN + + SET vShipment = IFNULL(vShipment, util.VN_CURDATE()); + + CALL vn.ticket_add( + vClientId, + vShipment, + vWarehouse, + vCompanyId, + vAddress, + vAgencyModeId, + NULL, + vDelivery, + vUserId, + TRUE, + vTicket + ); + ELSE + INSERT INTO vn.ticketTracking + SET ticketFk = vTicket, + userFk = vUserId, + stateFk = TICKET_FREE; + END IF; + + INSERT IGNORE INTO vn.orderTicket + SET orderFk = vSelf, + ticketFk = vTicket; + + -- Añade las notas + + IF vNotes IS NOT NULL AND vNotes != '' + THEN + INSERT INTO vn.ticketObservation SET + ticketFk = vTicket, + observationTypeFk = 4 /* salesperson */, + `description` = vNotes + ON DUPLICATE KEY UPDATE + `description` = CONCAT(VALUES(`description`),'. ', `description`); + END IF; + + -- Añade los movimientos y sus componentes + + OPEN cRows; + + lRows: LOOP + SET vDone = FALSE; + FETCH cRows INTO vRowId, vItem, vConcept, vAmount, vPrice, vRate, vIsLogifloraItem; + + IF vDone THEN + LEAVE lRows; + END IF; + + SET vSale = NULL; + + SELECT s.id, s.quantity INTO vSale, vOldQuantity + FROM vn.sale s + WHERE ticketFk = vTicket + AND price = vPrice + AND itemFk = vItem + AND discount = 0 + LIMIT 1; + + IF vSale THEN + UPDATE vn.sale + SET quantity = quantity + vAmount, + originalQuantity = quantity + WHERE id = vSale; + + SELECT s.quantity INTO vNewQuantity + FROM vn.sale s + WHERE id = vSale; + ELSE + -- Obtiene el coste + SELECT SUM(rc.`price`) valueSum INTO vPriceFixed + FROM orderRowComponent rc + JOIN vn.component c ON c.id = rc.componentFk + JOIN vn.componentType ct ON ct.id = c.typeFk AND ct.isBase + WHERE rc.rowFk = vRowId; + + INSERT INTO vn.sale + SET itemFk = vItem, + ticketFk = vTicket, + concept = vConcept, + quantity = vAmount, + price = vPrice, + priceFixed = vPriceFixed, + isPriceFixed = TRUE; + + SET vSale = LAST_INSERT_ID(); + + INSERT INTO vn.saleComponent + (saleFk, componentFk, `value`) + SELECT vSale, rc.componentFk, rc.price + FROM orderRowComponent rc + JOIN vn.component c ON c.id = rc.componentFk + WHERE rc.rowFk = vRowId + GROUP BY vSale, rc.componentFk; + END IF; + + UPDATE order_row SET Id_Movimiento = vSale + WHERE id = vRowId; + + -- Inserta en putOrder si la compra es de Floramondo + IF vIsLogifloraItem THEN + CALL cache.availableNoRaids_refresh(vCalc,FALSE,vWarehouse,vShipment); + + SET @available := 0; + + SELECT GREATEST(0,available) INTO @available + FROM cache.availableNoRaids + WHERE calc_id = vCalc + AND item_id = vItem; + + UPDATE cache.availableNoRaids + SET available = GREATEST(0,available - vAmount) + WHERE item_id = vItem + AND calc_id = vCalc; + + INSERT INTO edi.putOrder ( + deliveryInformationID, + supplyResponseId, + quantity , + EndUserPartyId, + EndUserPartyGLN, + FHAdminNumber, + saleFk + ) + SELECT di.ID, + i.supplyResponseFk, + CEIL((vAmount - @available)/ sr.NumberOfItemsPerCask), + o.address_id , + vClientId, + IFNULL(ca.fhAdminNumber, fhc.defaultAdminNumber), + vSale + FROM edi.deliveryInformation di + JOIN vn.item i ON i.supplyResponseFk = di.supplyResponseID + JOIN edi.supplyResponse sr ON sr.ID = i.supplyResponseFk + LEFT JOIN edi.clientFHAdminNumber ca ON ca.clientFk = vClientId + JOIN edi.floraHollandConfig fhc + JOIN hedera.`order` o ON o.id = vSelf + WHERE i.id = vItem + AND di.LatestOrderDateTime > util.VN_NOW() + AND vAmount > @available + LIMIT 1; + END IF; + END LOOP; + + CLOSE cRows; + END LOOP; + + CLOSE cDates; + + UPDATE `order` SET confirmed = TRUE, confirm_date = util.VN_NOW() + WHERE id = vSelf; + + COMMIT; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`invoiceOut_new`( + vSerial VARCHAR(255), + vInvoiceDate DATE, + vTaxArea VARCHAR(25), + OUT vNewInvoiceId INT) +BEGIN +/** + * Creación de facturas emitidas. + * requiere previamente tabla tmp.ticketToInvoice(id). + * + * @param vSerial serie a la cual se hace la factura + * @param vInvoiceDate fecha de la factura + * @param vTaxArea tipo de iva en relacion a la empresa y al cliente + * @param vNewInvoiceId id de la factura que se acaba de generar + * @return vNewInvoiceId + */ + DECLARE vIsAnySaleToInvoice BOOL; + DECLARE vIsAnyServiceToInvoice BOOL; + DECLARE vNewRef VARCHAR(255); + DECLARE vWorker INT DEFAULT account.myUser_getId(); + DECLARE vCompanyFk INT; + DECLARE vInterCompanyFk INT; + DECLARE vClientFk INT; + DECLARE vCplusStandardInvoiceTypeFk INT DEFAULT 1; + DECLARE vCplusCorrectingInvoiceTypeFk INT DEFAULT 6; + DECLARE vCplusSimplifiedInvoiceTypeFk INT DEFAULT 2; + DECLARE vCorrectingSerial VARCHAR(1) DEFAULT 'R'; + DECLARE vSimplifiedSerial VARCHAR(1) DEFAULT 'S'; + DECLARE vNewInvoiceInFk INT; + DECLARE vIsInterCompany BOOL DEFAULT FALSE; + DECLARE vIsCEESerial BOOL DEFAULT FALSE; + DECLARE vIsCorrectInvoiceDate BOOL; + DECLARE vMaxShipped DATE; + DECLARE vDone BOOL; + DECLARE vTicketFk INT; + DECLARE vCursor CURSOR FOR + SELECT id + FROM tmp.ticketToInvoice; + + DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE; + + SET vInvoiceDate = IFNULL(vInvoiceDate, util.VN_CURDATE()); + + SELECT t.clientFk, + t.companyFk, + MAX(DATE(t.shipped)), + DATE(vInvoiceDate) >= invoiceOut_getMaxIssued( + vSerial, + t.companyFk, + YEAR(vInvoiceDate)) + INTO vClientFk, + vCompanyFk, + vMaxShipped, + vIsCorrectInvoiceDate + FROM tmp.ticketToInvoice tt + JOIN ticket t ON t.id = tt.id; + + IF(vMaxShipped > vInvoiceDate) THEN + CALL util.throw("Invoice date can't be less than max date"); + END IF; + + IF NOT vIsCorrectInvoiceDate THEN + CALL util.throw('Exists an invoice with a previous date'); + END IF; + + -- Eliminem de tmp.ticketToInvoice els tickets que no han de ser facturats + DELETE ti.* + FROM tmp.ticketToInvoice ti + JOIN ticket t ON t.id = ti.id + JOIN sale s ON s.ticketFk = t.id + JOIN item i ON i.id = s.itemFk + JOIN supplier su ON su.id = t.companyFk + JOIN client c ON c.id = t.clientFk + LEFT JOIN itemTaxCountry itc ON itc.itemFk = i.id AND itc.countryFk = su.countryFk + WHERE (YEAR(t.shipped) < 2001 AND t.isDeleted) + OR c.isTaxDataChecked = FALSE + OR t.isDeleted + OR c.hasToInvoice = FALSE + OR itc.id IS NULL; + + SELECT SUM(s.quantity * s.price * (100 - s.discount)/100) <> 0 + INTO vIsAnySaleToInvoice + FROM tmp.ticketToInvoice t + JOIN sale s ON s.ticketFk = t.id; + + SELECT COUNT(*) > 0 INTO vIsAnyServiceToInvoice + FROM tmp.ticketToInvoice t + JOIN ticketService ts ON ts.ticketFk = t.id; + + IF (vIsAnySaleToInvoice OR vIsAnyServiceToInvoice) + AND (vCorrectingSerial = vSerial OR NOT hasAnyNegativeBase()) + THEN + + -- el trigger añade el siguiente Id_Factura correspondiente a la vSerial + INSERT INTO invoiceOut( + ref, + serial, + issued, + clientFk, + dued, + companyFk, + siiTypeInvoiceOutFk + ) + SELECT + 1, + vSerial, + vInvoiceDate, + vClientFk, + getDueDate(vInvoiceDate, dueDay), + vCompanyFk, + IF(vSerial = vCorrectingSerial, + vCplusCorrectingInvoiceTypeFk, + IF(vSerial = vSimplifiedSerial, + vCplusSimplifiedInvoiceTypeFk, + vCplusStandardInvoiceTypeFk)) + FROM client + WHERE id = vClientFk; + + SET vNewInvoiceId = LAST_INSERT_ID(); + + SELECT `ref` + INTO vNewRef + FROM invoiceOut + WHERE id = vNewInvoiceId; + + OPEN vCursor; + l: LOOP + SET vDone = FALSE; + FETCH vCursor INTO vTicketFk; + + IF vDone THEN + LEAVE l; + END IF; + + CALL ticket_recalc(vTicketFk, vTaxArea); + + END LOOP; + CLOSE vCursor; + + UPDATE ticket t + JOIN tmp.ticketToInvoice ti ON ti.id = t.id + SET t.refFk = vNewRef; + + DROP TEMPORARY TABLE IF EXISTS tmp.updateInter; + CREATE TEMPORARY TABLE tmp.updateInter ENGINE = MEMORY + SELECT s.id, ti.id ticket_id, vWorker Id_Trabajador + FROM tmp.ticketToInvoice ti + LEFT JOIN ticketState ts ON ti.id = ts.ticket + JOIN state s + WHERE IFNULL(ts.alertLevel, 0) < 3 and s.`code` = getAlert3State(ti.id); + + INSERT INTO ticketTracking(stateFk, ticketFk, userFk) + SELECT * FROM tmp.updateInter; + + CALL invoiceExpenseMake(vNewInvoiceId); + CALL invoiceTaxMake(vNewInvoiceId, vTaxArea); + + UPDATE invoiceOut io + JOIN ( + SELECT SUM(amount) total + FROM invoiceOutExpense + WHERE invoiceOutFk = vNewInvoiceId + ) base + JOIN ( + SELECT SUM(vat) total + FROM invoiceOutTax + WHERE invoiceOutFk = vNewInvoiceId + ) vat + SET io.amount = base.total + vat.total + WHERE io.id = vNewInvoiceId; + + DROP TEMPORARY TABLE tmp.updateInter; + + SELECT COUNT(*), id + INTO vIsInterCompany, vInterCompanyFk + FROM company + WHERE clientFk = vClientFk; + + IF (vIsInterCompany) THEN + + INSERT INTO invoiceIn(supplierFk, supplierRef, issued, companyFk) + SELECT vCompanyFk, vNewRef, vInvoiceDate, vInterCompanyFk; + + SET vNewInvoiceInFk = LAST_INSERT_ID(); + + DROP TEMPORARY TABLE IF EXISTS tmp.ticket; + CREATE TEMPORARY TABLE tmp.ticket + (KEY (ticketFk)) + ENGINE = MEMORY + SELECT id ticketFk + FROM tmp.ticketToInvoice; + + CALL `ticket_getTax`('NATIONAL'); + + SET @vTaxableBaseServices := 0.00; + SET @vTaxCodeGeneral := NULL; + + INSERT INTO invoiceInTax(invoiceInFk, taxableBase, expenseFk, taxTypeSageFk, transactionTypeSageFk) + SELECT vNewInvoiceInFk, + @vTaxableBaseServices, + sub.expenseFk, + sub.taxTypeSageFk, + sub.transactionTypeSageFk + FROM ( + SELECT @vTaxableBaseServices := SUM(tst.taxableBase) taxableBase, + i.expenseFk, + i.taxTypeSageFk, + i.transactionTypeSageFk, + @vTaxCodeGeneral := i.taxClassCodeFk + FROM tmp.ticketServiceTax tst + JOIN invoiceOutTaxConfig i ON i.taxClassCodeFk = tst.code + WHERE i.isService + HAVING taxableBase + ) sub; + + INSERT INTO invoiceInTax(invoiceInFk, taxableBase, expenseFk, taxTypeSageFk, transactionTypeSageFk) + SELECT vNewInvoiceInFk, + SUM(tt.taxableBase) - IF(tt.code = @vTaxCodeGeneral, + @vTaxableBaseServices, 0) taxableBase, + i.expenseFk, + i.taxTypeSageFk , + i.transactionTypeSageFk + FROM tmp.ticketTax tt + JOIN invoiceOutTaxConfig i ON i.taxClassCodeFk = tt.code + WHERE !i.isService + GROUP BY tt.pgcFk + HAVING taxableBase + ORDER BY tt.priority; + + CALL invoiceInDueDay_calculate(vNewInvoiceInFk); + + SELECT COUNT(*) INTO vIsCEESerial + FROM invoiceOutSerial + WHERE code = vSerial; + + IF vIsCEESerial THEN + + INSERT INTO invoiceInIntrastat ( + invoiceInFk, + intrastatFk, + amount, + stems, + countryFk, + net) + SELECT + vNewInvoiceInFk, + i.intrastatFk, + SUM(CAST((s.quantity * s.price * (100 - s.discount) / 100 ) AS DECIMAL(10, 2))), + SUM(CAST(IFNULL(i.stems, 1) * s.quantity AS DECIMAL(10, 2))), + su.countryFk, + CAST(SUM(IFNULL(i.stems, 1) + * s.quantity + * IF(ic.grams, ic.grams, IFNULL(i.weightByPiece, 0)) / 1000) AS DECIMAL(10, 2)) + FROM sale s + JOIN ticket t ON s.ticketFk = t.id + JOIN supplier su ON su.id = t.companyFk + JOIN item i ON i.id = s.itemFk + LEFT JOIN itemCost ic ON ic.itemFk = i.id AND ic.warehouseFk = t.warehouseFk + WHERE t.refFk = vNewRef + GROUP BY i.intrastatFk; + + END IF; + DROP TEMPORARY TABLE tmp.ticket; + DROP TEMPORARY TABLE tmp.ticketAmount; + DROP TEMPORARY TABLE tmp.ticketTax; + DROP TEMPORARY TABLE tmp.ticketServiceTax; + END IF; + END IF; + DROP TEMPORARY TABLE `tmp`.`ticketToInvoice`; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`productionError_add`() +BEGIN + DECLARE vDatedFrom DATETIME; + DECLARE vDatedTo DATETIME; +/** + * Rellena la tabla vn.productionError con estadisticas de encajadores, revisores y sacadores. Se ejecuta en el nightTask + */ + SELECT util.VN_CURDATE() - INTERVAL 1 DAY, util.dayend(util.VN_CURDATE() - INTERVAL 1 DAY) INTO vDatedFrom, vDatedTo; + CALL timeControl_calculateAll(vDatedFrom, vDatedTo); + + -- Rellena la tabla tmp.errorsByClaim con encajadores, revisores y sacadores + CREATE OR REPLACE TEMPORARY TABLE tmp.errorsByClaim + ENGINE = MEMORY + SELECT COUNT(c.ticketFk) errors, + cd.workerFk + FROM claimDevelopment cd + JOIN claim c ON cd.claimFk = c.id + JOIN ticket t ON c.ticketFk = t.id + JOIN claimResponsible cr ON cd.claimResponsibleFk = cr.id + WHERE t.shipped BETWEEN vDatedFrom AND vDatedTo + AND cr.code IN ('pic', 'chk', 'pck') + GROUP BY cd.workerFk; + + -- Genera la tabla tmp.volume con encajadores, sacadores y revisores + CREATE OR REPLACE TEMPORARY TABLE tmp.volume + ENGINE = MEMORY + SELECT SUM(w.volume) volume, + w.workerFk + FROM bs.workerProductivity w + WHERE w.dated BETWEEN vDatedFrom AND vDatedTo + GROUP BY w.workerFk; + + -- Rellena la tabla tmp.errorsByChecker con fallos de revisores + CREATE OR REPLACE TEMPORARY TABLE tmp.errorsByChecker + ENGINE = MEMORY + SELECT st.workerFk, + COUNT(t.id) errors + FROM saleMistake sm + JOIN saleTracking st ON sm.saleFk = st.saleFk + JOIN `state` s2 ON s2.id = st.stateFk + JOIN sale s ON s.id = sm.saleFk + JOIN ticket t on t.id = s.ticketFk + WHERE (t.shipped BETWEEN vDatedFrom AND vDatedTo) + AND s2.code IN ('OK','PREVIOUS_PREPARATION','PREPARED','CHECKED') + GROUP BY st.workerFk; + + -- Rellena la tabla tmp.expeditionErrors con fallos de expediciones + CREATE OR REPLACE TEMPORARY TABLE tmp.expeditionErrors + ENGINE = MEMORY + SELECT COUNT(t.id) errors, + e.workerFk + FROM vn.expeditionMistake pm + JOIN vn.expedition e ON e.id = pm.expeditionFk + JOIN vn.ticket t ON t.id = e.ticketFk + WHERE t.shipped BETWEEN vDatedFrom AND vDatedTo + GROUP BY e.workerFk; + + -- Genera la tabla tmp.total para sacadores y revisores + CREATE OR REPLACE TEMPORARY TABLE tmp.total + ENGINE = MEMORY + SELECT st.workerFk, + COUNT(DISTINCT t.id) ticketCount, + COUNT(s.id) lineCount + FROM saleTracking st + JOIN `state` s2 ON s2.id = st.stateFk + JOIN sale s ON s.id = st.saleFk + JOIN ticket t ON s.ticketFk = t.id + WHERE (t.shipped BETWEEN vDatedFrom AND vDatedTo) + AND s2.code IN ('OK','PREVIOUS_PREPARATION','PREPARED','CHECKED') + GROUP BY st.workerFk; + + -- Rellena la tabla vn.productionError con sacadores + INSERT INTO productionError(userFk, + firstname, + lastname, + rol, + ticketNumber, + lineNumber, + error, + volume, + hourStart, + hourEnd, + hourWorked, + dated) + SELECT w.id, + w.firstName, + w.lastName, + "Sacadores", + t.ticketCount totalTickets, + t.lineCount, + IFNULL(ec.errors,0) + IFNULL(ec2.errors,0) errors, + v.volume volume, + SUBSTRING(tc.tableTimed, 1, 5) hourStart, + SUBSTRING(tc.tableTimed, LENGTH(tc.tableTimed)-4, 5) hourEnd, + IFNULL(CAST(tc.timeWorkDecimal AS DECIMAL (10,2)) , 0) hourWorked, + vDatedFrom dated + FROM tmp.total t + LEFT JOIN worker w ON w.id = t.workerFk + LEFT JOIN tmp.timeControlCalculate tc ON tc.userFk = t.workerFk + LEFT JOIN tmp.errorsByClaim ec ON ec.workerFk = t.workerFk + LEFT JOIN tmp.volume v ON v.workerFk = t.workerFk + LEFT JOIN tmp.errorsByChecker ec2 ON ec2.workerFk = t.workerFk + JOIN (SELECT DISTINCT w.id -- Verificamos que son sacadores + FROM vn.collection c + JOIN vn.state s ON s.id = c.stateFk + JOIN vn.train tn ON tn.id = c.trainFk + JOIN vn.worker w ON w.id = c.workerFk + WHERE c.created BETWEEN vDatedFrom AND vDatedTo) sub ON sub.id = w.id + GROUP BY w.id; + + CREATE OR REPLACE TEMPORARY TABLE itemPickerErrors -- Errores de los sacadores, derivadores de los revisadores + ENGINE = MEMORY + SELECT COUNT(c.ticketFk) errors, + tt.userFk + FROM claimDevelopment cd + JOIN claim c ON cd.claimFk = c.id + JOIN ticket t ON c.ticketFk = t.id + JOIN claimResponsible cr ON cd.claimResponsibleFk = cr.id + JOIN ticketTracking tt ON tt.ticketFk = t.id + JOIN `state` s ON s.id = tt.stateFk + WHERE t.shipped BETWEEN vDatedFrom AND vDatedTo + AND cr.code = 'chk' + AND s.code = 'ON_PREPARATION' + GROUP BY tt.userFk; + + UPDATE productionError ep + JOIN itemPickerErrors ipe ON ipe.workerFk = ep.userFk + SET ep.error = ep.error + ipe.errors + WHERE vDatedFrom = ep.dated AND ep.rol = 'Sacadores'; + + DROP TEMPORARY TABLE itemPickerErrors; + + -- Rellena la tabla vn.productionError con revisores + CALL productionError_addCheckerPackager(vDatedFrom, vDatedTo, "Revisadores"); + + -- Genera la tabla tmp.total para encajadores + CREATE OR REPLACE TEMPORARY TABLE tmp.total + ENGINE = MEMORY + SELECT e.workerFk, + COUNT(DISTINCT t.id) ticketCount, + COUNT(s.id) lineCount + FROM expedition e + JOIN ticket t ON e.ticketFk = t.id + JOIN sale s ON s.ticketFk = t.id + WHERE t.shipped BETWEEN vDatedFrom AND vDatedTo + GROUP BY e.workerFk; + + -- Rellena la tabla vn.productionError con encajadores + CALL productionError_addCheckerPackager(vDatedFrom, vDatedTo, "Encajadores"); + + DROP TEMPORARY TABLE tmp.errorsByClaim, + tmp.volume, + tmp.errorsByChecker, + tmp.expeditionErrors, + tmp.total; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`sectorProductivity_add`() +BEGIN + DECLARE vDatedFrom DATETIME; + DECLARE vDatedTo DATETIME; + + SELECT DATE_SUB(util.VN_CURDATE(),INTERVAL 1 DAY), CONCAT(DATE_SUB(util.VN_CURDATE(),INTERVAL 1 DAY),' 23:59:59') INTO vDatedFrom, vDatedTo; + + DROP TEMPORARY TABLE IF EXISTS tmp.timeControlCalculate; + DROP TEMPORARY TABLE IF EXISTS tmp.errorsByChecker; + DROP TEMPORARY TABLE IF EXISTS tmp.previousErrors; + + CALL timeControl_calculateAll(vDatedFrom, vDatedTo); + + CREATE TEMPORARY TABLE tmp.errorsByChecker + ENGINE = MEMORY + SELECT sc.userFk workerFk, COUNT(DISTINCT s.ticketFk) errorsByChecker + FROM saleMistake sm + JOIN vn.saleGroupDetail sgd on sgd.saleFk = sm.saleFk + JOIN vn.sectorCollectionSaleGroup scsg on scsg.saleGroupFk = sgd.saleGroupFk + JOIN vn.sectorCollection sc on sc.id = scsg.sectorCollectionFk + JOIN sale s ON s.id = sm.saleFk + JOIN ticket t on t.id = s.ticketFk + WHERE (t.shipped BETWEEN vDatedFrom AND vDatedTo) + GROUP BY sc.userFk ; + + CREATE TEMPORARY TABLE tmp.previousErrors -- Errores de previa, derivadores de los revisadores (por reclamación) + ENGINE = MEMORY + SELECT tt.userFk, COUNT(c.ticketFk) errorsByClaim + FROM claimDevelopment cd + JOIN claim c ON cd.claimFk = c.id + JOIN ticket t ON c.ticketFk = t.id + JOIN claimResponsible cr ON cd.claimResponsibleFk = cr.id + JOIN ticketTracking tt ON tt.ticketFk = t.id + JOIN `state` s ON s.id = tt.stateFk + WHERE t.shipped BETWEEN vDatedFrom AND vDatedTo AND cr.description = 'Revisadores' AND s.code = 'OK PREVIOUS' + GROUP BY cd.workerFk; + + DELETE FROM sectorProductivity + WHERE dated = vDatedFrom + AND sector IN ('Algemesi Artificial','Algemesi Complementos'); + + INSERT INTO sectorProductivity(workerFk, firstName, lastName, sector, ticketCount, saleCount, error, volume, hourWorked, dated) + SELECT w.id workerFk, + w.firstName, + w.lastName, + se.description sector, + COUNT(DISTINCT s.ticketFk) ticketCount, + COUNT(sgd.id) saleCount, + IFNULL(ec2.errorsByChecker,0) + IFNULL(pe.errorsByClaim, 0) errors, + wp.volume, + IFNULL(CAST(tc.timeWorkDecimal AS DECIMAL (10,2)) , 0) AS hourWorked, + DATE(vDatedFrom) dated + FROM vn.saleGroupDetail sgd + JOIN vn.saleGroup sg on sg.id = sgd.saleGroupFk + JOIN vn.sectorCollectionSaleGroup scsg on scsg.saleGroupFk = sgd.saleGroupFk + JOIN vn.sectorCollection sc on sc.id = scsg.sectorCollectionFk + join vn.sector se on se.id = sc.sectorFk + JOIN vn.worker w ON w.id = sc.userFk + LEFT JOIN vn.sale s ON s.id = sgd.saleFk + LEFT JOIN tmp.timeControlCalculate tc ON tc.userFk = w.id + LEFT JOIN bs.workerProductivity wp ON wp.workerFk = w.id + LEFT JOIN `state` s2 ON s2.id = wp.stateFk AND s2.code = 'OK PREVIOUS' + LEFT JOIN tmp.errorsByChecker ec2 ON ec2.workerFk = w.id + LEFT JOIN tmp.previousErrors pe ON pe.workerFk = w.id + WHERE DATE(sc.created) = vDatedFrom + AND wp.dated = vDatedFrom + GROUP BY w.id; + + DROP TEMPORARY TABLE tmp.timeControlCalculate; + DROP TEMPORARY TABLE tmp.errorsByChecker; + DROP TEMPORARY TABLE tmp.previousErrors; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticketStateUpdate`(vTicketFk INT, vStateCode VARCHAR(45)) +BEGIN + + /* + * @deprecated:utilizar ticket_setState + */ + + DECLARE vAlertLevel INT; + + SELECT s.alertLevel INTO vAlertLevel + FROM vn.state s + JOIN vn.ticketState ts ON ts.stateFk = s.id + WHERE ts.ticketFk = vTicketFk; + + IF !(vStateCode = 'ON_CHECKING' AND vAlertLevel > 1) THEN + + INSERT INTO ticketTracking(stateFk, ticketFk, userFk) + SELECT id, vTicketFk, account.myUser_getId() + FROM vn.state + WHERE `code` = vStateCode collate utf8_unicode_ci; + + END IF; + +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_Clone`(vOriginalTicket INT, OUT vNewTicket INT) +BEGIN +/** + * Clona el contenido de un ticket en otro + * + * @param vOriginalTicket ticket Original + * @param vNewTicket ticket creado + */ + DECLARE vStateFk INT; + + INSERT INTO ticket ( + clientFk, + shipped, + addressFk, + agencyModeFk, + nickname, + warehouseFk, + companyFk, + landed, + zoneFk, + zonePrice, + zoneBonus, + routeFk, + priority, + hasPriority, + clonedFrom + ) + SELECT + clientFk, + shipped, + addressFk, + agencyModeFk, + nickname, + warehouseFk, + companyFk, + landed, + zoneFk, + zonePrice, + zoneBonus, + routeFk, + priority, + hasPriority, + vOriginalTicket + FROM ticket + WHERE id = vOriginalTicket; + + SET vNewTicket = LAST_INSERT_ID(); + + INSERT INTO ticketObservation(ticketFk, observationTypeFk, description) + SELECT vNewTicket, observationTypeFk, description + FROM ticketObservation + WHERE ticketFk = vOriginalTicket; + + INSERT INTO ticketTracking(ticketFk, stateFk, userFk, created) + SELECT vNewTicket, stateFk, userFk, created + FROM ticketTracking + WHERE ticketFk = vOriginalTicket + ORDER BY created; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_add`( + vClientId INT + ,vShipped DATE + ,vWarehouseFk INT + ,vCompanyFk INT + ,vAddressFk INT + ,vAgencyModeFk INT + ,vRouteFk INT + ,vlanded DATE + ,vUserId INT + ,vIsRequiredZone INT + ,OUT vNewTicket INT) +BEGIN +/** +* Crea un ticket, +* ¡¡NO se debe llamar directamente, llamar a salix que hace comprobaciones previas!! +* +* @param vClientId id del cliente +* @param vShipped dia preparacion +* @param vWarehouseFk id del warehouse +* @param vCompanyFk id la empresa +* @param vAddressFk id del consignatario +* @param vAgencyModeFk id de la agencia +* @param vRouteFk id de la ruta | NULL +* @param vlanded dia llegada +* @param vUserId que crea el ticket +* @param vIsRequiredZone Indica si tiene que tener zona valida para ser creado +* @return vNewTicket id del ticket creado +*/ + DECLARE vZoneFk INT; + DECLARE vPrice DECIMAL(10,2); + DECLARE vBonus DECIMAL(10,2); + DECLARE vIsActive BOOL; + + IF vClientId IS NULL THEN + CALL util.throw ('CLIENT_NOT_ESPECIFIED'); + END IF; + + SELECT isActive INTO vIsActive + FROM vn.client + WHERE id = vClientId; + + IF NOT vIsActive THEN + CALL util.throw ('CLIENT_NOT_ACTIVE'); + END IF; + + IF NOT vAddressFk OR vAddressFk IS NULL THEN + SELECT id INTO vAddressFk + FROM address + WHERE clientFk = vClientId + AND isDefaultAddress; + END IF; + + IF vAgencyModeFk IS NOT NULL THEN + CALL vn.zone_getShipped (vlanded, vAddressFk, vAgencyModeFk, TRUE); + + SELECT zoneFk, price, bonus + INTO vZoneFk, vPrice, vBonus + FROM tmp.zoneGetShipped + WHERE shipped = vShipped + AND warehouseFk = vWarehouseFk + LIMIT 1; + + IF (vZoneFk IS NULL OR vZoneFk = 0) AND vIsRequiredZone THEN + CALL util.throw ('NOT_ZONE_WITH_THIS_PARAMETERS'); + END IF; + END IF; + + INSERT INTO ticket ( + clientFk, + shipped, + addressFk, + agencyModeFk, + nickname, + warehouseFk, + routeFk, + companyFk, + landed, + zoneFk, + zonePrice, + zoneBonus + ) + SELECT vClientId, + vShipped, + a.id, + vAgencyModeFk, + a.nickname, + vWarehouseFk, + IF(vRouteFk,vRouteFk,NULL), + vCompanyFk, + vlanded, + vZoneFk, + vPrice, + vBonus + FROM address a + JOIN agencyMode am ON am.id = a.agencyModeFk + WHERE a.id = vAddressFk; + + SET vNewTicket = LAST_INSERT_ID(); + + INSERT INTO ticketObservation(ticketFk, observationTypeFk, description) + SELECT vNewTicket, ao.observationTypeFk, ao.description + FROM addressObservation ao + JOIN address a ON a.id = ao.addressFk + WHERE a.id = vAddressFk; + + IF (SELECT COUNT(*) + FROM bs.clientNewBorn cnb + WHERE cnb.clientFk = vClientId + AND NOT cnb.isRookie) = 0 THEN + + CALL vn.ticketObservation_addNewBorn(vNewTicket); + END IF; + + IF (SELECT ct.isCreatedAsServed FROM vn.clientType ct JOIN vn.client c ON c.typeFk = ct.code WHERE c.id = vClientId ) <> FALSE THEN + INSERT INTO ticketTracking(stateFk, ticketFk, userFk) + SELECT id, vNewTicket, account.myUser_getId() + FROM state + WHERE `code` = 'DELIVERED'; + END IF; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_setNextState`(vSelf INT) +BEGIN +/** + * Cambia el estado del ticket al siguiente estado según la tabla state + * + * @param vSelf id dle ticket + */ + DECLARE vStateFk INT; + DECLARE vNewStateFk INT; + + SELECT stateFk INTO vStateFk + FROM ticketState + WHERE ticketFk = vSelf; + + SELECT nextStateFk INTO vNewStateFk + FROM state + WHERE id = vStateFk; + + INSERT INTO ticketTracking(stateFk, ticketFk, userFk) + VALUES (vNewStateFk, vSelf, account.myUser_getId()); +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_setPreviousState`(vTicketFk INT) +BEGIN + DECLARE vControlFk INT; + + SELECT MAX(id) INTO vControlFk + FROM ticketTracking + WHERE ticketFk = vTicketFk; + + IF (SELECT s.code + FROM vn.state s + JOIN ticketTracking tt ON tt.stateFk = s.id + WHERE tt.id = vControlFk) + = 'PREVIOUS_PREPARATION' THEN + SELECT id + INTO vControlFk + FROM ticketTracking tt + JOIN vn.state s ON tt.stateFk = s.id + WHERE ticketFk = vTicketFk + AND id < vControlFk + AND s.code != 'PREVIOUS_PREPARATION' + ORDER BY id DESC + LIMIT 1; + + INSERT INTO ticketTracking(stateFk, ticketFk, userFk) + SELECT s.nextStateFk, tt.ticketFk, account.myUser_getId() + FROM ticketTracking tt + JOIN vn.state s ON tt.stateFk = s.id + WHERE id = vControlFk; + END IF; +END$$ +DELIMITER ; + +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, userFk) + 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 ; diff --git a/db/changes/240001/01-refactorHasAnyNegativeBase.sql b/db/changes/240001/01-refactorHasAnyNegativeBase.sql new file mode 100644 index 000000000..a3eb2d9c7 --- /dev/null +++ b/db/changes/240001/01-refactorHasAnyNegativeBase.sql @@ -0,0 +1,32 @@ +DELIMITER $$ +$$ +CREATE OR REPLACE DEFINER=`root`@`localhost` FUNCTION `vn`.`hasAnyNegativeBase`() RETURNS tinyint(1) + DETERMINISTIC +BEGIN + +/** +* Calcula si existe alguna base imponible negativa +* Requiere la tabla temporal tmp.ticketToInvoice(id) para getTaxBases() +* +* returns BOOLEAN +*/ + + DECLARE hasAnyNegativeBase BOOLEAN; + + CALL getTaxBases(); + + SELECT negative INTO hasAnyNegativeBase + FROM tmp.taxBases + LIMIT 1; + + DROP TEMPORARY TABLE + tmp.ticketTax, + tmp.ticket, + tmp.taxBases; + + RETURN hasAnyNegativeBase; + +END$$ +DELIMITER ; + + diff --git a/db/changes/240001/02-views.sql b/db/changes/240001/02-views.sql new file mode 100644 index 000000000..86a1049a7 --- /dev/null +++ b/db/changes/240001/02-views.sql @@ -0,0 +1,56 @@ +CREATE SCHEMA IF NOT EXISTS `vn2008`; +USE `vn`; + +CREATE OR REPLACE DEFINER=`root`@`localhost` + SQL SECURITY DEFINER + VIEW `ticketState` +AS SELECT `tt`.`created` AS `updated`, + `tt`.`stateFk` AS `stateFk`, + `tt`.`userFk` AS `workerFk`, + `tls`.`ticketFk` AS `ticketFk`, + `s`.`id` AS `state`, + `s`.`order` AS `productionOrder`, + `s`.`alertLevel` AS `alertLevel`, + `s`.`code` AS `code`, + `tls`.`ticketFk` AS `ticket`, + `tt`.`userFk` AS `worker`, + `s`.`isPreviousPreparable` AS `isPreviousPreparable`, + `s`.`isPicked` AS `isPicked` +FROM ( + ( + `ticketLastState` `tls` + JOIN `ticketTracking` `tt` ON(`tt`.`id` = `tls`.`ticketTrackingFk`) + ) + JOIN `state` `s` ON(`s`.`id` = `tt`.`stateFk`) + ); + +CREATE OR REPLACE DEFINER=`root`@`localhost` + SQL SECURITY DEFINER + VIEW `vn2008`.`v_inter` +AS SELECT `tt`.`id` AS `inter_id`, + `tt`.`stateFk` AS `state_id`, + `tt`.`notes` AS `nota`, + `tt`.`created` AS `odbc_date`, + `tt`.`ticketFk` AS `Id_Ticket`, + `tt`.`userFk` AS `Id_Trabajador`, + `tt`.`supervisorFk` AS `Id_supervisor` +FROM `vn`.`ticketTracking` `tt`; + +CREATE OR REPLACE DEFINER=`root`@`localhost` + SQL SECURITY DEFINER + VIEW `ticketStateToday` +AS SELECT + `ts`.`ticket` AS `ticket`, + `ts`.`state` AS `state`, + `ts`.`productionOrder` AS `productionOrder`, + `ts`.`alertLevel` AS `alertLevel`, + `ts`.`worker` AS `worker`, + `ts`.`code` AS `code`, + `ts`.`updated` AS `updated`, + `ts`.`isPicked` AS `isPicked` +FROM + (`ticketState` `ts` +JOIN `ticket` `t` ON + (`t`.`id` = `ts`.`ticket`)) +WHERE + `t`.`shipped` BETWEEN `util`.`VN_CURDATE`() AND `MIDNIGHT`(`util`.`VN_CURDATE`()); diff --git a/db/changes/235201/.gitkeep b/db/changes/240201/.gitkeep similarity index 100% rename from db/changes/235201/.gitkeep rename to db/changes/240201/.gitkeep diff --git a/db/changes/240201/00-tabletDocuware.sql b/db/changes/240201/00-tabletDocuware.sql new file mode 100644 index 000000000..ffa0226b3 --- /dev/null +++ b/db/changes/240201/00-tabletDocuware.sql @@ -0,0 +1,10 @@ +-- vn.docuwareTablet definition + +CREATE TABLE `vn`.`docuwareTablet` ( + `tablet` varchar(100) NOT NULL PRIMARY KEY, + `description` varchar(255) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; + +ALTER TABLE `vn`.`userConfig` +ADD COLUMN tabletFk varchar(100) DEFAULT NULL, +ADD FOREIGN KEY (tabletFk) REFERENCES `vn`.`docuwareTablet`(tablet); diff --git a/db/changes/240201/00-ticketSmsToClientSms.sql b/db/changes/240201/00-ticketSmsToClientSms.sql new file mode 100644 index 000000000..cd3cf7dd3 --- /dev/null +++ b/db/changes/240201/00-ticketSmsToClientSms.sql @@ -0,0 +1,9 @@ +ALTER TABLE `vn`.`clientSms` ADD `ticketFk` int(11) NULL; +ALTER TABLE `vn`.`clientSms` ADD CONSTRAINT `clientSms_FK_2` FOREIGN KEY (`ticketFk`) REFERENCES `vn`.`ticket`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +INSERT INTO`vn`.`clientSms` (`clientFk`, `smsFk`, `ticketFk`) + SELECT `t`.`clientFk`, `s`.`smsFk`, `s`.`ticketFk` + FROM `vn`.`clientSms` `s` + JOIN `vn`.`ticket` `t` ON `t`.`id` = `s`.`ticketFk`; + +RENAME TABLE `vn`.`ticketSms` TO `vn`.`ticketSms__`; diff --git a/db/changes/240201/00-timecontrol.sql b/db/changes/240201/00-timecontrol.sql new file mode 100644 index 000000000..c3ddf5d96 --- /dev/null +++ b/db/changes/240201/00-timecontrol.sql @@ -0,0 +1,17 @@ +DELETE FROM `salix`.`ACL` + WHERE model = 'VnUser' + AND property = 'renewToken'; + +INSERT INTO `account`.`role` (name, description) + VALUES ('timeControl','Tablet para fichar'); + +INSERT INTO `account`.`roleInherit` (role, inheritsFrom) + VALUES (127, 11); + +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) + VALUES + ('WorkerTimeControl', 'login', 'READ', 'ALLOW', 'ROLE', 'timeControl'), + ('WorkerTimeControl', 'getClockIn', 'READ', 'ALLOW', 'ROLE', 'timeControl'), + ('WorkerTimeControl', 'clockIn', 'WRITE', 'ALLOW', 'ROLE', 'timeControl'); + +CALL `account`.`role_sync`(); diff --git a/db/changes/240401/.gitkeep b/db/changes/240401/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index f422dcb45..8fd1961bb 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -5,10 +5,6 @@ SET DEFAULT ROLE 'salix' FOR 'root'@'%'; CREATE SCHEMA IF NOT EXISTS `vn2008`; CREATE SCHEMA IF NOT EXISTS `tmp`; -CREATE ROLE 'salix'; -GRANT 'salix' TO 'root'@'%'; -SET DEFAULT ROLE 'salix' FOR 'root'@'%'; - UPDATE `util`.`config` SET `environment`= 'development'; @@ -497,7 +493,7 @@ INSERT INTO `vn`.`clientCredit`(`clientFk`, `workerFk`, `amount`, `created`) (1104, 9, 90 , util.VN_CURDATE()), (1105, 9, 90 , util.VN_CURDATE()); -INSERT INTO `vn`.`clientCreditLimit`(`id`, `maxAmount`, `roleFk`) +INSERT INTO `vn`.`roleCreditLimit`(`id`, `maxAmount`, `roleFk`) VALUES (1, 9999999, 20), (2, 10000, 21), @@ -606,18 +602,19 @@ INSERT INTO `vn`.`taxArea` (`code`, `claveOperacionFactura`, `CodigoTransaccion` INSERT INTO `vn`.`invoiceOutSerial` (`code`, `description`, `isTaxed`, `taxAreaFk`, `isCEE`, `type`) VALUES - ('A', 'Global nacional', 1, 'NATIONAL', 0, 'global'), - ('T', 'Española rapida', 1, 'NATIONAL', 0, 'quick'), - ('V', 'Intracomunitaria global', 0, 'CEE', 1, 'global'), - ('M', 'Múltiple nacional', 1, 'NATIONAL', 0, 'quick'), - ('E', 'Exportación rápida', 0, 'WORLD', 0, 'quick'); + ('A', 'Global nacional', 1, 'NATIONAL', 0, 'global'), + ('T', 'Española rapida', 1, 'NATIONAL', 0, 'quick'), + ('V', 'Intracomunitaria global', 0, 'CEE', 1, 'global'), + ('M', 'Múltiple nacional', 1, 'NATIONAL', 0, 'quick'), + ('R', 'Rectificativa', 1, 'NATIONAL', 0, NULL), + ('E', 'Exportación rápida', 0, 'WORLD', 0, 'quick'); INSERT INTO `vn`.`invoiceOut`(`id`, `serial`, `amount`, `issued`,`clientFk`, `created`, `companyFk`, `dued`, `booked`, `bankFk`, `hasPdf`) VALUES (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), + (4, 'T', 8.88, util.VN_CURDATE(), 1104, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0), (5, 'A', 8.88, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1103, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 442, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1, 0); UPDATE `vn`.`invoiceOut` SET ref = 'T1111111' WHERE id = 1; @@ -723,7 +720,7 @@ INSERT INTO `vn`.`route`(`id`, `time`, `workerFk`, `created`, `vehicleFk`, `agen INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeFk`, `shipped`, `landed`, `clientFk`,`nickname`, `addressFk`, `refFk`, `isDeleted`, `zoneFk`, `zonePrice`, `zoneBonus`, `created`, `weight`) VALUES (1 , 3, 1, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1101, 'Bat cave', 121, NULL, 0, 1, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 1), - (2 , 1, 1, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 1, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 2), + (2 , 1, 1, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH), INTERVAL +1 DAY), 1101, 'Bat cave', 1, NULL, 0, 1, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH), 2), (3 , 1, 7, 1, 6, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -2 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 3, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH), NULL), (4 , 3, 2, 1, 2, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -3 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 9, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -3 MONTH), NULL), (5 , 3, 3, 3, 3, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH), DATE_ADD(DATE_ADD(util.VN_CURDATE(),INTERVAL -4 MONTH), INTERVAL +1 DAY), 1104, 'Stark tower', 124, NULL, 0, 10, 5, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -4 MONTH), NULL), @@ -775,7 +772,7 @@ INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `des -- FIX for state hours on local, inter_afterInsert -- UPDATE vncontrol.inter SET odbc_date = DATE_ADD(util.VN_CURDATE(), INTERVAL -10 SECOND); -INSERT INTO `vn`.`ticketTracking`(`ticketFk`, `stateFk`, `workerFk`, `created`) +INSERT INTO `vn`.`ticketTracking`(`ticketFk`, `stateFk`, `userFk`, `created`) VALUES (1, 16, 5 , DATE_ADD(util.VN_NOW(), INTERVAL -1 MONTH)), (2, 16, 5 , DATE_ADD(util.VN_NOW(), INTERVAL -1 MONTH)), @@ -819,6 +816,7 @@ INSERT INTO `vn`.`config`(`id`, `mdbServer`, `fakeEmail`, `defaultersMaxAmount`, VALUES (1, 'beta-server', 'nightmare@mydomain.com', '200', DATE_ADD(util.VN_CURDATE(),INTERVAL -1 MONTH)); + INSERT INTO `vn`.`greugeType`(`id`, `name`, `code`) VALUES (1, 'Diff', 'diff'), @@ -2970,9 +2968,9 @@ INSERT INTO `vn`.`wagonTypeTray` (`id`, `typeFk`, `height`, `colorFk`) (2, 1, 50, 2), (3, 1, 0, 3); -INSERT INTO `salix`.`accessTokenConfig` (`id`, `renewPeriod`, `renewInterval`) +INSERT INTO `salix`.`accessTokenConfig` (`id`, `renewPeriod`, `courtesyTime`, `renewInterval`) VALUES - (1, 21600, 300); + (1, 21600, 60, 300); INSERT INTO `vn`.`travelConfig` (`id`, `warehouseInFk`, `warehouseOutFk`, `agencyFk`, `companyFk`) VALUES @@ -3012,3 +3010,28 @@ INSERT INTO `vn`.`invoiceCorrectionType` (`id`, `description`) (1, 'Error in VAT calculation'), (2, 'Error in sales details'), (3, 'Error in customer data'); + +INSERT INTO `account`.`mailAliasAcl` (`mailAliasFk`, `roleFk`) + VALUES + (1, 1), + (2, 9), + (3, 15); + +INSERT INTO `vn`.`docuwareTablet` (`tablet`,`description`) + VALUES + ('Tablet1','Jarvis tablet'), + ('Tablet2','Avengers tablet'); + +INSERT INTO `vn`.`sms` (`id`, `senderFk`, `sender`, `destination`, `message`, `statusCode`, `status`, `created`) + VALUES (1, 66, '111111111', '0001111111111', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 0, 'OK', util.VN_CURDATE()), + (2, 66, '222222222', '0002222222222', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 0, 'PENDING', util.VN_CURDATE()), + (3, 66, '333333333', '0003333333333', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 0, 'ERROR', util.VN_CURDATE()), + (4, 66, '444444444', '0004444444444', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 0, 'OK', util.VN_CURDATE()); + +INSERT INTO `vn`.`clientSms` (`id`, `clientFk`, `smsFk`, `ticketFk`) + VALUES(1, 1103, 1, NULL), + (2, 1103, 2, NULL), + (3, 1103, 3, 32), + (4, 1103, 4, 32), + (13, 1101, 1, NULL), + (14, 1101, 4, 27); \ No newline at end of file diff --git a/db/dump/structure.sql b/db/dump/structure.sql index 1db4252f4..694f745ef 100644 --- a/db/dump/structure.sql +++ b/db/dump/structure.sql @@ -26391,6 +26391,7 @@ CREATE TABLE `cplusCorrectingType` ( ) ENGINE=InnoDBDEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; /*!40101 SET character_set_client = @saved_cs_client */; + -- -- Table structure for table `cplusRectificationType` -- diff --git a/db/tests/vn/ticket_componentMakeUpdate.spec.js b/db/tests/vn/ticket_componentMakeUpdate.spec.js deleted file mode 100644 index a059f1060..000000000 --- a/db/tests/vn/ticket_componentMakeUpdate.spec.js +++ /dev/null @@ -1,123 +0,0 @@ -const app = require('vn-loopback/server/server'); -const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; - -// 2277 solucionar problema al testear procedimiento con start transaction / rollback -xdescribe('ticket_componentMakeUpdate()', () => { - it('should recalculate the ticket components without make modifications', async() => { - let stmts = []; - let stmt; - - let params = { - ticketId: 11, - clientId: 1102, - agencyModeId: 2, - addressId: 122, - zoneId: 3, - warehouseId: 1, - companyId: 442, - isDeleted: 0, - hasToBeUnrouted: 0, - componentOption: 1 - }; - - stmts.push('START TRANSACTION'); - - stmt = new ParameterizedSQL('SELECT * FROM vn.ticket WHERE id = ?', [ - params.ticketId - ]); - stmts.push(stmt); - - let originalTicketIndex = stmts.push(stmt) - 1; - - stmt = new ParameterizedSQL('CALL vn.ticket_componentMakeUpdate(?, ?, ?, ?, ?, ?, ?, DATE_ADD(CURDATE(), INTERVAL +1 DAY), DATE_ADD(CURDATE(), INTERVAL +1 DAY), ?, ?, ?)', [ - params.ticketId, - params.clientId, - params.agencyModeId, - params.addressId, - params.zoneId, - params.warehouseId, - params.companyId, - params.isDeleted, - params.hasToBeUnrouted, - params.componentOption - ]); - stmts.push(stmt); - - stmt = new ParameterizedSQL('SELECT * FROM vn.ticket WHERE id = ?', [ - params.ticketId - ]); - stmts.push(stmt); - - let updatedTicketIndex = stmts.push(stmt) - 1; - - stmts.push('ROLLBACK'); - - let sql = ParameterizedSQL.join(stmts, ';'); - let result = await app.models.Ticket.rawStmt(sql); - - let originalTicketData = result[originalTicketIndex]; - let updatedTicketData = result[updatedTicketIndex]; - - expect(originalTicketData[0].isDeleted).toEqual(updatedTicketData[0].isDeleted); - expect(originalTicketData[0].routeFk).toEqual(updatedTicketData[0].routeFk); - }); - - it('should delete and unroute a ticket and recalculate the components', async() => { - let stmts = []; - let stmt; - - let params = { - ticketId: 11, - clientId: 1102, - agencyModeId: 2, - addressId: 122, - zoneId: 3, - warehouseId: 1, - companyId: 442, - isDeleted: 1, - hasToBeUnrouted: 1, - componentOption: 1 - }; - - stmts.push('START TRANSACTION'); - - stmt = new ParameterizedSQL('SELECT * FROM vn.ticket WHERE id = ?', [ - params.ticketId - ]); - stmts.push(stmt); - - let originalTicketIndex = stmts.push(stmt) - 1; - - stmt = new ParameterizedSQL('CALL vn.ticket_componentMakeUpdate(?, ?, ?, ?, ?, ?, ?, DATE_ADD(CURDATE(), INTERVAL +1 DAY), DATE_ADD(CURDATE(), INTERVAL +1 DAY), ?, ?, ?)', [ - params.ticketId, - params.clientId, - params.agencyModeId, - params.addressId, - params.zoneId, - params.warehouseId, - params.companyId, - params.isDeleted, - params.hasToBeUnrouted, - params.componentOption - ]); - stmts.push(stmt); - - stmt = new ParameterizedSQL('SELECT * FROM vn.ticket WHERE id = ?', [ - params.ticketId - ]); - stmts.push(stmt); - - let updatedTicketIndex = stmts.push(stmt) - 1; - - stmts.push('ROLLBACK'); - - let sql = ParameterizedSQL.join(stmts, ';'); - let result = await app.models.Ticket.rawStmt(sql); - - let originalTicketData = result[originalTicketIndex]; - let updatedTicketData = result[updatedTicketIndex]; - - expect(originalTicketData[0].isDeleted).not.toEqual(updatedTicketData[0].isDeleted); - expect(originalTicketData[0].routeFk).not.toEqual(updatedTicketData[0].routeFk); - }); -}); diff --git a/e2e/paths/03-worker/04_time_control.spec.js b/e2e/paths/03-worker/04_time_control.spec.js index 5f64aa6ce..c6589d2e3 100644 --- a/e2e/paths/03-worker/04_time_control.spec.js +++ b/e2e/paths/03-worker/04_time_control.spec.js @@ -56,63 +56,6 @@ describe('Worker time control path', () => { expect(result).toContain(month); }); - it(`should return error when insert 'out' of first entry`, async() => { - pending('https://redmine.verdnatura.es/issues/4707'); - await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton); - await page.pickTime(selectors.workerTimeControl.dialogTimeInput, eightAm); - await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out'); - await page.respondToDialog('accept'); - const message = await page.waitForSnackbar(); - - expect(message.text).toBeDefined(); - }); - - it(`should insert 'in' monday`, async() => { - pending('https://redmine.verdnatura.es/issues/4707'); - await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton); - await page.pickTime(selectors.workerTimeControl.dialogTimeInput, eightAm); - await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in'); - await page.respondToDialog('accept'); - const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText'); - - expect(result).toEqual(eightAm); - }); - - it(`should insert 'out' monday`, async() => { - pending('https://redmine.verdnatura.es/issues/4707'); - await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton); - await page.pickTime(selectors.workerTimeControl.dialogTimeInput, fourPm); - await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out'); - await page.respondToDialog('accept'); - const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfMonday, 'innerText'); - - expect(result).toEqual(fourPm); - }); - - it(`should check Hank Pym worked 8:20 hours`, async() => { - pending('https://redmine.verdnatura.es/issues/4707'); - await page.waitForTextInElement(selectors.workerTimeControl.mondayWorkedHours, '08:20 h.'); - await page.waitForTextInElement(selectors.workerTimeControl.weekWorkedHours, '08:20 h.'); - }); - - it('should remove first entry of monday', async() => { - pending('https://redmine.verdnatura.es/issues/4707'); - await page.waitForTextInElement(selectors.workerTimeControl.firstEntryOfMonday, eightAm); - await page.waitForTextInElement(selectors.workerTimeControl.secondEntryOfMonday, fourPm); - await page.waitToClick(selectors.workerTimeControl.firstEntryOfMondayDelete); - await page.respondToDialog('accept'); - const message = await page.waitForSnackbar(); - - expect(message.text).toContain('Entry removed'); - }); - - it(`should be the 'out' the first entry of monday`, async() => { - pending('https://redmine.verdnatura.es/issues/4707'); - const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText'); - - expect(result).toEqual(fourPm); - }); - it('should change week of month', async() => { await page.click(selectors.workerTimeControl.thrirdWeekDay); const result = await page.getProperty(selectors.workerTimeControl.mondayWorkedHours, 'innerText'); diff --git a/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js b/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js index 5e82306cc..b74f81253 100644 --- a/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js +++ b/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js @@ -188,17 +188,6 @@ describe('Ticket Edit sale path', () => { expect(result).toContain('22.50'); }); - it('should check in the history that logs has been added', async() => { - pending('https://redmine.verdnatura.es/issues/5455'); - await page.reload({waitUntil: ['networkidle0', 'domcontentloaded']}); - await page.waitToClick(selectors.ticketSales.firstSaleHistoryButton); - await page.waitForSelector(selectors.ticketSales.firstSaleHistory); - const result = await page.countElement(selectors.ticketSales.firstSaleHistory); - - expect(result).toBeGreaterThan(0); - await page.waitToClick(selectors.ticketSales.closeHistory); - }); - it('should recalculate price of sales', async() => { await page.waitToClick(selectors.ticketSales.firstSaleCheckbox); await page.waitToClick(selectors.ticketSales.secondSaleCheckbox); diff --git a/e2e/paths/05-ticket/11_diary.spec.js b/e2e/paths/05-ticket/11_diary.spec.js deleted file mode 100644 index e4c63855a..000000000 --- a/e2e/paths/05-ticket/11_diary.spec.js +++ /dev/null @@ -1,31 +0,0 @@ -import selectors from '../../helpers/selectors.js'; -import getBrowser from '../../helpers/puppeteer'; - -// #2221 Local MySQL8 crashes when rest method Items/getBalance is called -xdescribe('Ticket diary path', () => { - let page; - - beforeAll(async() => { - page = (await getBrowser()).page; - await page.loginAndModule('employee', 'ticket'); - }); - - afterAll(async() => { - await page.browser().close(); - }); - - it(`should navigate to item diary from ticket sale and check the lines`, async() => { - await page.accessToSearchResult('1'); - await page.waitToClick(selectors.ticketSummary.firstSaleItemId); - await page.waitToClick(selectors.ticketSummary.popoverDiaryButton); - await page.waitForState('item.card.diary'); - - const secondIdClass = await page.getClassName(selectors.itemDiary.secondTicketId); - const fourthBalanceClass = await page.getClassName(selectors.itemDiary.fourthBalance); - const firstBalanceClass = await page.getClassName(selectors.itemDiary.firstBalance); - - expect(secondIdClass).toContain('message'); - expect(fourthBalanceClass).toContain('message'); - expect(firstBalanceClass).toContain('balance'); - }); -}); diff --git a/e2e/paths/05-ticket/18_index_payout.spec.js b/e2e/paths/05-ticket/18_index_payout.spec.js index 7e5201d11..9c5518424 100644 --- a/e2e/paths/05-ticket/18_index_payout.spec.js +++ b/e2e/paths/05-ticket/18_index_payout.spec.js @@ -35,7 +35,7 @@ describe('Ticket index payout path', () => { await page.waitToClick(selectors.ticketsIndex.openAdvancedSearchButton); await page.write(selectors.ticketsIndex.advancedSearchClient, '1101'); await page.keyboard.press('Enter'); - await page.waitForNumberOfElements(selectors.ticketsIndex.anySearchResult, 9); + await page.waitForNumberOfElements(selectors.ticketsIndex.anySearchResult, 10); await page.waitToClick(selectors.ticketsIndex.firstTicketCheckbox); await page.waitToClick(selectors.ticketsIndex.secondTicketCheckbox); diff --git a/e2e/paths/06-claim/02_detail.spec.js b/e2e/paths/06-claim/02_detail.spec.js deleted file mode 100644 index eb4ac5d71..000000000 --- a/e2e/paths/06-claim/02_detail.spec.js +++ /dev/null @@ -1,114 +0,0 @@ -import selectors from '../../helpers/selectors.js'; -import getBrowser from '../../helpers/puppeteer.js'; - -// #1528 e2e claim/detail -xdescribe('Claim detail', () => { - let browser; - let page; - - beforeAll(async() => { - browser = await getBrowser(); - page = browser.page; - await page.loginAndModule('salesPerson', 'claim'); - await page.accessToSearchResult('1'); - await page.accessToSection('claim.card.detail'); - }); - - afterAll(async() => { - await browser.close(); - }); - - it('should add the first claimable item from ticket to the claim', async() => { - await page.waitToClick(selectors.claimDetail.addItemButton); - await page.waitToClick(selectors.claimDetail.firstClaimableSaleFromTicket); - const message = await page.waitForSnackbar(); - - expect(message.text).toContain('Data saved!'); - }); - - it('should confirm the claim contains now two items', async() => { - const result = await page.countElement(selectors.claimDetail.claimDetailLine); - - expect(result).toEqual(2); - }); - - it('should edit de first item claimed quantity', async() => { - await page.clearInput(selectors.claimDetail.firstItemQuantityInput); // selector deleted, find new upon fixes - await page.write(selectors.claimDetail.firstItemQuantityInput, '4'); // selector deleted, find new upon fixes - await page.keyboard.press('Enter'); - const message = await page.waitForSnackbar(); - - expect(message.text).toContain('Data saved!'); - }); - - it('should confirm the first item quantity, and the claimed total were correctly edited', async() => { - const claimedQuantity = page - .waitToGetProperty(selectors.claimDetail.firstItemQuantityInput, 'value'); // selector deleted, find new upon fixes - - const totalClaimed = page - .waitToGetProperty(selectors.claimDetail.totalClaimed, 'innerText'); - - expect(claimedQuantity).toEqual('4'); - expect(totalClaimed).toContain('€47.62'); - }); - - it('should login as salesAssistant and navigate to the claim.detail section', async() => { - await page.loginAndModule('salesAssistant', 'claim'); - await page.accessToSearchResult('1'); - await page.accessToSection('claim.card.detail'); - let url = await page.expectURL('/detail'); // replace with waitForState - - expect(url).toBe(true); - }); - - it('should edit de second item claimed discount', async() => { - await page.waitToClick(selectors.claimDetail.secondItemDiscount); - await page.write(selectors.claimDetail.discount, '100'); - await page.keyboard.press('Enter'); - const message = await page.waitForSnackbar(); - - expect(message.text).toContain('Data saved!'); - }); - - it('should check the mana is the expected one', async() => { - await page.waitToClick(selectors.claimDetail.secondItemDiscount); - const result = await page.waitToGetProperty(selectors.claimDetail.discoutPopoverMana, 'innerText'); - - expect(result).toContain('MANÁ: €106'); - }); - - it('should delete the second item from the claim', async() => { - await page.waitToClick(selectors.claimDetail.secondItemDeleteButton); - const message = await page.waitForSnackbar(); - - expect(message.text).toContain('Data saved!'); - }); - - it('should confirm the claim contains now one item', async() => { - const result = await page.countElement(selectors.claimDetail.claimDetailLine); - - expect(result).toEqual(1); - }); - - it('should add the deleted ticket from to the claim', async() => { - await page.waitToClick(selectors.claimDetail.addItemButton); - await page.waitToClick(selectors.claimDetail.firstClaimableSaleFromTicket); - const message = await page.waitForSnackbar(); - - expect(message.text).toContain('Data saved!'); - }); - - it(`should have been redirected to the next section in claims`, async() => { - let url = await page.expectURL('development'); // replace with waitForState - - expect(url).toBe(true); - }); - - it('should navigate back to claim.detail to confirm the claim contains now two items', async() => { - await page.accessToSection('claim.card.detail'); - await page.waitForSelector(selectors.claimDetail.claimDetailLine); - const result = await page.countElement(selectors.claimDetail.claimDetailLine); - - expect(result).toEqual(2); - }); -}); diff --git a/e2e/paths/08-route/03_create_and_clone.spec.js b/e2e/paths/08-route/03_create_and_clone.spec.js index 0b8da98b4..31c0cfc18 100644 --- a/e2e/paths/08-route/03_create_and_clone.spec.js +++ b/e2e/paths/08-route/03_create_and_clone.spec.js @@ -26,7 +26,7 @@ describe('Route create path', () => { await page.waitToClick(selectors.createRouteView.submitButton); const message = await page.waitForSnackbar(); - expect(message.text).toContain('Access denied'); + expect(message.text).toContain('Access Denied'); }); }); diff --git a/e2e/paths/09-invoice-out/01_summary.spec.js b/e2e/paths/09-invoice-out/01_summary.spec.js index 728f0130a..09ac66ffc 100644 --- a/e2e/paths/09-invoice-out/01_summary.spec.js +++ b/e2e/paths/09-invoice-out/01_summary.spec.js @@ -28,7 +28,6 @@ describe('InvoiceOut summary path', () => { it('should contain the tax breakdown', async() => { const firstTax = await page.waitToGetProperty(selectors.invoiceOutSummary.taxOne, 'innerText'); - const secondTax = await page.waitToGetProperty(selectors.invoiceOutSummary.taxTwo, 'innerText'); expect(firstTax).toContain('10%'); @@ -37,10 +36,9 @@ describe('InvoiceOut summary path', () => { it('should contain the tickets info', async() => { const firstTicket = await page.waitToGetProperty(selectors.invoiceOutSummary.ticketOne, 'innerText'); - const secondTicket = await page.waitToGetProperty(selectors.invoiceOutSummary.ticketTwo, 'innerText'); expect(firstTicket).toContain('Bat cave'); - expect(secondTicket).toContain('Stark tower'); + expect(secondTicket).toContain('Bat cave'); }); }); diff --git a/e2e/paths/10-travel/02_basic_data_and_log.spec.js b/e2e/paths/10-travel/02_basic_data_and_log.spec.js index 0079e8023..e6c601d7d 100644 --- a/e2e/paths/10-travel/02_basic_data_and_log.spec.js +++ b/e2e/paths/10-travel/02_basic_data_and_log.spec.js @@ -105,17 +105,4 @@ describe('Travel basic data path', () => { it(`should check the received checkbox was saved even tho it doesn't make sense`, async() => { await page.waitForClassPresent(selectors.travelBasicData.received, 'checked'); }); - - it('should navigate to the travel logs', async() => { - pending('https://redmine.verdnatura.es/issues/5455'); - await page.accessToSection('travel.card.log'); - await page.waitForState('travel.card.log'); - }); - - it('should check the 1st log contains details from the changes made', async() => { - pending('https://redmine.verdnatura.es/issues/5455'); - const result = await page.waitToGetProperty(selectors.travelLog.firstLogFirstTD, 'innerText'); - - expect(result).toContain('new reference!'); - }); }); diff --git a/front/core/components/treeview/index.spec.js b/front/core/components/treeview/index.spec.js index 9277f3ee4..1979d517f 100644 --- a/front/core/components/treeview/index.spec.js +++ b/front/core/components/treeview/index.spec.js @@ -33,18 +33,6 @@ describe('Component vnTreeview', () => { $element.remove(); }); - // See how to test DOM element in Jest - xdescribe('undrop()', () => { - it(`should reset all drop events and properties`, () => { - controller.dropping = angular.element(``); - jest.spyOn(controller.dropping.classList, 'remove'); - - controller.undrop(); - - expect(controller.dropping).toBeNull(); - }); - }); - describe('dragOver()', () => { it(`should set the dragClientY property`, () => { const event = new Event('dragover'); diff --git a/front/core/directives/rule.js b/front/core/directives/rule.js index f65efe176..34781c2aa 100644 --- a/front/core/directives/rule.js +++ b/front/core/directives/rule.js @@ -23,7 +23,6 @@ export function directive($translate, $window) { let rule = $attrs.rule.split('.'); let modelName = rule.shift(); let fieldName = rule.shift(); - let split = $attrs.ngModel.split('.'); if (!fieldName) fieldName = split.pop() || null; if (!modelName) modelName = firstUpper(split.pop() || ''); diff --git a/front/core/locale/es.yml b/front/core/locale/es.yml index 96c34d98c..17e955ff5 100644 --- a/front/core/locale/es.yml +++ b/front/core/locale/es.yml @@ -68,3 +68,4 @@ Load more results: Cargar más resultados Send cau: Enviar cau By sending this ticket, all the data related to the error, the section, the user, etc., are already sent.: Al enviar este cau ya se envían todos los datos relacionados con el error, la sección, el usuario, etc ExplainReason: Explique el motivo por el que no deberia aparecer este fallo +You already have the mailAlias: Ya tienes este alias de correo diff --git a/front/core/services/locale/en.yml b/front/core/services/locale/en.yml index 2be73e696..8eb8a6f0d 100644 --- a/front/core/services/locale/en.yml +++ b/front/core/services/locale/en.yml @@ -3,4 +3,4 @@ Could not contact the server: Could not contact the server, make sure you have a Please enter your username: Please enter your username It seems that the server has fall down: It seems that the server has fall down, wait a few minutes and try again Session has expired: Your session has expired, please login again -Access denied: Access denied \ No newline at end of file +Access Denied: Access Denied \ No newline at end of file diff --git a/front/core/services/locale/es.yml b/front/core/services/locale/es.yml index e9811e38f..2c43ca3b2 100644 --- a/front/core/services/locale/es.yml +++ b/front/core/services/locale/es.yml @@ -3,5 +3,5 @@ Could not contact the server: No se ha podido contactar con el servidor, asegura Please enter your username: Por favor introduce tu nombre de usuario It seems that the server has fall down: Parece que el servidor se ha caído, espera unos minutos e inténtalo de nuevo Session has expired: Tu sesión ha expirado, por favor vuelve a iniciar sesión -Access denied: Acción no permitida +Access Denied: Acción no permitida Direction not found: Dirección no encontrada diff --git a/front/core/services/token.js b/front/core/services/token.js index 426fe2b73..c8cb4f6bb 100644 --- a/front/core/services/token.js +++ b/front/core/services/token.js @@ -82,7 +82,7 @@ export default class Token { if (!data) return; this.renewPeriod = data.renewPeriod; this.stopRenewer(); - this.inservalId = setInterval(() => this.checkValidity(), data.renewInterval * 1000); + this.intervalId = setInterval(() => this.checkValidity(), data.renewInterval * 1000); }); } @@ -103,17 +103,13 @@ export default class Token { const token = res.data; this.set(token.id, now, token.ttl, this.remember); }) - .catch(res => { - if (res.data?.error?.code !== 'periodNotExceeded') - throw res; - }) .finally(() => { this.checking = false; }); } stopRenewer() { - clearInterval(this.inservalId); + clearInterval(this.intervalId); } } Token.$inject = ['vnInterceptor', '$http', '$rootScope']; diff --git a/front/salix/locale/es.yml b/front/salix/locale/es.yml index 8ed58a4e4..044d0d043 100644 --- a/front/salix/locale/es.yml +++ b/front/salix/locale/es.yml @@ -18,7 +18,7 @@ Show summary: Mostrar vista previa What is new: Novedades de la versión Settings: Ajustes There is a new version, click here to reload: Hay una nueva versión, pulse aquí para recargar -This ticket is locked.: Este ticket está bloqueado +This ticket is locked: Este ticket está bloqueado # Actions diff --git a/front/salix/module.js b/front/salix/module.js index 62d6cac98..0ce855308 100644 --- a/front/salix/module.js +++ b/front/salix/module.js @@ -120,7 +120,7 @@ function $exceptionHandler(vnApp, $window, $state, $injector) { messageT = 'Invalid login'; break; case 403: - messageT = exception.data?.error?.message || 'Access denied'; + messageT = exception.data?.error?.message || 'Access Denied'; break; case 502: messageT = 'It seems that the server has fall down'; diff --git a/loopback/common/mixins/loggable.js b/loopback/common/mixins/loggable.js new file mode 100644 index 000000000..760fdf60a --- /dev/null +++ b/loopback/common/mixins/loggable.js @@ -0,0 +1,12 @@ +const LoopBackContext = require('loopback-context'); +async function handleObserve(ctx) { + ctx.options.httpCtx = LoopBackContext.getCurrentContext(); +} +module.exports = function(Self) { + let Mixin = { + 'before save': handleObserve, + 'before delete': handleObserve, + }; + for (const [listener, handler] of Object.entries(Mixin)) + Self.observe(listener, handler); +}; diff --git a/loopback/common/models/loggable.js b/loopback/common/models/loggable.js deleted file mode 100644 index 360c84566..000000000 --- a/loopback/common/models/loggable.js +++ /dev/null @@ -1,15 +0,0 @@ -const LoopBackContext = require('loopback-context'); - -module.exports = function(Self) { - Self.setup = function() { - Self.super_.setup.call(this); - }; - - Self.observe('before save', async function(ctx) { - ctx.options.httpCtx = LoopBackContext.getCurrentContext(); - }); - - Self.observe('before delete', async function(ctx) { - ctx.options.httpCtx = LoopBackContext.getCurrentContext(); - }); -}; diff --git a/loopback/common/models/loggable.json b/loopback/common/models/loggable.json deleted file mode 100644 index 9101532a3..000000000 --- a/loopback/common/models/loggable.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "Loggable", - "base": "VnModel", - "validateUpsert": true -} diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 7d5b5ed47..2c7dc6be1 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -45,6 +45,7 @@ "Extension format is invalid": "Extension format is invalid", "NO_ZONE_FOR_THIS_PARAMETERS": "NO_ZONE_FOR_THIS_PARAMETERS", "This client can't be invoiced": "This client can't be invoiced", + "You must provide the correction information to generate a corrective invoice": "You must provide the correction information to generate a corrective invoice", "The introduced hour already exists": "The introduced hour already exists", "Invalid parameters to create a new ticket": "Invalid parameters to create a new ticket", "Concept cannot be blank": "Concept cannot be blank", @@ -178,12 +179,13 @@ "The renew period has not been exceeded": "The renew period has not been exceeded", "You can not use the same password": "You can not use the same password", "Valid priorities": "Valid priorities: %d", - "Negative basis of tickets": "Negative basis of tickets: {{ticketsIds}}", + "hasAnyNegativeBase": "Negative basis of tickets: {{ticketsIds}}", + "hasAnyPositiveBase": "Positive basis of tickets: {{ticketsIds}}", "This ticket cannot be left empty.": "This ticket cannot be left empty. %s", "Social name should be uppercase": "Social name should be uppercase", "Street should be uppercase": "Street should be uppercase", "You don't have enough privileges.": "You don't have enough privileges.", - "This ticket is locked.": "This ticket is locked.", + "This ticket is locked": "This ticket is locked", "This ticket is not editable.": "This ticket is not editable.", "The ticket doesn't exist.": "The ticket doesn't exist.", "The sales do not exists": "The sales do not exists", @@ -200,5 +202,6 @@ "Try again": "Try again", "keepPrice": "keepPrice", "Cannot past travels with entries": "Cannot past travels with entries", - "It was not able to remove the next expeditions:": "It was not able to remove the next expeditions: {{expeditions}}" -} \ No newline at end of file + "It was not able to remove the next expeditions:": "It was not able to remove the next expeditions: {{expeditions}}", + "Incorrect pin": "Incorrect pin." +} diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 01384efb4..25c76971d 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -72,6 +72,7 @@ "The secret can't be blank": "La contraseña no puede estar en blanco", "We weren't able to send this SMS": "No hemos podido enviar el SMS", "This client can't be invoiced": "Este cliente no puede ser facturado", + "You must provide the correction information to generate a corrective invoice": "Debes informar la información de corrección para generar una factura rectificativa", "This ticket can't be invoiced": "Este ticket no puede ser facturado", "You cannot add or modify services to an invoiced ticket": "No puedes añadir o modificar servicios a un ticket facturado", "This ticket can not be modified": "Este ticket no puede ser modificado", @@ -305,14 +306,15 @@ "Mail not sent": "Se ha producido un fallo al enviar la factura al cliente [{{clientId}}]({{{clientUrl}}}), por favor revisa la dirección de correo electrónico", "The renew period has not been exceeded": "El periodo de renovación no ha sido superado", "Valid priorities": "Prioridades válidas: %d", - "Negative basis of tickets": "Base negativa para los tickets: {{ticketsIds}}", + "hasAnyNegativeBase": "Base negativa para los tickets: {{ticketsIds}}", + "hasAnyPositiveBase": "Base positivas para los tickets: {{ticketsIds}}", "You cannot assign an alias that you are not assigned to": "No puede asignar un alias que no tenga asignado", "This ticket cannot be left empty.": "Este ticket no se puede dejar vacío. %s", "The company has not informed the supplier account for bank transfers": "La empresa no tiene informado la cuenta de proveedor para transferencias bancarias", "You cannot assign/remove an alias that you are not assigned to": "No puede asignar/eliminar un alias que no tenga asignado", "This invoice has a linked vehicle.": "Esta factura tiene un vehiculo vinculado", "You don't have enough privileges.": "No tienes suficientes permisos.", - "This ticket is locked.": "Este ticket está bloqueado.", + "This ticket is locked": "Este ticket está bloqueado.", "This ticket is not editable.": "Este ticket no es editable.", "The ticket doesn't exist.": "No existe el ticket.", "Social name should be uppercase": "La razón social debe ir en mayúscula", @@ -329,5 +331,10 @@ "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", "Cannot past travels with entries": "No se pueden pasar envíos con entradas", - "It was not able to remove the next expeditions:": "No se pudo eliminar las siguientes expediciones: {{expeditions}}" -} \ No newline at end of file + "It was not able to remove the next expeditions:": "No se pudo eliminar las siguientes expediciones: {{expeditions}}", + "This user does not have an assigned tablet": "Este usuario no tiene tablet asignada", + "Incorrect pin": "Pin incorrecto.", + "You already have the mailAlias": "Ya tienes este alias de correo", + "The alias cant be modified": "Este alias de correo no puede ser modificado", + "No tickets to invoice": "No hay tickets para facturar" +} diff --git a/loopback/server/boot/date.js b/loopback/server/boot/date.js index 810745562..d592dc416 100644 --- a/loopback/server/boot/date.js +++ b/loopback/server/boot/date.js @@ -1,6 +1,5 @@ module.exports = () => { - Date.vnUTC = () => { - const env = process.env.NODE_ENV; + Date.vnUTC = (env = process.env.NODE_ENV) => { if (!env || env === 'development') return new Date(Date.UTC(2001, 0, 1, 11)); diff --git a/loopback/server/middleware.json b/loopback/server/middleware.json index 31a2f113b..cfc693217 100644 --- a/loopback/server/middleware.json +++ b/loopback/server/middleware.json @@ -39,7 +39,7 @@ "./middleware/salix-version": {} }, "parse": { - "body-parser#json":{} + "body-parser#json":{} }, "routes": { "loopback#rest": { diff --git a/loopback/server/model-config.json b/loopback/server/model-config.json index 33ef3797d..56b5360e8 100644 --- a/loopback/server/model-config.json +++ b/loopback/server/model-config.json @@ -25,20 +25,19 @@ "FieldAcl": { "dataSource": "vn" }, - "Role": { - "dataSource": "vn", - "options": { - "mysql": { - "table": "salix.Role" - } - } - }, "RoleMapping": { "dataSource": "vn", "options": { "mysql": { "table": "salix.RoleMapping" } + }, + "relations": { + "role": { + "type": "belongsTo", + "model": "VnRole", + "foreignKey": "roleId" + } } }, "Schema": { diff --git a/modules/account/back/methods/account/specs/change-password.spec.js b/modules/account/back/methods/account/specs/change-password.spec.js index 2fa3010af..c79960212 100644 --- a/modules/account/back/methods/account/specs/change-password.spec.js +++ b/modules/account/back/methods/account/specs/change-password.spec.js @@ -2,7 +2,7 @@ const {models} = require('vn-loopback/server/server'); describe('account changePassword()', () => { const userId = 70; - const unauthCtx = { + const unAuthCtx = { req: { headers: {}, connection: { @@ -79,7 +79,7 @@ describe('account changePassword()', () => { passExpired: yesterday } , options); - await models.VnUser.signIn(unauthCtx, 'trainee', 'nightmare', options); + await models.VnUser.signIn(unAuthCtx, 'trainee', 'nightmare', options); } catch (e) { if (e.message != 'Pass expired') throw e; diff --git a/modules/account/back/model-config.json b/modules/account/back/model-config.json index b4bd6dbaf..0cd43d0ce 100644 --- a/modules/account/back/model-config.json +++ b/modules/account/back/model-config.json @@ -14,6 +14,9 @@ "MailAliasAccount": { "dataSource": "vn" }, + "MailAliasAcl": { + "dataSource": "vn" + }, "MailConfig": { "dataSource": "vn" }, diff --git a/modules/account/back/models/account.json b/modules/account/back/models/account.json index 3c22521cb..6c2784696 100644 --- a/modules/account/back/models/account.json +++ b/modules/account/back/models/account.json @@ -1,49 +1,49 @@ { - "name": "Account", - "base": "VnModel", - "options": { - "mysql": { - "table": "account.account" - } - }, - "properties": { - "id": { - "id": true - } - }, - "relations": { - "user": { - "type": "belongsTo", - "model": "VnUser", - "foreignKey": "id" - }, - "aliases": { - "type": "hasMany", - "model": "MailAliasAccount", - "foreignKey": "account" - } - }, - "acls": [ - { - "property": "login", - "accessType": "EXECUTE", - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "ALLOW" + "name": "Account", + "base": "VnModel", + "options": { + "mysql": { + "table": "account.account" + } + }, + "properties": { + "id": { + "id": true + } + }, + "relations": { + "user": { + "type": "belongsTo", + "model": "VnUser", + "foreignKey": "id" }, - { + "aliases": { + "type": "hasMany", + "model": "MailAliasAccount", + "foreignKey": "account" + } + }, + "acls": [ + { + "property": "login", + "accessType": "EXECUTE", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + }, + { "property": "logout", - "accessType": "EXECUTE", - "principalType": "ROLE", - "principalId": "$authenticated", - "permission": "ALLOW" - }, - { + "accessType": "EXECUTE", + "principalType": "ROLE", + "principalId": "$authenticated", + "permission": "ALLOW" + }, + { "property": "changePassword", - "accessType": "EXECUTE", - "principalType": "ROLE", - "principalId": "$everyone", - "permission": "ALLOW" - } - ] + "accessType": "EXECUTE", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + } + ] } diff --git a/modules/account/back/models/ldap-config.js b/modules/account/back/models/ldap-config.js index b557d243c..89f0add48 100644 --- a/modules/account/back/models/ldap-config.js +++ b/modules/account/back/models/ldap-config.js @@ -239,7 +239,7 @@ module.exports = Self => { // Prepare data - let roles = await $.Role.find({ + let roles = await $.VnRole.find({ fields: ['id', 'name', 'description'] }); let roleRoles = await $.RoleRole.find({ diff --git a/modules/account/back/models/mail-alias-account.js b/modules/account/back/models/mail-alias-account.js index 6f5213f24..5eb561408 100644 --- a/modules/account/back/models/mail-alias-account.js +++ b/modules/account/back/models/mail-alias-account.js @@ -2,54 +2,44 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { + Self.rewriteDbError(function(err) { + if (err.code === 'ER_DUP_ENTRY') + return new UserError(`You already have the mailAlias`); + return err; + }); + Self.observe('before save', async ctx => { const changes = ctx.currentInstance || ctx.instance; - await Self.hasGrant(ctx, changes.mailAlias); + await checkModifyPermission(ctx, changes.mailAlias); }); Self.observe('before delete', async ctx => { const mailAliasAccount = await Self.findById(ctx.where.id); - await Self.hasGrant(ctx, mailAliasAccount.mailAlias); + await checkModifyPermission(ctx, mailAliasAccount.mailAlias); }); - /** - * Checks if current user has - * grant to add/remove alias - * - * @param {Object} ctx - Request context - * @param {Interger} mailAlias - mailAlias id - * @return {Boolean} True for user with grant - */ - Self.hasGrant = async function(ctx, mailAlias) { + async function checkModifyPermission(ctx, mailAliasFk) { + const userId = ctx.options.accessToken.userId; const models = Self.app.models; - const accessToken = {req: {accessToken: ctx.options.accessToken}}; - const userId = accessToken.req.accessToken.userId; - const canEditAlias = await models.ACL.checkAccessAcl(accessToken, 'MailAliasAccount', 'canEditAlias', 'WRITE'); - if (canEditAlias) return true; + const roles = await models.RoleMapping.find({ + fields: ['roleId'], + where: {principalId: userId} + }); - const user = await models.VnUser.findById(userId, {fields: ['hasGrant']}); - if (!user.hasGrant) - throw new UserError(`You don't have grant privilege`); - - const account = await models.Account.findById(userId, { - fields: ['id'], - include: { - relation: 'aliases', - scope: { - fields: ['mailAlias'] - } + const availableMailAlias = await models.MailAliasAcl.findOne({ + fields: ['mailAliasFk'], + include: {relation: 'mailAlias'}, + where: { + roleFk: { + inq: roles.map(role => role.roleId), + }, + mailAliasFk } }); - const aliases = account.aliases().map(alias => alias.mailAlias); - - const hasAlias = aliases.includes(mailAlias); - if (!hasAlias) - throw new UserError(`You cannot assign/remove an alias that you are not assigned to`); - - return true; - }; + if (!availableMailAlias) throw new UserError('The alias cant be modified'); + } }; diff --git a/modules/account/back/models/mail-alias-acl.json b/modules/account/back/models/mail-alias-acl.json new file mode 100644 index 000000000..014b95d14 --- /dev/null +++ b/modules/account/back/models/mail-alias-acl.json @@ -0,0 +1,31 @@ +{ + "name": "MailAliasAcl", + "base": "VnModel", + "options": { + "mysql": { + "table": "account.mailAliasAcl" + } + }, + "properties": { + "mailAliasFk": { + "id": true, + "type": "number" + }, + "roleFk": { + "id": true, + "type": "number" + } + }, + "relations": { + "mailAlias": { + "type": "belongsTo", + "model": "MailAlias", + "foreignKey": "mailAliasFk" + }, + "role": { + "type": "belongsTo", + "model": "Role", + "foreignKey": "roleFk" + } + } +} diff --git a/modules/account/back/models/role-inherit.json b/modules/account/back/models/role-inherit.json index 4b69ffdc2..a89f47b77 100644 --- a/modules/account/back/models/role-inherit.json +++ b/modules/account/back/models/role-inherit.json @@ -15,12 +15,12 @@ "relations": { "owner": { "type": "belongsTo", - "model": "Role", + "model": "VnRole", "foreignKey": "role" }, "inherits": { "type": "belongsTo", - "model": "Role", + "model": "VnRole", "foreignKey": "inheritsFrom" } } diff --git a/modules/account/back/models/role-role.json b/modules/account/back/models/role-role.json index 77df7a920..e59351c59 100644 --- a/modules/account/back/models/role-role.json +++ b/modules/account/back/models/role-role.json @@ -14,12 +14,12 @@ "relations": { "owner": { "type": "belongsTo", - "model": "Role", + "model": "VnRole", "foreignKey": "role" }, "inherits": { "type": "belongsTo", - "model": "Role", + "model": "VnRole", "foreignKey": "inheritsFrom" } } diff --git a/modules/account/front/acl/create/index.html b/modules/account/front/acl/create/index.html index 7f4fa9e46..14332f737 100644 --- a/modules/account/front/acl/create/index.html +++ b/modules/account/front/acl/create/index.html @@ -15,7 +15,7 @@ @@ -32,7 +32,7 @@ diff --git a/modules/account/front/acl/search-panel/index.html b/modules/account/front/acl/search-panel/index.html index b83b9c255..a3efab440 100644 --- a/modules/account/front/acl/search-panel/index.html +++ b/modules/account/front/acl/search-panel/index.html @@ -4,7 +4,7 @@ - \ No newline at end of file + diff --git a/modules/account/front/create/index.html b/modules/account/front/create/index.html index acc07d346..70a518885 100644 --- a/modules/account/front/create/index.html +++ b/modules/account/front/create/index.html @@ -30,7 +30,7 @@ + url="VnRoles"> diff --git a/modules/account/front/role/basic-data/index.html b/modules/account/front/role/basic-data/index.html index 749927186..846f8b455 100644 --- a/modules/account/front/role/basic-data/index.html +++ b/modules/account/front/role/basic-data/index.html @@ -1,25 +1,27 @@
+ label="Description" + ng-model="$ctrl.role.description" + rule="VnRole.description" + > @@ -35,4 +37,4 @@ ng-click="watcher.loadOriginalData()"> -
\ No newline at end of file + diff --git a/modules/account/front/role/card/index.js b/modules/account/front/role/card/index.js index 6f888211d..3c7c758ef 100644 --- a/modules/account/front/role/card/index.js +++ b/modules/account/front/role/card/index.js @@ -3,7 +3,7 @@ import ModuleCard from 'salix/components/module-card'; class Controller extends ModuleCard { reload() { - this.$http.get(`Roles/${this.$params.id}`) + this.$http.get(`VnRoles/${this.$params.id}`) .then(res => this.role = res.data); } } diff --git a/modules/account/front/role/card/index.spec.js b/modules/account/front/role/card/index.spec.js index f39840e5f..f02c08f28 100644 --- a/modules/account/front/role/card/index.spec.js +++ b/modules/account/front/role/card/index.spec.js @@ -1,6 +1,6 @@ import './index'; -describe('component vnRoleCard', () => { +fdescribe('component vnRoleCard', () => { let controller; let $httpBackend; @@ -15,7 +15,7 @@ describe('component vnRoleCard', () => { it('should reload the controller data', () => { controller.$params.id = 1; - $httpBackend.expectGET('Roles/1').respond('foo'); + $httpBackend.expectGET('VnRoles/1').respond('foo'); controller.reload(); $httpBackend.flush(); diff --git a/modules/account/front/role/create/index.html b/modules/account/front/role/create/index.html index 02900d580..77d6fc2c1 100644 --- a/modules/account/front/role/create/index.html +++ b/modules/account/front/role/create/index.html @@ -1,6 +1,6 @@ @@ -12,15 +12,15 @@ + rule="VnRole.description"> diff --git a/modules/account/front/role/descriptor/index.html b/modules/account/front/role/descriptor/index.html index 4cd4ac822..d8bf4857a 100644 --- a/modules/account/front/role/descriptor/index.html +++ b/modules/account/front/role/descriptor/index.html @@ -24,4 +24,4 @@ on-accept="$ctrl.onDelete()" question="Are you sure you want to continue?" message="Role will be removed"> - \ No newline at end of file + diff --git a/modules/account/front/role/descriptor/index.js b/modules/account/front/role/descriptor/index.js index a1b578133..17b585cb7 100644 --- a/modules/account/front/role/descriptor/index.js +++ b/modules/account/front/role/descriptor/index.js @@ -11,7 +11,7 @@ class Controller extends Descriptor { } onDelete() { - return this.$http.delete(`Roles/${this.id}`) + return this.$http.delete(`VnRoles/${this.id}`) .then(() => this.$state.go('account.role')) .then(() => this.vnApp.showSuccess(this.$t('Role removed'))); } diff --git a/modules/account/front/role/descriptor/index.spec.js b/modules/account/front/role/descriptor/index.spec.js index e2761c639..eafb96727 100644 --- a/modules/account/front/role/descriptor/index.spec.js +++ b/modules/account/front/role/descriptor/index.spec.js @@ -1,6 +1,6 @@ import './index'; -describe('component vnRoleDescriptor', () => { +fdescribe('component vnRoleDescriptor', () => { let controller; let $httpBackend; @@ -18,7 +18,7 @@ describe('component vnRoleDescriptor', () => { controller.$state.go = jest.fn(); jest.spyOn(controller.vnApp, 'showSuccess'); - $httpBackend.expectDELETE('Roles/1').respond(); + $httpBackend.expectDELETE('VnRoles/1').respond(); controller.onDelete(); $httpBackend.flush(); diff --git a/modules/account/front/role/main/index.html b/modules/account/front/role/main/index.html index 9d7e6e053..cfef28e57 100644 --- a/modules/account/front/role/main/index.html +++ b/modules/account/front/role/main/index.html @@ -1,6 +1,6 @@ @@ -15,4 +15,4 @@ - \ No newline at end of file + diff --git a/modules/account/front/role/subroles/index.html b/modules/account/front/role/subroles/index.html index bc554b9f9..eba1002b0 100644 --- a/modules/account/front/role/subroles/index.html +++ b/modules/account/front/role/subroles/index.html @@ -33,14 +33,14 @@ ng-click="$ctrl.onAddClick()" fixed-bottom-right> - @@ -49,7 +49,7 @@ - this.$.summary = res.data); } diff --git a/modules/account/front/search-panel/index.html b/modules/account/front/search-panel/index.html index f80b537aa..a539d9657 100644 --- a/modules/account/front/search-panel/index.html +++ b/modules/account/front/search-panel/index.html @@ -19,7 +19,7 @@ vn-one label="Role" ng-model="filter.roleFk" - url="Roles" + url="VnRoles" value-field="id" show-field="name">
@@ -28,4 +28,4 @@ - \ No newline at end of file + diff --git a/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js b/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js index be3baccd7..a01590f58 100644 --- a/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js +++ b/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js @@ -123,7 +123,7 @@ module.exports = Self => { await models.TicketTracking.create({ ticketFk: newRefundTicket.id, stateFk: state.id, - workerFk: worker.id + userFk: worker.id }, myOptions); const salesToRefund = await models.ClaimBeginning.find(salesFilter, myOptions); diff --git a/modules/claim/back/methods/claim-dms/removeFile.js b/modules/claim/back/methods/claim-dms/removeFile.js index edc714235..28e78c9d7 100644 --- a/modules/claim/back/methods/claim-dms/removeFile.js +++ b/modules/claim/back/methods/claim-dms/removeFile.js @@ -1,3 +1,5 @@ +const UserError = require('vn-loopback/util/user-error'); + module.exports = Self => { Self.remoteMethodCtx('removeFile', { description: 'Removes a claim document', @@ -19,8 +21,8 @@ module.exports = Self => { }); Self.removeFile = async(ctx, id, options) => { - let tx; const myOptions = {}; + let tx; if (typeof options == 'object') Object.assign(myOptions, options); @@ -31,19 +33,18 @@ module.exports = Self => { } try { - const models = Self.app.models; - const targetClaimDms = await models.ClaimDms.findById(id, null, myOptions); - const targetDms = await models.Dms.findById(targetClaimDms.dmsFk, null, myOptions); - const trashDmsType = await models.DmsType.findOne({where: {code: 'trash'}}, myOptions); + const claimDms = await Self.findById(id, null, myOptions); - await models.Dms.removeFile(ctx, targetClaimDms.dmsFk, myOptions); - await targetClaimDms.destroy(myOptions); + const targetDms = await Self.app.models.Dms.removeFile(ctx, claimDms.dmsFk, myOptions); - await targetDms.updateAttribute('dmsTypeFk', trashDmsType.id, myOptions); + if (!targetDms || ! claimDms) + throw new UserError('Try again'); + + const claimDmsDestroyed = await claimDms.destroy(myOptions); if (tx) await tx.commit(); - return targetDms; + return claimDmsDestroyed; } catch (e) { if (tx) await tx.rollback(); throw e; diff --git a/modules/claim/back/methods/claim/uploadFile.js b/modules/claim/back/methods/claim/uploadFile.js index 3d0737cf8..4fd041456 100644 --- a/modules/claim/back/methods/claim/uploadFile.js +++ b/modules/claim/back/methods/claim/uploadFile.js @@ -1,6 +1,3 @@ -const UserError = require('vn-loopback/util/user-error'); -const fs = require('fs-extra'); -const path = require('path'); module.exports = Self => { Self.remoteMethodCtx('uploadFile', { @@ -57,96 +54,33 @@ module.exports = Self => { }); Self.uploadFile = async(ctx, id, options) => { - const tx = await Self.beginTransaction({}); + const {Dms, ClaimDms} = Self.app.models; const myOptions = {}; + let tx; if (typeof options == 'object') Object.assign(myOptions, options); - if (!myOptions.transaction) + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); myOptions.transaction = tx; + } - const models = Self.app.models; - const promises = []; - const TempContainer = models.TempContainer; - const ClaimContainer = models.ClaimContainer; - const fileOptions = {}; - const args = ctx.args; - - let srcFile; try { - const hasWriteRole = await models.DmsType.hasWriteRole(ctx, args.dmsTypeId, myOptions); - if (!hasWriteRole) - throw new UserError(`You don't have enough privileges`); + const uploadedFiles = await Dms.uploadFile(ctx, myOptions); - // Upload file to temporary path - const tempContainer = await TempContainer.container('dms'); - const uploaded = await TempContainer.upload(tempContainer.name, ctx.req, ctx.result, fileOptions); - const files = Object.values(uploaded.files).map(file => { - return file[0]; - }); - - const addedDms = []; - for (const uploadedFile of files) { - const newDms = await createDms(ctx, uploadedFile, myOptions); - const pathHash = ClaimContainer.getHash(newDms.id); - - const file = await TempContainer.getFile(tempContainer.name, uploadedFile.name); - srcFile = path.join(file.client.root, file.container, file.name); - - const claimContainer = await ClaimContainer.container(pathHash); - const dstFile = path.join(claimContainer.client.root, pathHash, newDms.file); - - await fs.move(srcFile, dstFile, { - overwrite: true - }); - - addedDms.push(newDms); - } - - addedDms.forEach(dms => { - const newClaimDms = models.ClaimDms.create({ - claimFk: id, - dmsFk: dms.id - }, myOptions); - - promises.push(newClaimDms); - }); - const resolvedPromises = await Promise.all(promises); + const promises = uploadedFiles.map(dms => ClaimDms.create({ + claimFk: id, + dmsFk: dms.id + }, myOptions)); + await Promise.all(promises); if (tx) await tx.commit(); - return resolvedPromises; + return uploadedFiles; } catch (e) { if (tx) await tx.rollback(); - - if (fs.existsSync(srcFile)) - await fs.unlink(srcFile); - throw e; } }; - - async function createDms(ctx, file, myOptions) { - const models = Self.app.models; - const myUserId = ctx.req.accessToken.userId; - const args = ctx.args; - - const newDms = await models.Dms.create({ - workerFk: myUserId, - dmsTypeFk: args.dmsTypeId, - companyFk: args.companyId, - warehouseFk: args.warehouseId, - reference: args.reference, - description: args.description, - contentType: file.type, - hasFile: args.hasFile - }, myOptions); - - let fileName = file.name; - const extension = models.DmsContainer.getFileExtension(fileName); - fileName = `${newDms.id}.${extension}`; - - return newDms.updateAttribute('file', fileName, myOptions); - } }; diff --git a/modules/claim/back/models/claim-beginning.json b/modules/claim/back/models/claim-beginning.json index d355881e8..d224586da 100644 --- a/modules/claim/back/models/claim-beginning.json +++ b/modules/claim/back/models/claim-beginning.json @@ -1,6 +1,9 @@ { "name": "ClaimBeginning", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "claimBeginning" diff --git a/modules/claim/back/models/claim-development.json b/modules/claim/back/models/claim-development.json index b0f352f50..732955660 100644 --- a/modules/claim/back/models/claim-development.json +++ b/modules/claim/back/models/claim-development.json @@ -1,6 +1,9 @@ { "name": "ClaimDevelopment", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "claimDevelopment" diff --git a/modules/claim/back/models/claim-dms.json b/modules/claim/back/models/claim-dms.json index 26c90fd69..ed12c925b 100644 --- a/modules/claim/back/models/claim-dms.json +++ b/modules/claim/back/models/claim-dms.json @@ -1,6 +1,9 @@ { "name": "ClaimDms", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "claimDms" diff --git a/modules/claim/back/models/claim-end.json b/modules/claim/back/models/claim-end.json index 9f12ff93a..ef5477f50 100644 --- a/modules/claim/back/models/claim-end.json +++ b/modules/claim/back/models/claim-end.json @@ -1,6 +1,9 @@ { "name": "ClaimEnd", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "claimEnd" diff --git a/modules/claim/back/models/claim-observation.json b/modules/claim/back/models/claim-observation.json index 2d418b76e..1e4cb6a0f 100644 --- a/modules/claim/back/models/claim-observation.json +++ b/modules/claim/back/models/claim-observation.json @@ -1,6 +1,9 @@ { "name": "ClaimObservation", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "claimObservation" diff --git a/modules/claim/back/models/claim-state.json b/modules/claim/back/models/claim-state.json index f5bde4168..c50fdebdf 100644 --- a/modules/claim/back/models/claim-state.json +++ b/modules/claim/back/models/claim-state.json @@ -1,6 +1,9 @@ { "name": "ClaimState", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "claimState" @@ -32,7 +35,7 @@ "relations": { "writeRole": { "type": "belongsTo", - "model": "Role", + "model": "VnRole", "foreignKey": "roleFk" } }, diff --git a/modules/claim/back/models/claim.json b/modules/claim/back/models/claim.json index a7db1f3e1..b85b9e073 100644 --- a/modules/claim/back/models/claim.json +++ b/modules/claim/back/models/claim.json @@ -1,6 +1,9 @@ { "name": "Claim", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "claim" diff --git a/modules/client/back/methods/client-dms/removeFile.js b/modules/client/back/methods/client-dms/removeFile.js index 786a32928..bc9a0f719 100644 --- a/modules/client/back/methods/client-dms/removeFile.js +++ b/modules/client/back/methods/client-dms/removeFile.js @@ -19,9 +19,8 @@ module.exports = Self => { }); Self.removeFile = async(ctx, id, options) => { - const models = Self.app.models; - let tx; const myOptions = {}; + let tx; if (typeof options == 'object') Object.assign(myOptions, options); @@ -34,13 +33,16 @@ module.exports = Self => { try { const clientDms = await Self.findById(id, null, myOptions); - await models.Dms.removeFile(ctx, clientDms.dmsFk, myOptions); + const targetDms = await Self.app.models.Dms.removeFile(ctx, clientDms.dmsFk, myOptions); - const destroyedClient = await clientDms.destroy(myOptions); + if (!targetDms || !clientDms) + throw new UserError('Try again'); + + const clientDmsDestroyed = await clientDms.destroy(myOptions); if (tx) await tx.commit(); - return destroyedClient; + return clientDmsDestroyed; } catch (e) { if (tx) await tx.rollback(); throw e; diff --git a/modules/client/back/methods/client/canBeInvoiced.js b/modules/client/back/methods/client/canBeInvoiced.js index 843e9549f..cdb865500 100644 --- a/modules/client/back/methods/client/canBeInvoiced.js +++ b/modules/client/back/methods/client/canBeInvoiced.js @@ -1,7 +1,7 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = function(Self) { - Self.remoteMethodCtx('canBeInvoiced', { + Self.remoteMethod('canBeInvoiced', { description: 'Change property isEqualizated in all client addresses', accessType: 'READ', accepts: [ diff --git a/modules/client/back/methods/client/filter.js b/modules/client/back/methods/client/filter.js index 47d5f6d2f..f805c4be9 100644 --- a/modules/client/back/methods/client/filter.js +++ b/modules/client/back/methods/client/filter.js @@ -107,17 +107,29 @@ module.exports = Self => { return {or: [ {'c.phone': {like: `%${value}%`}}, {'c.mobile': {like: `%${value}%`}}, + {'a.phone': {like: `%${value}%`}}, ]}; case 'zoneFk': - param = 'a.postalCode'; - return {[param]: {inq: postalCode}}; + return {'a.postalCode': {inq: postalCode}}; + case 'city': + return {or: [ + {'c.city': {like: `%${value}%`}}, + {'a.city': {like: `%${value}%`}} + ]}; + case 'postcode': + return {or: [ + {'c.postcode': value}, + {'a.postalCode': value} + ]}; + case 'provinceFk': + return {or: [ + {'p.id': value}, + {'a.provinceFk': value} + ]}; case 'name': case 'salesPersonFk': case 'fi': case 'socialName': - case 'city': - case 'postcode': - case 'provinceFk': case 'email': param = `c.${param}`; return {[param]: {like: `%${value}%`}}; @@ -134,24 +146,29 @@ module.exports = Self => { c.fi, c.socialName, c.phone, + a.phone, c.mobile, c.city, + a.city, c.postcode, + a.postalCode, c.email, c.isActive, c.isFreezed, - p.id AS provinceFk, + p.id AS provinceClientFk, + a.provinceFk AS provinceAddressFk, p.name AS province, u.id AS salesPersonFk, u.name AS salesPerson FROM client c LEFT JOIN account.user u ON u.id = c.salesPersonFk LEFT JOIN province p ON p.id = c.provinceFk - JOIN vn.address a ON a.clientFk = c.id + JOIN address a ON a.clientFk = c.id ` ); stmt.merge(conn.makeWhere(filter.where)); + stmt.merge('GROUP BY c.id'); stmt.merge(conn.makePagination(filter)); const clientsIndex = stmts.push(stmt) - 1; diff --git a/modules/client/back/methods/client/specs/consumption.spec.js b/modules/client/back/methods/client/specs/consumption.spec.js index 47a495d79..85dbb7422 100644 --- a/modules/client/back/methods/client/specs/consumption.spec.js +++ b/modules/client/back/methods/client/specs/consumption.spec.js @@ -16,7 +16,7 @@ describe('client consumption() filter', () => { }; const result = await models.Client.consumption(ctx, filter, options); - expect(result.length).toEqual(10); + expect(result.length).toEqual(11); await tx.rollback(); } catch (e) { @@ -49,7 +49,7 @@ describe('client consumption() filter', () => { const thirdRow = result[2]; expect(result.length).toEqual(3); - expect(firstRow.quantity).toEqual(10); + expect(firstRow.quantity).toEqual(11); expect(secondRow.quantity).toEqual(15); expect(thirdRow.quantity).toEqual(20); diff --git a/modules/client/back/methods/client/uploadFile.js b/modules/client/back/methods/client/uploadFile.js index 99ede27c6..1a5965955 100644 --- a/modules/client/back/methods/client/uploadFile.js +++ b/modules/client/back/methods/client/uploadFile.js @@ -55,9 +55,9 @@ module.exports = Self => { }); Self.uploadFile = async(ctx, id, options) => { - const models = Self.app.models; - let tx; + const {Dms, ClientDms} = Self.app.models; const myOptions = {}; + let tx; if (typeof options == 'object') Object.assign(myOptions, options); @@ -67,23 +67,20 @@ module.exports = Self => { myOptions.transaction = tx; } - const promises = []; - try { - const uploadedFiles = await models.Dms.uploadFile(ctx, myOptions); - uploadedFiles.forEach(dms => { - const newClientDms = models.ClientDms.create({ + const uploadedFiles = await Dms.uploadFile(ctx, myOptions); + const promises = uploadedFiles.map(dms => + ClientDms.create({ clientFk: id, dmsFk: dms.id - }, myOptions); + }, myOptions) - promises.push(newClientDms); - }); - const resolvedPromises = await Promise.all(promises); + ); + await Promise.all(promises); if (tx) await tx.commit(); - return resolvedPromises; + return uploadedFiles; } catch (e) { if (tx) await tx.rollback(); throw e; diff --git a/modules/client/back/model-config.json b/modules/client/back/model-config.json index 0cc5df9a2..fc1254dd8 100644 --- a/modules/client/back/model-config.json +++ b/modules/client/back/model-config.json @@ -29,7 +29,7 @@ "ClientCredit": { "dataSource": "vn" }, - "ClientCreditLimit": { + "RoleCreditLimit": { "dataSource": "vn" }, "ClientConsumptionQueue": { @@ -113,9 +113,6 @@ "SageTransactionType": { "dataSource": "vn" }, - "TicketSms": { - "dataSource": "vn" - }, "TpvError": { "dataSource": "vn" }, diff --git a/modules/client/back/models/address.json b/modules/client/back/models/address.json index 5f962677d..e8bf8d8a0 100644 --- a/modules/client/back/models/address.json +++ b/modules/client/back/models/address.json @@ -1,7 +1,10 @@ { "name": "Address", "description": "Client addresses", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "address" diff --git a/modules/client/back/models/client-contact.json b/modules/client/back/models/client-contact.json index 3f71ab79e..55cc9d436 100644 --- a/modules/client/back/models/client-contact.json +++ b/modules/client/back/models/client-contact.json @@ -1,7 +1,10 @@ { "name": "ClientContact", "description": "Client phone contacts", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "clientContact" diff --git a/modules/client/back/models/client-dms.json b/modules/client/back/models/client-dms.json index 14b19498e..6dbcd0140 100644 --- a/modules/client/back/models/client-dms.json +++ b/modules/client/back/models/client-dms.json @@ -1,6 +1,9 @@ { "name": "ClientDms", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "clientDms" diff --git a/modules/client/back/models/client-informa.json b/modules/client/back/models/client-informa.json index 0c652484e..5e536faff 100644 --- a/modules/client/back/models/client-informa.json +++ b/modules/client/back/models/client-informa.json @@ -1,6 +1,9 @@ { "name": "ClientInforma", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "log": { "model":"ClientLog", "relation": "client", diff --git a/modules/client/back/models/client-observation.json b/modules/client/back/models/client-observation.json index 95d00d374..b204ebeb4 100644 --- a/modules/client/back/models/client-observation.json +++ b/modules/client/back/models/client-observation.json @@ -1,7 +1,10 @@ { "name": "ClientObservation", "description": "Client notes", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "clientObservation" diff --git a/modules/client/back/models/client-sample.json b/modules/client/back/models/client-sample.json index a32f308ab..4cd55d9df 100644 --- a/modules/client/back/models/client-sample.json +++ b/modules/client/back/models/client-sample.json @@ -1,6 +1,9 @@ { "name": "ClientSample", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "clientSample" diff --git a/modules/client/back/models/client-sms.json b/modules/client/back/models/client-sms.json index b2244ebbb..df1b58737 100644 --- a/modules/client/back/models/client-sms.json +++ b/modules/client/back/models/client-sms.json @@ -21,6 +21,11 @@ "type": "belongsTo", "model": "Sms", "foreignKey": "smsFk" + }, + "ticket": { + "type": "belongsTo", + "model": "Ticket", + "foreignKey": "ticketFk" } } } diff --git a/modules/client/back/models/client.js b/modules/client/back/models/client.js index 72b702779..a9e14effa 100644 --- a/modules/client/back/models/client.js +++ b/modules/client/back/models/client.js @@ -463,7 +463,7 @@ module.exports = Self => { throw new UserError(`You can't change the credit set to zero from a financialBoss`); } - const creditLimits = await models.ClientCreditLimit.find({ + const creditLimits = await models.RoleCreditLimit.find({ fields: ['roleFk'], where: { maxAmount: {gte: changes.credit} diff --git a/modules/client/back/models/client.json b/modules/client/back/models/client.json index f32915bb5..bfde05162 100644 --- a/modules/client/back/models/client.json +++ b/modules/client/back/models/client.json @@ -1,6 +1,9 @@ { "name": "Client", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "client" diff --git a/modules/client/back/models/greuge.json b/modules/client/back/models/greuge.json index 884cbd34e..f57744f8a 100644 --- a/modules/client/back/models/greuge.json +++ b/modules/client/back/models/greuge.json @@ -1,6 +1,9 @@ { "name": "Greuge", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "greuge" diff --git a/modules/client/back/models/recovery.json b/modules/client/back/models/recovery.json index 5ea89197d..89ec54494 100644 --- a/modules/client/back/models/recovery.json +++ b/modules/client/back/models/recovery.json @@ -1,6 +1,9 @@ { "name": "Recovery", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "recovery" diff --git a/modules/client/back/models/client-credit-limit.json b/modules/client/back/models/role-credit-limit.json similarity index 79% rename from modules/client/back/models/client-credit-limit.json rename to modules/client/back/models/role-credit-limit.json index 740f0cf53..e36180dfc 100644 --- a/modules/client/back/models/client-credit-limit.json +++ b/modules/client/back/models/role-credit-limit.json @@ -1,9 +1,9 @@ { - "name": "ClientCreditLimit", + "name": "RoleCreditLimit", "base": "VnModel", "options": { "mysql": { - "table": "clientCreditLimit" + "table": "roleCreditLimit" } }, "properties": { @@ -19,8 +19,8 @@ "relations": { "role": { "type": "belongsTo", - "model": "Role", + "model": "VnRole", "foreignKey": "roleFk" } } -} \ No newline at end of file +} diff --git a/modules/client/back/models/ticket-sms.json b/modules/client/back/models/ticket-sms.json deleted file mode 100644 index 03f592f51..000000000 --- a/modules/client/back/models/ticket-sms.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "TicketSms", - "base": "VnModel", - "options": { - "mysql": { - "table": "ticketSms" - } - }, - "properties": { - "smsFk": { - "type": "number", - "id": true, - "description": "Identifier" - } - }, - "relations": { - "ticket": { - "type": "belongsTo", - "model": "Ticket", - "foreignKey": "ticketFk" - }, - "sms": { - "type": "belongsTo", - "model": "Sms", - "foreignKey": "smsFk" - } - } -} diff --git a/modules/client/front/basic-data/index.html b/modules/client/front/basic-data/index.html index e48b39fdc..acab99d91 100644 --- a/modules/client/front/basic-data/index.html +++ b/modules/client/front/basic-data/index.html @@ -62,7 +62,7 @@ diff --git a/modules/client/front/sms/index.html b/modules/client/front/sms/index.html index e2bc0785e..7fb3b870e 100644 --- a/modules/client/front/sms/index.html +++ b/modules/client/front/sms/index.html @@ -1,40 +1,2 @@ - - - - - - - - Sender - Destination - Message - Status - Created - - - - - - - {{::clientSms.sms.sender.name}} - - - {{::clientSms.sms.destination}} - {{::clientSms.sms.message}} - {{::clientSms.sms.status}} - {{::clientSms.sms.created | date:'dd/MM/yyyy HH:mm'}} - - - - - - - + + diff --git a/modules/client/front/sms/index.js b/modules/client/front/sms/index.js index 6ad64282e..8fa130248 100644 --- a/modules/client/front/sms/index.js +++ b/modules/client/front/sms/index.js @@ -1,32 +1,14 @@ import ngModule from '../module'; import Section from 'salix/components/section'; -export default class Controller extends Section { +class Controller extends Section { constructor($element, $) { super($element, $); + } - this.filter = { - fields: ['id', 'smsFk'], - include: { - relation: 'sms', - scope: { - fields: [ - 'senderFk', - 'sender', - 'destination', - 'message', - 'statusCode', - 'status', - 'created'], - include: { - relation: 'sender', - scope: { - fields: ['name'] - } - } - } - } - }; + async $onInit() { + this.$state.go('client.card.summary', {id: this.$params.id}); + window.location.href = await this.vnApp.getUrl(`Customer/${this.$params.id}/sms`); } } diff --git a/modules/entry/back/models/buy.json b/modules/entry/back/models/buy.json index 30379eaf6..fa804f4d8 100644 --- a/modules/entry/back/models/buy.json +++ b/modules/entry/back/models/buy.json @@ -1,6 +1,9 @@ { "name": "Buy", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "buy" diff --git a/modules/entry/back/models/entry-observation.json b/modules/entry/back/models/entry-observation.json index cdf0c5e6e..6a1592037 100644 --- a/modules/entry/back/models/entry-observation.json +++ b/modules/entry/back/models/entry-observation.json @@ -1,6 +1,9 @@ { "name": "EntryObservation", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "entryObservation" diff --git a/modules/entry/back/models/entry.json b/modules/entry/back/models/entry.json index a7508b4e8..0f3e389b6 100644 --- a/modules/entry/back/models/entry.json +++ b/modules/entry/back/models/entry.json @@ -1,6 +1,9 @@ { "name": "Entry", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "entry" diff --git a/modules/invoiceIn/back/models/invoice-in-tax.json b/modules/invoiceIn/back/models/invoice-in-tax.json index 5bfbbe2a8..53b5548b6 100644 --- a/modules/invoiceIn/back/models/invoice-in-tax.json +++ b/modules/invoiceIn/back/models/invoice-in-tax.json @@ -1,6 +1,9 @@ { "name": "InvoiceInTax", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "invoiceInTax" diff --git a/modules/invoiceIn/back/models/invoice-in.json b/modules/invoiceIn/back/models/invoice-in.json index 5be55c851..59c179e76 100644 --- a/modules/invoiceIn/back/models/invoice-in.json +++ b/modules/invoiceIn/back/models/invoice-in.json @@ -1,6 +1,9 @@ { "name": "InvoiceIn", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "invoiceIn" diff --git a/modules/invoiceOut/back/methods/invoiceOut/createManualInvoice.js b/modules/invoiceOut/back/methods/invoiceOut/createManualInvoice.js index 18e6903d6..043dfbead 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/createManualInvoice.js +++ b/modules/invoiceOut/back/methods/invoiceOut/createManualInvoice.js @@ -85,7 +85,7 @@ module.exports = Self => { throw new UserError(`A ticket with an amount of zero can't be invoiced`); // Validates ticket nagative base - const hasNegativeBase = await getNegativeBase(ticketId, myOptions); + const hasNegativeBase = await getNegativeBase(maxShipped, clientId, companyId, myOptions); if (hasNegativeBase && company.code == 'VNL') throw new UserError(`A ticket with a negative base can't be invoiced`); } else { @@ -162,10 +162,13 @@ module.exports = Self => { return result.invoiceable; } - async function getNegativeBase(ticketId, options) { + async function getNegativeBase(maxShipped, clientId, companyId, options) { const models = Self.app.models; - const query = 'SELECT vn.hasSomeNegativeBase(?) AS base'; - const [result] = await models.InvoiceOut.rawSql(query, [ticketId], options); + await models.InvoiceOut.rawSql('CALL invoiceOut_exportationFromClient(?,?,?)', + [maxShipped, clientId, companyId], options + ); + const query = 'SELECT vn.hasAnyNegativeBase() AS base'; + const [result] = await models.InvoiceOut.rawSql(query, [], options); return result.base; } diff --git a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js index fa22dab1e..530b02353 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js +++ b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js @@ -80,6 +80,7 @@ module.exports = Self => { invoiceType, args.companyFk, args.invoiceDate, + null, options ); diff --git a/modules/invoiceOut/back/methods/invoiceOut/negativeBases.js b/modules/invoiceOut/back/methods/invoiceOut/negativeBases.js index ae9c404af..96c789316 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/negativeBases.js +++ b/modules/invoiceOut/back/methods/invoiceOut/negativeBases.js @@ -90,7 +90,7 @@ module.exports = Self => { AND t.refFk IS NULL AND c.typeFk IN ('normal','trust') GROUP BY t.clientFk, negativeBase.taxableBase - HAVING amount <> 0`, [args.from, args.to])); + HAVING amount < 0`, [args.from, args.to])); stmt = new ParameterizedSQL(` SELECT f.* diff --git a/modules/invoiceOut/back/methods/invoiceOut/negativeBasesCsv.js b/modules/invoiceOut/back/methods/invoiceOut/negativeBasesCsv.js index d70a8fce5..87e9a67ea 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/negativeBasesCsv.js +++ b/modules/invoiceOut/back/methods/invoiceOut/negativeBasesCsv.js @@ -10,13 +10,17 @@ module.exports = Self => { type: 'date', description: 'From date', required: true - }, - { + }, { arg: 'to', type: 'date', description: 'To date', required: true - }], + }, { + arg: 'filter', + type: 'object', + description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string' + }, + ], returns: [ { arg: 'body', diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/transferinvoice.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/transferinvoice.spec.js index 800a4ea83..11575999a 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/transferinvoice.spec.js +++ b/modules/invoiceOut/back/methods/invoiceOut/specs/transferinvoice.spec.js @@ -2,7 +2,7 @@ const models = require('vn-loopback/server/server').models; const LoopBackContext = require('loopback-context'); -describe('InvoiceOut tranferInvoice()', () => { +describe('InvoiceOut transferInvoice()', () => { const activeCtx = { accessToken: {userId: 5}, http: { @@ -23,20 +23,29 @@ describe('InvoiceOut tranferInvoice()', () => { const tx = await models.InvoiceOut.beginTransaction({}); const options = {transaction: tx}; const args = { - id: '1', - ref: 'T4444444', + id: '4', + refFk: 'T4444444', newClientFk: 1, - cplusRectificationId: 1, - siiTypeInvoiceOutId: 1, - invoiceCorrectionTypeId: 1 + cplusRectificationTypeFk: 1, + siiTypeInvoiceOutFk: 1, + invoiceCorrectionTypeFk: 1 }; ctx.args = args; try { + const {clientFk: oldClient} = await models.InvoiceOut.findById(args.id, {fields: ['clientFk']}); + const invoicesBefore = await models.InvoiceOut.find({}, options); const result = await models.InvoiceOut.transferInvoice( ctx, options); + const invoicesAfter = await models.InvoiceOut.find({}, options); + const rectificativeInvoice = invoicesAfter[invoicesAfter.length - 2]; + const newInvoice = invoicesAfter[invoicesAfter.length - 1]; expect(result).toBeDefined(); + expect(invoicesAfter.length - invoicesBefore.length).toEqual(2); + expect(rectificativeInvoice.clientFk).toEqual(oldClient); + expect(newInvoice.clientFk).toEqual(args.newClientFk); + await tx.rollback(); } catch (e) { await tx.rollback(); @@ -49,20 +58,44 @@ describe('InvoiceOut tranferInvoice()', () => { const options = {transaction: tx}; const args = { id: '1', - ref: 'T1111111', + refFk: 'T1111111', newClientFk: 1101, - cplusRectificationId: 1, - siiTypeInvoiceOutId: 1, - invoiceCorrectionTypeId: 1 + cplusRectificationTypeFk: 1, + siiTypeInvoiceOutFk: 1, + invoiceCorrectionTypeFk: 1 }; ctx.args = args; try { await models.InvoiceOut.transferInvoice( ctx, options); + await tx.rollback(); } catch (e) { expect(e.message).toBe(`Select a different client`); await tx.rollback(); } }); + + it('should throw an UserError when it is refund', async() => { + const tx = await models.InvoiceOut.beginTransaction({}); + const options = {transaction: tx}; + const args = { + id: '1', + refFk: 'T1111111', + newClientFk: 1102, + cplusRectificationTypeFk: 1, + siiTypeInvoiceOutFk: 1, + invoiceCorrectionTypeFk: 1 + }; + ctx.args = args; + try { + await models.InvoiceOut.transferInvoice( + ctx, + options); + await tx.rollback(); + } catch (e) { + expect(e.message).toContain(`This ticket is already a refund`); + await tx.rollback(); + } + }); }); diff --git a/modules/invoiceOut/back/methods/invoiceOut/transferInvoice.js b/modules/invoiceOut/back/methods/invoiceOut/transferInvoice.js index dde535c99..5f2428539 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/transferInvoice.js +++ b/modules/invoiceOut/back/methods/invoiceOut/transferInvoice.js @@ -12,7 +12,7 @@ module.exports = Self => { description: 'Issued invoice id' }, { - arg: 'ref', + arg: 'refFk', type: 'string', required: true }, @@ -22,17 +22,17 @@ module.exports = Self => { required: true }, { - arg: 'cplusRectificationId', + arg: 'cplusRectificationTypeFk', type: 'number', required: true }, { - arg: 'siiTypeInvoiceOutId', + arg: 'siiTypeInvoiceOutFk', type: 'number', required: true }, { - arg: 'invoiceCorrectionTypeId', + arg: 'invoiceCorrectionTypeFk', type: 'number', required: true }, @@ -50,14 +50,14 @@ module.exports = Self => { Self.transferInvoice = async(ctx, options) => { const models = Self.app.models; const myOptions = {userId: ctx.req.accessToken.userId}; - const args = ctx.args; + const {id, refFk, newClientFk, cplusRectificationTypeFk, siiTypeInvoiceOutFk, invoiceCorrectionTypeFk} = ctx.args; let tx; if (typeof options == 'object') Object.assign(myOptions, options); - const {clientFk} = await models.InvoiceOut.findById(args.id); + const {clientFk} = await models.InvoiceOut.findById(id); - if (clientFk == args.newClientFk) + if (clientFk == newClientFk) throw new UserError(`Select a different client`); if (!myOptions.transaction) { @@ -65,10 +65,10 @@ module.exports = Self => { myOptions.transaction = tx; } try { - const filterRef = {where: {refFk: args.ref}}; + const filterRef = {where: {refFk: refFk}}; const tickets = await models.Ticket.find(filterRef, myOptions); const ticketsIds = tickets.map(ticket => ticket.id); - await models.Ticket.refund(ctx, ticketsIds, null, myOptions); + const refundTickets = await models.Ticket.refund(ctx, ticketsIds, null, myOptions); const filterTicket = {where: {ticketFk: {inq: ticketsIds}}}; @@ -82,20 +82,16 @@ module.exports = Self => { const clonedTicketIds = []; for (const clonedTicket of clonedTickets) { - await clonedTicket.updateAttribute('clientFk', args.newClientFk, myOptions); + await clonedTicket.updateAttribute('clientFk', newClientFk, myOptions); clonedTicketIds.push(clonedTicket.id); } - const invoiceIds = await models.Ticket.invoiceTickets(ctx, clonedTicketIds, myOptions); - const [invoiceId] = invoiceIds; + const invoiceCorrection = + {correctedFk: id, cplusRectificationTypeFk, siiTypeInvoiceOutFk, invoiceCorrectionTypeFk}; + const refundTicketIds = refundTickets.map(ticket => ticket.id); - await models.InvoiceCorrection.create({ - correctingFk: invoiceId, - correctedFk: args.id, - cplusRectificationTypeFk: args.cplusRectificationId, - siiTypeInvoiceOutFk: args.siiTypeInvoiceOutId, - invoiceCorrectionTypeFk: args.invoiceCorrectionTypeId - }, myOptions); + await models.Ticket.invoiceTickets(ctx, refundTicketIds, invoiceCorrection, myOptions); + const [invoiceId] = await models.Ticket.invoiceTickets(ctx, clonedTicketIds, null, myOptions); if (tx) { await tx.commit(); diff --git a/modules/invoiceOut/back/models/invoice-correction.json b/modules/invoiceOut/back/models/invoice-correction.json index 43e4f07ef..58f6f63b7 100644 --- a/modules/invoiceOut/back/models/invoice-correction.json +++ b/modules/invoiceOut/back/models/invoice-correction.json @@ -16,13 +16,43 @@ "type": "number" }, "cplusRectificationTypeFk": { - "type": "number" + "type": "number", + "required": true }, "siiTypeInvoiceOutFk": { - "type": "number" + "type": "number", + "required": true }, "invoiceCorrectionTypeFk": { - "type": "number" + "type": "number", + "required": true + }, + "relations": { + "correcting": { + "type": "belongsTo", + "model": "InvoiceOut", + "foreignKey": "correctingFk" + }, + "corrected": { + "type": "belongsTo", + "model": "InvoiceOut", + "foreignKey": "correctedFk" + }, + "cplusRectificationType": { + "type": "belongsTo", + "model": "cplusRectificationType", + "foreignKey": "cplusRectificationTypeFk" + }, + "siiTypeInvoiceOut": { + "type": "belongsTo", + "model": "siiTypeInvoiceOut", + "foreignKey": "siiTypeInvoiceOutFk" + }, + "invoiceCorrectionType": { + "type": "belongsTo", + "model": "invoiceCorrectionType", + "foreignKey": "invoiceCorrectionTypeFk" + } } } } diff --git a/modules/invoiceOut/back/models/sii-type-invoice-out.json b/modules/invoiceOut/back/models/sii-type-invoice-out.json index 17b312617..58d50a12c 100644 --- a/modules/invoiceOut/back/models/sii-type-invoice-out.json +++ b/modules/invoiceOut/back/models/sii-type-invoice-out.json @@ -12,6 +12,9 @@ "type": "number", "description": "Identifier" }, + "code": { + "type": "string" + }, "description": { "type": "string" } diff --git a/modules/invoiceOut/front/descriptor-menu/index.html b/modules/invoiceOut/front/descriptor-menu/index.html index 0052f0c03..435db3612 100644 --- a/modules/invoiceOut/front/descriptor-menu/index.html +++ b/modules/invoiceOut/front/descriptor-menu/index.html @@ -7,7 +7,8 @@ + data="siiTypeInvoiceOuts" + where="{code: {like: 'R%'}}"> + + transferInvoice +
- - - - #{{id}} - {{::name}} - - - - - {{::description}} - - - - - - - - - -
+ + + + #{{id}} - {{::name}} + + + + + {{::description}} + + + + + + + {{::code}} - {{::description}} + + + + + +
diff --git a/modules/invoiceOut/front/descriptor-menu/index.js b/modules/invoiceOut/front/descriptor-menu/index.js index d3862a753..2c28599e7 100644 --- a/modules/invoiceOut/front/descriptor-menu/index.js +++ b/modules/invoiceOut/front/descriptor-menu/index.js @@ -129,15 +129,15 @@ class Controller extends Section { 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 + refFk: this.invoiceOut.ref, + newClientFk: this.clientId, + cplusRectificationTypeFk: this.cplusRectificationType, + siiTypeInvoiceOutFk: this.siiTypeInvoiceOut, + invoiceCorrectionTypeFk: this.invoiceCorrectionType }; this.$http.post(`InvoiceOuts/transferInvoice`, params).then(res => { const invoiceId = res.data; - this.vnApp.showSuccess(this.$t('Invoice trasfered!')); + this.vnApp.showSuccess(this.$t('Transferred invoice')); this.$state.go('invoiceOut.card.summary', {id: invoiceId}); }); } diff --git a/modules/invoiceOut/front/descriptor-menu/locale/es.yml b/modules/invoiceOut/front/descriptor-menu/locale/es.yml index 0f74b5fec..aaeefd9cc 100644 --- a/modules/invoiceOut/front/descriptor-menu/locale/es.yml +++ b/modules/invoiceOut/front/descriptor-menu/locale/es.yml @@ -22,4 +22,5 @@ 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 +Rectificative type: Tipo rectificativa +Transferred invoice: Factura transferida diff --git a/modules/invoiceOut/front/index/manual/index.html b/modules/invoiceOut/front/index/manual/index.html index c3362a319..5872911e4 100644 --- a/modules/invoiceOut/front/index/manual/index.html +++ b/modules/invoiceOut/front/index/manual/index.html @@ -6,6 +6,7 @@ auto-load="true" url="InvoiceOutSerials" data="invoiceOutSerials" + where="{code: {neq: 'R'}}" order="code">
{ am.name agencyName, u.name AS workerUserName, v.numberPlate AS vehiclePlateNumber, - Date_format(r.time, '%H:%i') hour + Date_format(r.time, '%H:%i') hour, + eu.email FROM route r LEFT JOIN agencyMode am ON am.id = r.agencyModeFk LEFT JOIN agency a ON a.id = am.agencyFk LEFT JOIN vehicle v ON v.id = r.vehicleFk LEFT JOIN worker w ON w.id = r.workerFk - LEFT JOIN account.user u ON u.id = w.id` + LEFT JOIN account.user u ON u.id = w.id + LEFT JOIN account.emailUser eu ON eu.userFk = r.workerFk` ); stmt.merge(conn.makeSuffix(filter)); diff --git a/modules/route/back/methods/route/getExpeditionSummary.js b/modules/route/back/methods/route/getExpeditionSummary.js new file mode 100644 index 000000000..ee89401a8 --- /dev/null +++ b/modules/route/back/methods/route/getExpeditionSummary.js @@ -0,0 +1,64 @@ +module.exports = Self => { + Self.remoteMethod('getExpeditionSummary', { + description: 'Get summary of expeditions for a given route', + accepts: [ + { + arg: 'routeFk', + type: 'number', + required: true, + description: 'Foreign key for Route' + } + ], + returns: { + type: 'object', + root: true + }, + http: { + path: '/getExpeditionSummary', + verb: 'get' + } + }); + + Self.getExpeditionSummary = async(routeFk, options) => { + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const query = ` + SELECT routeFk, + addressFk, + SUM(total) total, + SUM(delivery) delivery, + SUM(lost) lost, + SUM(delivered) delivered, + GROUP_CONCAT(totalPacking ORDER BY total DESC SEPARATOR ' ') itemPackingType + FROM ( + SELECT r.id AS routeFk, + t.addressFk, + CONCAT (IFNULL(e.itemPackingTypeFk,'-'), '', COUNT(*)) totalPacking, + COUNT(*) total, + SUM(est.code = 'ON DELIVERY') delivery, + SUM(est.code = 'LOST') lost, + SUM(est.code = 'DELIVERED') delivered, + t.priority + FROM vn.ticket t + JOIN vn.route r ON r.id = t.routeFk + JOIN vn.expedition e ON e.ticketFk = t.id + LEFT JOIN vn.expeditionStateType est ON est.id = e.stateTypeFk + JOIN vn.agencyMode am ON am.id = r.agencyModeFk + JOIN vn.agency ag ON ag.id = am.agencyFk + LEFT JOIN vn.userConfig uc ON uc.userFk = account.myUser_getId() + WHERE (r.created = util.VN_CURDATE() OR r.created = util.yesterday()) + AND t.routeFk = ? + GROUP BY t.addressFk, e.itemPackingTypeFk + ) sub + GROUP BY addressFk + ORDER BY priority DESC + `; + + const results = await Self.rawSql(query, [routeFk], myOptions); + return results; + }; +}; + diff --git a/modules/route/back/methods/route/getTickets.js b/modules/route/back/methods/route/getTickets.js index 1eb9e27f5..d1ebf9ee7 100644 --- a/modules/route/back/methods/route/getTickets.js +++ b/modules/route/back/methods/route/getTickets.js @@ -3,7 +3,7 @@ const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; module.exports = Self => { Self.remoteMethod('getTickets', { - description: 'Return the tickets information displayed on the route module', + description: 'Find all instances of the model matched by filter from the data source.', accessType: 'READ', accepts: [ { @@ -40,22 +40,32 @@ module.exports = Self => { t.clientFk, t.priority, t.addressFk, - st.code AS ticketStateCode, - st.name AS ticketStateName, - wh.name AS warehouseName, - tob.description AS ticketObservation, + st.code ticketStateCode, + st.name ticketStateName, + wh.name warehouseName, + tob.description ticketObservation, a.street, a.postalCode, a.city, - am.name AS agencyModeName, - u.nickname AS userNickname, - vn.ticketTotalVolume(t.id) AS volume, + am.name agencyModeName, + u.nickname userNickname, + vn.ticketTotalVolume(t.id) volume, tob.description, - GROUP_CONCAT(DISTINCT i.itemPackingTypeFk ORDER BY i.itemPackingTypeFk) ipt + GROUP_CONCAT(DISTINCT i.itemPackingTypeFk ORDER BY i.itemPackingTypeFk) ipt, + c.phone clientPhone, + c.mobile clientMobile, + a.phone addressPhone, + a.mobile addressMobile, + a.longitude, + a.latitude, + wm.mediaValue salePersonPhone, + t.cmrFk, + t.isSigned signed FROM vn.route r JOIN ticket t ON t.routeFk = r.id - JOIN vn.sale s ON s.ticketFk = t.id - JOIN vn.item i ON i.id = s.itemFk + JOIN client c ON t.clientFk = c.id + LEFT JOIN vn.sale s ON s.ticketFk = t.id + LEFT JOIN vn.item i ON i.id = s.itemFk LEFT JOIN ticketState ts ON ts.ticketFk = t.id LEFT JOIN state st ON st.id = ts.stateFk LEFT JOIN warehouse wh ON wh.id = t.warehouseFk @@ -65,7 +75,8 @@ module.exports = Self => { LEFT JOIN address a ON a.id = t.addressFk LEFT JOIN agencyMode am ON am.id = t.agencyModeFk LEFT JOIN account.user u ON u.id = r.workerFk - LEFT JOIN vehicle v ON v.id = r.vehicleFk` + LEFT JOIN vehicle v ON v.id = r.vehicleFk + LEFT JOIN workerMedia wm ON wm.workerFk = c.salesPersonFk` ); if (!filter.where) filter.where = {}; diff --git a/modules/route/back/methods/route/specs/getExpeditionSummary.spec.js b/modules/route/back/methods/route/specs/getExpeditionSummary.spec.js new file mode 100644 index 000000000..9d70c339a --- /dev/null +++ b/modules/route/back/methods/route/specs/getExpeditionSummary.spec.js @@ -0,0 +1,10 @@ +const app = require('vn-loopback/server/server'); + +describe('route getExpeditionSummary()', () => { + const routeId = 1; + it('should return a summary of expeditions for a route', async() => { + const result = await app.models.Route.getExpeditionSummary(routeId); + + expect(result.every(route => route.id = routeId)).toBeTruthy(); + }); +}); diff --git a/modules/route/back/models/route.js b/modules/route/back/models/route.js index cbdd75679..9b5f3564f 100644 --- a/modules/route/back/models/route.js +++ b/modules/route/back/models/route.js @@ -17,6 +17,7 @@ module.exports = Self => { require('../methods/route/cmr')(Self); require('../methods/route/getExternalCmrs')(Self); require('../methods/route/downloadCmrsZip')(Self); + require('../methods/route/getExpeditionSummary')(Self); require('../methods/route/getByWorker')(Self); Self.validate('kmStart', validateDistance, { diff --git a/modules/route/back/models/route.json b/modules/route/back/models/route.json index cdb64dd71..f8be9023c 100644 --- a/modules/route/back/models/route.json +++ b/modules/route/back/models/route.json @@ -1,6 +1,9 @@ { "name": "Route", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "route" diff --git a/modules/shelving/back/models/sector.json b/modules/shelving/back/models/sector.json index 47d66bd8d..5ff67491b 100644 --- a/modules/shelving/back/models/sector.json +++ b/modules/shelving/back/models/sector.json @@ -20,18 +20,10 @@ "type": "number", "required": true }, - "isPreviousPreparedByPacking": { - "type": "boolean", - "required": true - }, "code": { "type": "string", "required": false }, - "isPreviousPrepared": { - "type": "boolean", - "required": true - }, "isPackagingArea": { "type": "boolean", "required": true diff --git a/modules/shelving/back/models/shelving.json b/modules/shelving/back/models/shelving.json index 3103b5a4a..46fce31e8 100644 --- a/modules/shelving/back/models/shelving.json +++ b/modules/shelving/back/models/shelving.json @@ -1,6 +1,9 @@ { "name": "Shelving", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "shelving" diff --git a/modules/supplier/back/models/supplier-account.json b/modules/supplier/back/models/supplier-account.json index bc9cf0e24..460c435e2 100644 --- a/modules/supplier/back/models/supplier-account.json +++ b/modules/supplier/back/models/supplier-account.json @@ -1,6 +1,9 @@ { "name": "SupplierAccount", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "supplierAccount" diff --git a/modules/supplier/back/models/supplier-address.json b/modules/supplier/back/models/supplier-address.json index 001b3a31f..fcd599287 100644 --- a/modules/supplier/back/models/supplier-address.json +++ b/modules/supplier/back/models/supplier-address.json @@ -1,7 +1,10 @@ { "name": "SupplierAddress", "description": "Supplier addresses", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "supplierAddress" diff --git a/modules/supplier/back/models/supplier-contact.json b/modules/supplier/back/models/supplier-contact.json index f928cd204..4ea1b8c2a 100644 --- a/modules/supplier/back/models/supplier-contact.json +++ b/modules/supplier/back/models/supplier-contact.json @@ -1,6 +1,9 @@ { "name": "SupplierContact", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "supplierContact" diff --git a/modules/supplier/back/models/supplier.json b/modules/supplier/back/models/supplier.json index b6245ef32..59d23f106 100644 --- a/modules/supplier/back/models/supplier.json +++ b/modules/supplier/back/models/supplier.json @@ -1,6 +1,9 @@ { "name": "Supplier", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "supplier" diff --git a/modules/ticket/back/methods/boxing/specs/getVideo.spec.js b/modules/ticket/back/methods/boxing/specs/getVideo.spec.js deleted file mode 100644 index 8e8cdc5b9..000000000 --- a/modules/ticket/back/methods/boxing/specs/getVideo.spec.js +++ /dev/null @@ -1,40 +0,0 @@ -const models = require('vn-loopback/server/server').models; -const https = require('https'); - -xdescribe('boxing getVideo()', () => { - it('should return data', async() => { - const tx = await models.PackingSiteConfig.beginTransaction({}); - - try { - const options = {transaction: tx}; - - const id = 1; - const video = 'video.mp4'; - - const response = { - pipe: () => {}, - on: () => {}, - end: () => {}, - }; - - const req = { - headers: 'apiHeader', - data: { - pipe: () => {}, - on: () => {}, - } - }; - - spyOn(https, 'request').and.returnValue(response); - - const result = await models.Boxing.getVideo(id, video, req, null, options); - - expect(result[0]).toEqual(response.data.videos[0].filename); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); -}); diff --git a/modules/ticket/back/methods/sale/refund.js b/modules/ticket/back/methods/sale/refund.js index 17b70f12b..a7831e7e3 100644 --- a/modules/ticket/back/methods/sale/refund.js +++ b/modules/ticket/back/methods/sale/refund.js @@ -19,7 +19,7 @@ module.exports = Self => { } ], returns: { - type: ['number'], + type: ['object'], root: true }, http: { @@ -54,7 +54,7 @@ module.exports = Self => { if (tx) await tx.commit(); - return refundsTicket[0]; + return refundsTicket; } catch (e) { if (tx) await tx.rollback(); throw e; diff --git a/modules/ticket/back/methods/sale/specs/canEdit.spec.js b/modules/ticket/back/methods/sale/specs/canEdit.spec.js index eef9136a8..200ea24cc 100644 --- a/modules/ticket/back/methods/sale/specs/canEdit.spec.js +++ b/modules/ticket/back/methods/sale/specs/canEdit.spec.js @@ -102,7 +102,7 @@ describe('sale canEdit()', () => { try { const options = {transaction: tx}; - const role = await models.Role.findOne({ + const role = await models.VnRole.findOne({ where: { name: roleEnabled.principalId } @@ -159,7 +159,7 @@ describe('sale canEdit()', () => { try { const options = {transaction: tx}; - const role = await models.Role.findOne({ + const role = await models.VnRole.findOne({ where: { name: roleEnabled.principalId } diff --git a/modules/ticket/back/methods/sale/specs/refund.spec.js b/modules/ticket/back/methods/sale/specs/refund.spec.js index 08eb1fabd..60f77e90c 100644 --- a/modules/ticket/back/methods/sale/specs/refund.spec.js +++ b/modules/ticket/back/methods/sale/specs/refund.spec.js @@ -23,9 +23,9 @@ describe('Sale refund()', () => { try { const options = {transaction: tx}; - const refundedTicket = await models.Sale.refund(ctx, salesIds, servicesIds, withWarehouse, options); + const refundedTickets = await models.Sale.refund(ctx, salesIds, servicesIds, withWarehouse, options); - expect(refundedTicket).toBeDefined(); + expect(refundedTickets).toBeDefined(); await tx.rollback(); } catch (e) { @@ -42,11 +42,11 @@ describe('Sale refund()', () => { const options = {transaction: tx}; const ticketsBefore = await models.Ticket.find({}, options); - const ticket = await models.Sale.refund(ctx, salesIds, servicesIds, withWarehouse, options); + const tickets = await models.Sale.refund(ctx, salesIds, servicesIds, withWarehouse, options); const refundedTicket = await models.Ticket.findOne({ where: { - id: ticket.id + id: tickets[0].id }, include: [ { diff --git a/modules/ticket/back/methods/ticket-dms/removeFile.js b/modules/ticket/back/methods/ticket-dms/removeFile.js index f48dc7fef..6fba4c552 100644 --- a/modules/ticket/back/methods/ticket-dms/removeFile.js +++ b/modules/ticket/back/methods/ticket-dms/removeFile.js @@ -19,7 +19,6 @@ module.exports = Self => { }); Self.removeFile = async(ctx, id, options) => { - const models = Self.app.models; const myOptions = {}; let tx; @@ -32,18 +31,18 @@ module.exports = Self => { } try { - const targetTicketDms = await models.TicketDms.findById(id, null, myOptions); - const targetDms = await models.Dms.findById(targetTicketDms.dmsFk, null, myOptions); - const trashDmsType = await models.DmsType.findOne({where: {code: 'trash'}}, myOptions); + const ticketDms = await Self.findById(id, null, myOptions); - await models.Dms.removeFile(ctx, targetTicketDms.dmsFk, myOptions); - await targetTicketDms.destroy(myOptions); + const targetDms = await Self.app.models.Dms.removeFile(ctx, ticketDms.dmsFk, myOptions); - await targetDms.updateAttribute('dmsTypeFk', trashDmsType.id, myOptions); + if (!targetDms || !ticketDms) + throw new UserError('Try again'); + + const ticketDmsDestroyed = await ticketDms.destroy(myOptions); if (tx) await tx.commit(); - return targetDms; + return ticketDmsDestroyed; } catch (e) { if (tx) await tx.rollback(); throw e; diff --git a/modules/ticket/back/methods/ticket/canBeInvoiced.js b/modules/ticket/back/methods/ticket/canBeInvoiced.js index 348f02348..855a864c2 100644 --- a/modules/ticket/back/methods/ticket/canBeInvoiced.js +++ b/modules/ticket/back/methods/ticket/canBeInvoiced.js @@ -10,20 +10,20 @@ module.exports = function(Self) { description: 'The tickets id', type: ['number'], required: true + }, + { + arg: 'isRectificative', + description: 'If it is rectificative', + type: 'boolean' } ], - returns: { - arg: 'data', - type: 'boolean', - root: true - }, http: { path: `/canBeInvoiced`, verb: 'get' } }); - Self.canBeInvoiced = async(ctx, ticketsIds, options) => { + Self.canBeInvoiced = async(ctx, ticketsIds, isRectificative, options) => { const myOptions = {}; const $t = ctx.req.__; // $translate @@ -34,26 +34,14 @@ module.exports = function(Self) { where: { id: {inq: ticketsIds} }, - fields: ['id', 'refFk', 'shipped', 'totalWithVat', 'companyFk'] + fields: ['id', 'refFk', 'shipped', 'totalWithVat'] }, myOptions); - const [firstTicket] = tickets; - const companyFk = firstTicket.companyFk; - - const query = - `SELECT COUNT(*) isSpanishCompany - FROM supplier s - JOIN country c ON c.id = s.countryFk - AND c.code = 'ES' - WHERE s.id = ?`; - const [supplierCompany] = await Self.rawSql(query, [companyFk], options); - - const isSpanishCompany = supplierCompany?.isSpanishCompany; - - const [result] = await Self.rawSql('SELECT hasAnyNegativeBase() AS base', null, options); - const hasAnyNegativeBase = result?.base && isSpanishCompany; - if (hasAnyNegativeBase) - throw new UserError($t('Negative basis of tickets', {ticketsIds: ticketsIds})); + const taxBaseFunction = isRectificative ? 'hasAnyPositiveBase' : 'hasAnyNegativeBase'; + const [hasAnyIncorrectBase] = + await Self.rawSql(`SELECT ${taxBaseFunction}() AS hasBasesProblem`, null, options); + if (hasAnyIncorrectBase?.hasBasesProblem) + throw new UserError($t(taxBaseFunction, {ticketsIds: ticketsIds})); const today = Date.vnNew(); tickets.some(ticket => { @@ -70,7 +58,5 @@ module.exports = function(Self) { if (ticketsIds.length == 1 && priceZero) throw new UserError(`A ticket with an amount of zero can't be invoiced`); }); - - return true; }; }; diff --git a/modules/ticket/back/methods/ticket/invoiceTickets.js b/modules/ticket/back/methods/ticket/invoiceTickets.js index fa3ee93af..06429836e 100644 --- a/modules/ticket/back/methods/ticket/invoiceTickets.js +++ b/modules/ticket/back/methods/ticket/invoiceTickets.js @@ -10,7 +10,13 @@ module.exports = function(Self) { description: 'The tickets id', type: ['number'], required: true + }, + { + arg: 'invoiceCorrection', + description: 'The invoice correction', + type: 'object', } + ], returns: { type: ['object'], @@ -22,7 +28,7 @@ module.exports = function(Self) { } }); - Self.invoiceTickets = async(ctx, ticketsIds, options) => { + Self.invoiceTickets = async(ctx, ticketsIds, invoiceCorrection, options) => { const models = Self.app.models; const date = Date.vnNew(); date.setHours(0, 0, 0, 0); @@ -41,10 +47,10 @@ module.exports = function(Self) { let invoicesIds = []; try { const tickets = await models.Ticket.find({ + fields: ['id', 'clientFk', 'companyFk', 'addressFk'], where: { id: {inq: ticketsIds} - }, - fields: ['id', 'clientFk', 'companyFk'] + } }, myOptions); const [firstTicket] = tickets; @@ -55,22 +61,20 @@ module.exports = function(Self) { if (!isSameClient) throw new UserError(`You can't invoice tickets from multiple clients`); - const client = await models.Client.findById(clientId, { - fields: ['id', 'hasToInvoiceByAddress'] + const {hasToInvoiceByAddress} = await models.Client.findById(clientId, { + fields: ['hasToInvoiceByAddress'] }, myOptions); - if (client.hasToInvoiceByAddress) { - const query = ` - SELECT DISTINCT addressFk - FROM ticket t - WHERE id IN (?)`; - const result = await Self.rawSql(query, [ticketsIds], myOptions); + let ticketsByAddress = hasToInvoiceByAddress + ? Object.values(tickets.reduce((group, {id, addressFk}) => { + group[addressFk] = group[addressFk] ?? []; + group[addressFk].push(id); + return group; + }, {})) + : [ticketsIds]; - const addressIds = result.map(address => address.addressFk); - for (const address of addressIds) - await createInvoice(ctx, companyId, ticketsIds, address, invoicesIds, myOptions); - } else - await createInvoice(ctx, companyId, ticketsIds, null, invoicesIds, myOptions); + for (const ticketIds of ticketsByAddress) + invoicesIds.push(await createInvoice(ctx, companyId, ticketIds, invoiceCorrection, myOptions)); if (tx) await tx.commit(); } catch (e) { @@ -85,9 +89,8 @@ module.exports = function(Self) { return invoicesIds; }; - async function createInvoice(ctx, companyId, ticketsIds, address, invoicesIds, myOptions) { + async function createInvoice(ctx, companyId, ticketsIds, invoiceCorrection, myOptions) { const models = Self.app.models; - await models.Ticket.rawSql(` CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice (PRIMARY KEY (id)) @@ -95,11 +98,8 @@ module.exports = function(Self) { SELECT id FROM vn.ticket WHERE id IN (?) - ${address ? `AND addressFk = ${address}` : ''} `, [ticketsIds], myOptions); - - const invoiceId = await models.Ticket.makeInvoice(ctx, 'R', companyId, Date.vnNew(), myOptions); - invoicesIds.push(invoiceId); + return models.Ticket.makeInvoice(ctx, 'R', companyId, Date.vnNew(), invoiceCorrection, myOptions); } }; diff --git a/modules/ticket/back/methods/ticket/isEditableOrThrow.js b/modules/ticket/back/methods/ticket/isEditableOrThrow.js index f8285cecd..41438be3a 100644 --- a/modules/ticket/back/methods/ticket/isEditableOrThrow.js +++ b/modules/ticket/back/methods/ticket/isEditableOrThrow.js @@ -41,7 +41,7 @@ module.exports = Self => { throw new ForbiddenError(`This ticket is not editable.`); if (isLocked && !isWeekly) - throw new ForbiddenError(`This ticket is locked.`); + throw new ForbiddenError(`This ticket is locked`); if (isWeekly && !canEditWeeklyTicket) throw new ForbiddenError(`You don't have enough privileges.`); diff --git a/modules/ticket/back/methods/ticket/makeInvoice.js b/modules/ticket/back/methods/ticket/makeInvoice.js index e7ee806c7..83222a4ee 100644 --- a/modules/ticket/back/methods/ticket/makeInvoice.js +++ b/modules/ticket/back/methods/ticket/makeInvoice.js @@ -22,6 +22,11 @@ module.exports = function(Self) { description: 'The invoice date', type: 'date', required: true + }, + { + arg: 'invoiceCorrection', + description: 'The invoice correction', + type: 'object', } ], returns: { @@ -34,7 +39,7 @@ module.exports = function(Self) { } }); - Self.makeInvoice = async(ctx, invoiceType, companyFk, invoiceDate, options) => { + Self.makeInvoice = async(ctx, invoiceType, companyFk, invoiceDate, invoiceCorrection, options) => { const models = Self.app.models; invoiceDate.setHours(0, 0, 0, 0); @@ -62,20 +67,24 @@ module.exports = function(Self) { fields: ['id', 'clientFk', 'addressFk'] }, myOptions); - await models.Ticket.canBeInvoiced(ctx, ticketsIds, myOptions); + await models.Ticket.canBeInvoiced(ctx, ticketsIds, !!invoiceCorrection, myOptions); const [firstTicket] = tickets; const clientId = firstTicket.clientFk; - const clientCanBeInvoiced = await models.Client.canBeInvoiced(clientId, companyFk, myOptions); + const clientCanBeInvoiced = + await models.Client.canBeInvoiced(clientId, companyFk, myOptions); + if (!clientCanBeInvoiced) throw new UserError(`This client can't be invoiced`); - const query = `SELECT vn.invoiceSerial(?, ?, ?) AS serial`; - const [{serial}] = await Self.rawSql(query, [ - clientId, - companyFk, - invoiceType, - ], myOptions); + const [{serial}] = invoiceCorrection ? [{serial: 'R'}] : await Self.rawSql( + `SELECT vn.invoiceSerial(?, ?, ?) AS serial`, + [ + clientId, + companyFk, + invoiceType + ], + myOptions); const invoiceOutSerial = await models.InvoiceOutSerial.findById(serial); if (invoiceOutSerial?.taxAreaFk == 'WORLD') { @@ -87,11 +96,17 @@ module.exports = function(Self) { await Self.rawSql('CALL invoiceOut_new(?, ?, null, @invoiceId)', [serial, invoiceDate], myOptions); const [resultInvoice] = await Self.rawSql('SELECT @invoiceId id', [], myOptions); - if (!resultInvoice) + if (!resultInvoice?.id) throw new UserError('No tickets to invoice', 'notInvoiced'); - if (serial != 'R' && resultInvoice.id) - await Self.rawSql('CALL invoiceOutBooking(?)', [resultInvoice.id], myOptions); + if (invoiceCorrection) { + await models.InvoiceCorrection.create( + Object.assign(invoiceCorrection, {correctingFk: resultInvoice.id}), + myOptions + ); + } + + await Self.rawSql('CALL invoiceOutBooking(?)', [resultInvoice.id], myOptions); if (tx) await tx.commit(); diff --git a/modules/ticket/back/methods/ticket/refund.js b/modules/ticket/back/methods/ticket/refund.js index 758384ae2..4fed02260 100644 --- a/modules/ticket/back/methods/ticket/refund.js +++ b/modules/ticket/back/methods/ticket/refund.js @@ -15,7 +15,7 @@ module.exports = Self => { } ], returns: { - type: ['number'], + type: ['object'], root: true }, http: { diff --git a/modules/ticket/back/methods/ticket/sendSms.js b/modules/ticket/back/methods/ticket/sendSms.js index ffc95c6b4..36e52fe3d 100644 --- a/modules/ticket/back/methods/ticket/sendSms.js +++ b/modules/ticket/back/methods/ticket/sendSms.js @@ -33,7 +33,9 @@ module.exports = Self => { Self.sendSms = async(ctx, id, destination, message) => { const models = Self.app.models; const sms = await models.Sms.send(ctx, destination, message); - await models.TicketSms.create({ + const {clientFk} = await models.Ticket.findById(id); + await models.ClientSms.create({ + clientFk, ticketFk: id, smsFk: sms.id }); diff --git a/modules/ticket/back/methods/ticket/specs/addSale.spec.js b/modules/ticket/back/methods/ticket/specs/addSale.spec.js index 8c0e39bec..72c9541d9 100644 --- a/modules/ticket/back/methods/ticket/specs/addSale.spec.js +++ b/modules/ticket/back/methods/ticket/specs/addSale.spec.js @@ -89,6 +89,6 @@ describe('ticket addSale()', () => { error = e; } - expect(error.message).toEqual(`This ticket is locked.`); + expect(error.message).toEqual(`This ticket is locked`); }); }); diff --git a/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js b/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js index 538dbc49f..78973e040 100644 --- a/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js +++ b/modules/ticket/back/methods/ticket/specs/canBeInvoiced.spec.js @@ -27,10 +27,7 @@ describe('ticket canBeInvoiced()', () => { WHERE id IN (?) `, [ticketId], options); - const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options); - - expect(canBeInvoiced).toEqual(false); - + await models.Ticket.canBeInvoiced(ctx, [ticketId], false, options); await tx.rollback(); } catch (e) { error = e; @@ -59,10 +56,7 @@ describe('ticket canBeInvoiced()', () => { WHERE id IN (?) `, [ticketId], options); - const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options); - - expect(canBeInvoiced).toEqual(false); - + await models.Ticket.canBeInvoiced(ctx, [ticketId], false, options); await tx.rollback(); } catch (e) { error = e; @@ -95,10 +89,7 @@ describe('ticket canBeInvoiced()', () => { WHERE id IN (?) `, [ticketId], options); - const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options); - - expect(canBeInvoiced).toEqual(false); - + await models.Ticket.canBeInvoiced(ctx, [ticketId], false, options); await tx.rollback(); } catch (e) { error = e; @@ -123,14 +114,36 @@ describe('ticket canBeInvoiced()', () => { WHERE id IN (?) `, [ticketId], options); - const canBeInvoiced = await models.Ticket.canBeInvoiced(ctx, [ticketId], options); - - expect(canBeInvoiced).toEqual(true); - + await models.Ticket.canBeInvoiced(ctx, [ticketId], false, options); await tx.rollback(); } catch (e) { await tx.rollback(); throw e; } }); + + it('should return falsy for a ticket has positiveBase', async() => { + const tx = await models.Ticket.beginTransaction({}); + + try { + const options = {transaction: tx}; + + await models.Ticket.rawSql(` + CREATE OR REPLACE TEMPORARY TABLE tmp.ticketToInvoice + (PRIMARY KEY (id)) + ENGINE = MEMORY + SELECT id + FROM vn.ticket + WHERE id IN (?) + `, [ticketId], options); + + await models.Ticket.canBeInvoiced(ctx, [ticketId], true, options); + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toEqual(`hasAnyPositiveBase`); + }); }); diff --git a/modules/ticket/back/methods/ticket/specs/invoiceTickets.spec.js b/modules/ticket/back/methods/ticket/specs/invoiceTickets.spec.js index 8971fb24a..162dc066a 100644 --- a/modules/ticket/back/methods/ticket/specs/invoiceTickets.spec.js +++ b/modules/ticket/back/methods/ticket/specs/invoiceTickets.spec.js @@ -31,7 +31,7 @@ describe('ticket invoiceTickets()', () => { const options = {transaction: tx}; const ticketsIds = [11, 16]; - await models.Ticket.invoiceTickets(ctx, ticketsIds, options); + await models.Ticket.invoiceTickets(ctx, ticketsIds, null, options); await tx.rollback(); } catch (e) { @@ -57,7 +57,7 @@ describe('ticket invoiceTickets()', () => { await client.updateAttribute('isTaxDataChecked', false, options); const ticketsIds = [11]; - await models.Ticket.invoiceTickets(ctx, ticketsIds, options); + await models.Ticket.invoiceTickets(ctx, ticketsIds, null, options); await tx.rollback(); } catch (e) { @@ -80,8 +80,8 @@ describe('ticket invoiceTickets()', () => { const options = {transaction: tx}; const ticketsIds = [11]; - await models.Ticket.invoiceTickets(ctx, ticketsIds, options); - await models.Ticket.invoiceTickets(ctx, ticketsIds, options); + await models.Ticket.invoiceTickets(ctx, ticketsIds, null, options); + await models.Ticket.invoiceTickets(ctx, ticketsIds, null, options); await tx.rollback(); } catch (e) { @@ -102,7 +102,7 @@ describe('ticket invoiceTickets()', () => { const options = {transaction: tx}; const ticketsIds = [11]; - const invoicesIds = await models.Ticket.invoiceTickets(ctx, ticketsIds, options); + const invoicesIds = await models.Ticket.invoiceTickets(ctx, ticketsIds, null, options); expect(invoicesIds.length).toBeGreaterThan(0); diff --git a/modules/ticket/back/methods/ticket/specs/isEditableOrThrow.spec.js b/modules/ticket/back/methods/ticket/specs/isEditableOrThrow.spec.js index 6c89bac26..bdf547325 100644 --- a/modules/ticket/back/methods/ticket/specs/isEditableOrThrow.spec.js +++ b/modules/ticket/back/methods/ticket/specs/isEditableOrThrow.spec.js @@ -40,7 +40,7 @@ describe('ticket isEditableOrThrow()', () => { expect(error.message).toEqual(`This ticket is not editable.`); }); - it('should throw an error as this ticket is locked.', async() => { + it('should throw an error as This ticket is locked', async() => { const tx = await models.Ticket.beginTransaction({}); let error; try { @@ -57,7 +57,7 @@ describe('ticket isEditableOrThrow()', () => { await tx.rollback(); } - expect(error.message).toEqual(`This ticket is locked.`); + expect(error.message).toEqual(`This ticket is locked`); }); it('should throw an error as you do not have enough privileges.', async() => { diff --git a/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js b/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js index 9b1fd8f6f..456303602 100644 --- a/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js +++ b/modules/ticket/back/methods/ticket/specs/makeInvoice.spec.js @@ -42,7 +42,7 @@ describe('ticket makeInvoice()', () => { WHERE id IN (?) `, [ticketsIds], options); - const invoiceId = await models.Ticket.makeInvoice(ctx, invoiceType, companyFk, invoiceDate, options); + const invoiceId = await models.Ticket.makeInvoice(ctx, invoiceType, companyFk, invoiceDate, null, options); expect(invoiceId).toBeDefined(); @@ -70,7 +70,7 @@ describe('ticket makeInvoice()', () => { WHERE id IN (?) `, [ticketsId], options); - await models.Ticket.makeInvoice(ctx, invoiceType, companyFk, invoiceDate, options); + await models.Ticket.makeInvoice(ctx, invoiceType, companyFk, invoiceDate, null, options); await tx.rollback(); } catch (e) { error = e; diff --git a/modules/ticket/back/methods/ticket/specs/recalculateComponents.spec.js b/modules/ticket/back/methods/ticket/specs/recalculateComponents.spec.js index 383c2c6d5..d358a79f5 100644 --- a/modules/ticket/back/methods/ticket/specs/recalculateComponents.spec.js +++ b/modules/ticket/back/methods/ticket/specs/recalculateComponents.spec.js @@ -39,6 +39,6 @@ describe('ticket recalculateComponents()', () => { error = e; } - expect(error).toEqual(new ForbiddenError(`This ticket is locked.`)); + expect(error).toEqual(new ForbiddenError(`This ticket is locked`)); }); }); diff --git a/modules/ticket/back/methods/ticket/specs/sendSms.spec.js b/modules/ticket/back/methods/ticket/specs/sendSms.spec.js index 3f93198d1..afc1ada54 100644 --- a/modules/ticket/back/methods/ticket/specs/sendSms.spec.js +++ b/modules/ticket/back/methods/ticket/specs/sendSms.spec.js @@ -14,12 +14,12 @@ describe('ticket sendSms()', () => { await models.Ticket.sendSms(ctx, id, destination, message, options); - const filter = { - ticketFk: id - }; - const ticketSms = await models.TicketSms.findOne(filter, options); + const clientSms = await models.ClientSms.findOne( + {where: {ticketFk: id}}, + options + ); - expect(ticketSms.ticketFk).toEqual(id); + expect(clientSms.ticketFk).toEqual(id); await tx.rollback(); } catch (e) { diff --git a/modules/ticket/back/methods/ticket/specs/state.spec.js b/modules/ticket/back/methods/ticket/specs/state.spec.js index 9b5e80165..f369932de 100644 --- a/modules/ticket/back/methods/ticket/specs/state.spec.js +++ b/modules/ticket/back/methods/ticket/specs/state.spec.js @@ -94,10 +94,10 @@ describe('ticket state()', () => { const ticketTracking = await models.Ticket.state(ctx, params, options); - expect(ticketTracking.__data.ticketFk).toBe(params.ticketFk); - expect(ticketTracking.__data.stateFk).toBe(params.stateFk); - expect(ticketTracking.__data.workerFk).toBe(49); - expect(ticketTracking.__data.id).toBeDefined(); + expect(ticketTracking.ticketFk).toBe(params.ticketFk); + expect(ticketTracking.stateFk).toBe(params.stateFk); + expect(ticketTracking.userFk).toBe(49); + expect(ticketTracking.id).toBeDefined(); await tx.rollback(); } catch (e) { @@ -116,14 +116,14 @@ describe('ticket state()', () => { const ticket = await models.Ticket.create(sampleTicket, options); const ctx = {req: {accessToken: {userId: 18}}}; const assignedState = await models.State.findOne({where: {code: 'PICKER_DESIGNED'}}, options); - const params = {ticketFk: ticket.id, stateFk: assignedState.id, workerFk: 1}; + const params = {ticketFk: ticket.id, stateFk: assignedState.id, userFk: 1}; const res = await models.Ticket.state(ctx, params, options); - expect(res.__data.ticketFk).toBe(params.ticketFk); - expect(res.__data.stateFk).toBe(params.stateFk); - expect(res.__data.workerFk).toBe(params.workerFk); - expect(res.__data.workerFk).toBe(1); - expect(res.__data.id).toBeDefined(); + expect(res.ticketFk).toBe(params.ticketFk); + expect(res.stateFk).toBe(params.stateFk); + expect(res.userFk).toBe(params.userFk); + expect(res.userFk).toBe(1); + expect(res.id).toBeDefined(); await tx.rollback(); } catch (e) { diff --git a/modules/ticket/back/methods/ticket/specs/transferClient.spec.js b/modules/ticket/back/methods/ticket/specs/transferClient.spec.js index c09c20083..5a9edd17e 100644 --- a/modules/ticket/back/methods/ticket/specs/transferClient.spec.js +++ b/modules/ticket/back/methods/ticket/specs/transferClient.spec.js @@ -23,7 +23,7 @@ describe('Ticket transferClient()', () => { error = e; } - expect(error.message).toEqual(`This ticket is locked.`); + expect(error.message).toEqual(`This ticket is locked`); }); it('should be assigned a different clientFk', async() => { diff --git a/modules/ticket/back/methods/ticket/state.js b/modules/ticket/back/methods/ticket/state.js index 01bfbba20..adac2e42f 100644 --- a/modules/ticket/back/methods/ticket/state.js +++ b/modules/ticket/back/methods/ticket/state.js @@ -51,12 +51,12 @@ module.exports = Self => { params.stateFk = state.id; } - if (!params.workerFk) { + if (!params.userFk) { const worker = await models.Worker.findOne({ where: {id: userId} }, myOptions); - params.workerFk = worker.id; + params.userFk = worker.id; } const ticketState = await models.TicketState.findById(params.ticketFk, { diff --git a/modules/ticket/back/methods/ticket/uploadFile.js b/modules/ticket/back/methods/ticket/uploadFile.js index 4de9904e1..5b79aa973 100644 --- a/modules/ticket/back/methods/ticket/uploadFile.js +++ b/modules/ticket/back/methods/ticket/uploadFile.js @@ -47,7 +47,7 @@ module.exports = Self => { }); Self.uploadFile = async(ctx, id, options) => { - const models = Self.app.models; + const {Dms, TicketDms} = Self.app.models; const myOptions = {}; let tx; @@ -59,22 +59,19 @@ module.exports = Self => { myOptions.transaction = tx; } - const promises = []; try { - const uploadedFiles = await models.Dms.uploadFile(ctx, myOptions); - uploadedFiles.forEach(dms => { - const newTicketDms = models.TicketDms.create({ - ticketFk: id, - dmsFk: dms.id - }, myOptions); + const uploadedFiles = await Dms.uploadFile(ctx, myOptions); - promises.push(newTicketDms); - }); - const resolvedPromises = await Promise.all(promises); + const promises = uploadedFiles.map(dms => TicketDms.create({ + ticketFk: id, + dmsFk: dms.id + }, myOptions)); + + await Promise.all(promises); if (tx) await tx.commit(); - return resolvedPromises; + return uploadedFiles; } catch (e) { if (tx) await tx.rollback(); throw e; diff --git a/modules/ticket/back/models/expedition-state.json b/modules/ticket/back/models/expedition-state.json index 262eb2e38..eda0f79fd 100644 --- a/modules/ticket/back/models/expedition-state.json +++ b/modules/ticket/back/models/expedition-state.json @@ -24,5 +24,12 @@ "userFk": { "type": "number" } + }, + "relations": { + "expeditionStateType": { + "type": "belongsTo", + "model": "ExpeditionStateType", + "foreignKey": "typeFk" + } } } diff --git a/modules/ticket/back/models/expedition.json b/modules/ticket/back/models/expedition.json index 069c6e281..2dcca1e87 100644 --- a/modules/ticket/back/models/expedition.json +++ b/modules/ticket/back/models/expedition.json @@ -1,6 +1,9 @@ { "name": "Expedition", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "expedition" diff --git a/modules/ticket/back/models/sale.json b/modules/ticket/back/models/sale.json index 72ca1f5e0..96a36bbc9 100644 --- a/modules/ticket/back/models/sale.json +++ b/modules/ticket/back/models/sale.json @@ -1,6 +1,9 @@ { "name": "Sale", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "sale" diff --git a/modules/ticket/back/models/ticket-dms.json b/modules/ticket/back/models/ticket-dms.json index 071999be7..a3e697506 100644 --- a/modules/ticket/back/models/ticket-dms.json +++ b/modules/ticket/back/models/ticket-dms.json @@ -1,6 +1,9 @@ { "name": "TicketDms", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "ticketDms" diff --git a/modules/ticket/back/models/ticket-observation.json b/modules/ticket/back/models/ticket-observation.json index 64e49b217..26d6f7586 100644 --- a/modules/ticket/back/models/ticket-observation.json +++ b/modules/ticket/back/models/ticket-observation.json @@ -1,6 +1,9 @@ { "name": "TicketObservation", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "ticketObservation" diff --git a/modules/ticket/back/models/ticket-packaging.json b/modules/ticket/back/models/ticket-packaging.json index 6c94c810e..0cf494809 100644 --- a/modules/ticket/back/models/ticket-packaging.json +++ b/modules/ticket/back/models/ticket-packaging.json @@ -1,6 +1,9 @@ { "name": "TicketPackaging", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "ticketPackaging" diff --git a/modules/ticket/back/models/ticket-refund.json b/modules/ticket/back/models/ticket-refund.json index d344a3f1c..249270c8b 100644 --- a/modules/ticket/back/models/ticket-refund.json +++ b/modules/ticket/back/models/ticket-refund.json @@ -1,6 +1,9 @@ { "name": "TicketRefund", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "ticketRefund" diff --git a/modules/ticket/back/models/ticket-request.json b/modules/ticket/back/models/ticket-request.json index f8407792e..2cfcd30a1 100644 --- a/modules/ticket/back/models/ticket-request.json +++ b/modules/ticket/back/models/ticket-request.json @@ -1,6 +1,9 @@ { "name": "TicketRequest", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "ticketRequest" diff --git a/modules/ticket/back/models/ticket-service.json b/modules/ticket/back/models/ticket-service.json index f1dbede13..4dfbd2fbd 100644 --- a/modules/ticket/back/models/ticket-service.json +++ b/modules/ticket/back/models/ticket-service.json @@ -1,6 +1,9 @@ { "name": "TicketService", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "ticketService" diff --git a/modules/ticket/back/models/ticket-state.json b/modules/ticket/back/models/ticket-state.json index a10938ef0..3dd322939 100644 --- a/modules/ticket/back/models/ticket-state.json +++ b/modules/ticket/back/models/ticket-state.json @@ -33,9 +33,9 @@ "model": "State", "foreignKey": "stateFk" }, - "worker": { + "user": { "type": "belongsTo", - "model": "Worker", + "model": "VnUser", "foreignKey": "workerFk" } } diff --git a/modules/ticket/back/models/ticket-tracking.js b/modules/ticket/back/models/ticket-tracking.js index 92e046d3e..72e1880b9 100644 --- a/modules/ticket/back/models/ticket-tracking.js +++ b/modules/ticket/back/models/ticket-tracking.js @@ -2,5 +2,5 @@ module.exports = function(Self) { require('../methods/ticket-tracking/setDelivered')(Self); Self.validatesPresenceOf('stateFk', {message: 'State cannot be blank'}); - Self.validatesPresenceOf('workerFk', {message: 'Worker cannot be blank'}); + Self.validatesPresenceOf('userFk', {message: 'Worker cannot be blank'}); }; diff --git a/modules/ticket/back/models/ticket-tracking.json b/modules/ticket/back/models/ticket-tracking.json index 8b5ce0b64..025a07bc0 100644 --- a/modules/ticket/back/models/ticket-tracking.json +++ b/modules/ticket/back/models/ticket-tracking.json @@ -1,6 +1,9 @@ { "name": "TicketTracking", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "ticketTracking" @@ -8,21 +11,18 @@ }, "properties": { "id": { - "id": true, - "type": "number", - "forceId": false + "id": true, + "type": "number", + "forceId": false }, "created": { - "type": "date" + "type": "date" }, "ticketFk": { - "type": "number" + "type": "number" }, "stateFk": { - "type": "number" - }, - "workerFk": { - "type": "number" + "type": "number" } }, "relations": { @@ -36,10 +36,10 @@ "model": "State", "foreignKey": "stateFk" }, - "worker": { + "user": { "type": "belongsTo", - "model": "Worker", - "foreignKey": "workerFk" + "model": "VnUser", + "foreignKey": "userFk" } } } diff --git a/modules/ticket/back/models/ticket-weekly.json b/modules/ticket/back/models/ticket-weekly.json index c5e485aa2..7494cac79 100644 --- a/modules/ticket/back/models/ticket-weekly.json +++ b/modules/ticket/back/models/ticket-weekly.json @@ -1,6 +1,9 @@ { "name": "TicketWeekly", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "ticketWeekly" diff --git a/modules/ticket/back/models/ticket.json b/modules/ticket/back/models/ticket.json index ec4193bed..c55cd82bb 100644 --- a/modules/ticket/back/models/ticket.json +++ b/modules/ticket/back/models/ticket.json @@ -1,6 +1,9 @@ { "name": "Ticket", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "ticket" diff --git a/modules/ticket/front/descriptor-menu/index.js b/modules/ticket/front/descriptor-menu/index.js index 18db8c147..cd819e623 100644 --- a/modules/ticket/front/descriptor-menu/index.js +++ b/modules/ticket/front/descriptor-menu/index.js @@ -292,7 +292,7 @@ class Controller extends Section { const query = 'Tickets/refund'; return this.$http.post(query, params) .then(res => { - const refundTicket = res.data; + const [refundTicket] = res.data; this.vnApp.showSuccess(this.$t('The following refund ticket have been created', { ticketId: refundTicket.id })); diff --git a/modules/ticket/front/descriptor-menu/index.spec.js b/modules/ticket/front/descriptor-menu/index.spec.js index 1bb270165..c755b14c3 100644 --- a/modules/ticket/front/descriptor-menu/index.spec.js +++ b/modules/ticket/front/descriptor-menu/index.spec.js @@ -262,11 +262,12 @@ describe('Ticket Component vnTicketDescriptorMenu', () => { const params = { ticketsIds: [16] }; - $httpBackend.expectPOST('Tickets/refund', params).respond({id: 99}); + const response = {id: 99}; + $httpBackend.expectPOST('Tickets/refund', params).respond([response]); controller.refund(); $httpBackend.flush(); - expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', {id: 99}); + expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', response); }); }); diff --git a/modules/ticket/front/sale/index.js b/modules/ticket/front/sale/index.js index 4f6a9e757..ed6d9b10a 100644 --- a/modules/ticket/front/sale/index.js +++ b/modules/ticket/front/sale/index.js @@ -526,7 +526,7 @@ class Controller extends Section { const params = {salesIds: salesIds, withWarehouse: withWarehouse}; const query = 'Sales/refund'; this.$http.post(query, params).then(res => { - const refundTicket = res.data; + const [refundTicket] = res.data; this.vnApp.showSuccess(this.$t('The following refund ticket have been created', { ticketId: refundTicket.id })); diff --git a/modules/ticket/front/sale/index.spec.js b/modules/ticket/front/sale/index.spec.js index 70781eb58..36be32f52 100644 --- a/modules/ticket/front/sale/index.spec.js +++ b/modules/ticket/front/sale/index.spec.js @@ -729,7 +729,7 @@ describe('Ticket', () => { salesIds: [1, 4], }; const refundTicket = {id: 99}; - $httpBackend.expect('POST', 'Sales/refund', params).respond(200, refundTicket); + $httpBackend.expect('POST', 'Sales/refund', params).respond(200, [refundTicket]); controller.createRefund(); $httpBackend.flush(); diff --git a/modules/ticket/front/tracking/index/index.html b/modules/ticket/front/tracking/index/index.html index 12c4778c9..10ee6d848 100644 --- a/modules/ticket/front/tracking/index/index.html +++ b/modules/ticket/front/tracking/index/index.html @@ -23,9 +23,9 @@ {{::tracking.state.name}} - {{::tracking.worker.user.name || 'System' | translate}} + ng-class="{'link': tracking.user.id}" + ng-click="workerDescriptor.show($event, tracking.user.id)"> + {{::tracking.user.name || 'System' | translate}} {{::tracking.created | date:'dd/MM/yyyy HH:mm'}} diff --git a/modules/ticket/front/tracking/index/index.js b/modules/ticket/front/tracking/index/index.js index 95665b071..ff3dc881b 100644 --- a/modules/ticket/front/tracking/index/index.js +++ b/modules/ticket/front/tracking/index/index.js @@ -7,15 +7,9 @@ class Controller extends Section { this.filter = { include: [ { - relation: 'worker', + relation: 'user', scope: { - fields: ['id'], - include: { - relation: 'user', - scope: { - fields: ['name'] - } - } + fields: ['name'] } }, { relation: 'state', diff --git a/modules/travel/back/models/travel-thermograph.json b/modules/travel/back/models/travel-thermograph.json index 08eec2847..cc8e60aaf 100644 --- a/modules/travel/back/models/travel-thermograph.json +++ b/modules/travel/back/models/travel-thermograph.json @@ -1,6 +1,9 @@ { "name": "TravelThermograph", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "travelThermograph" diff --git a/modules/travel/back/models/travel.json b/modules/travel/back/models/travel.json index 95d458121..701894a76 100644 --- a/modules/travel/back/models/travel.json +++ b/modules/travel/back/models/travel.json @@ -1,6 +1,9 @@ { "name": "Travel", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "travel" diff --git a/modules/worker/back/methods/calendar/absences.js b/modules/worker/back/methods/calendar/absences.js index 8420ed770..8a5d18987 100644 --- a/modules/worker/back/methods/calendar/absences.js +++ b/modules/worker/back/methods/calendar/absences.js @@ -39,6 +39,7 @@ module.exports = Self => { started.setFullYear(year); started.setMonth(0); started.setDate(1); + started.setHours(0, 0, 0, 0); const ended = Date.vnNew(); ended.setFullYear(year); diff --git a/modules/worker/back/methods/worker-dms/removeFile.js b/modules/worker/back/methods/worker-dms/removeFile.js index b441c56ce..8eb6c05fd 100644 --- a/modules/worker/back/methods/worker-dms/removeFile.js +++ b/modules/worker/back/methods/worker-dms/removeFile.js @@ -18,13 +18,35 @@ module.exports = Self => { } }); - Self.removeFile = async(ctx, id) => { - const models = Self.app.models; - const workerDms = await Self.findById(id); + Self.removeFile = async(ctx, dmsFk, options) => { + const myOptions = {}; + let tx; + if (typeof options == 'object') + Object.assign(myOptions, options); - await models.Dms.removeFile(ctx, workerDms.dmsFk); + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } - return workerDms.destroy(); + try { + const WorkerDms = await Self.findOne({ + where: {document: dmsFk} + }, myOptions); + + const targetDms = await Self.app.models.Dms.removeFile(ctx, dmsFk, myOptions); + + if (!targetDms || !WorkerDms) + throw new UserError('Try again'); + + const workerDmsDestroyed = await WorkerDms.destroy(myOptions); + if (tx) await tx.commit(); + + return workerDmsDestroyed; + } catch (e) { + await tx.rollback(); + throw e; + } }; }; diff --git a/modules/worker/back/methods/worker-time-control/addTimeEntry.js b/modules/worker/back/methods/worker-time-control/addTimeEntry.js index cc652fb90..5dbac51ca 100644 --- a/modules/worker/back/methods/worker-time-control/addTimeEntry.js +++ b/modules/worker/back/methods/worker-time-control/addTimeEntry.js @@ -43,16 +43,9 @@ module.exports = Self => { const isTeamBoss = await models.ACL.checkAccessAcl(ctx, 'Worker', 'isTeamBoss', 'WRITE'); const isHimself = userId == workerId; - if (!isSubordinate || (isSubordinate && isHimself && !isTeamBoss)) + if (!isSubordinate || (isHimself && !isTeamBoss)) throw new UserError(`You don't have enough privileges`); - query = `CALL vn.workerTimeControl_clockIn(?,?,?)`; - const [response] = await Self.rawSql(query, [workerId, args.timed, args.direction], myOptions); - if (response[0] && response[0].error) - throw new UserError(response[0].error); - - await models.WorkerTimeControl.resendWeeklyHourEmail(ctx, workerId, args.timed, myOptions); - - return response; + return Self.clockIn(workerId, args.timed, args.direction, myOptions); }; }; diff --git a/modules/worker/back/methods/worker-time-control/clockIn.js b/modules/worker/back/methods/worker-time-control/clockIn.js new file mode 100644 index 000000000..44e0c547a --- /dev/null +++ b/modules/worker/back/methods/worker-time-control/clockIn.js @@ -0,0 +1,45 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethod('clockIn', { + description: 'Check if the employee can clock in', + accessType: 'WRITE', + accepts: [ + { + arg: 'workerFk', + type: 'number', + required: true, + }, + { + arg: 'timed', + type: 'date' + }, + { + arg: 'direction', + type: 'string' + }, + + ], + http: { + path: `/clockIn`, + verb: 'POST' + }, + returns: { + type: 'Object', + root: true + } + }); + + Self.clockIn = async(workerFk, timed, direction, options) => { + const myOptions = {}; + if (typeof options == 'object') + Object.assign(myOptions, options); + + const query = 'CALL vn.workerTimeControl_clockIn(?, ?, ?)'; + const [[response]] = await Self.rawSql(query, [workerFk, timed, direction], myOptions); + if (response && response.error) + throw new UserError(response.error); + + return response; + }; +}; diff --git a/modules/worker/back/methods/worker-time-control/getClockIn.js b/modules/worker/back/methods/worker-time-control/getClockIn.js new file mode 100644 index 000000000..470700643 --- /dev/null +++ b/modules/worker/back/methods/worker-time-control/getClockIn.js @@ -0,0 +1,32 @@ +module.exports = Self => { + Self.remoteMethod('getClockIn', { + description: 'Shows the clockings for each day, in columns per day', + accessType: 'READ', + accepts: [ + { + arg: 'workerFk', + type: 'int', + required: true, + }, + + ], + http: { + path: `/getClockIn`, + verb: 'GET' + }, + returns: { + type: ['Object'], + root: true + }, + }); + + Self.getClockIn = async(workerFk, options) => { + const myOptions = {}; + if (typeof options == 'object') + Object.assign(myOptions, options); + + const query = `CALL vn.workerTimeControl_getClockIn(?, ?)`; + const [result] = await Self.rawSql(query, [workerFk, Date.vnNew()], myOptions); + return result; + }; +}; diff --git a/modules/worker/back/methods/worker-time-control/login.js b/modules/worker/back/methods/worker-time-control/login.js new file mode 100644 index 000000000..9aa4bd145 --- /dev/null +++ b/modules/worker/back/methods/worker-time-control/login.js @@ -0,0 +1,35 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('login', { + description: 'Consult the user\'s information and the buttons that must be activated after logging in', + accessType: 'READ', + accepts: [ + { + arg: 'pin', + type: 'string', + required: true + }, + ], + returns: { + type: 'Object', + root: true + }, + http: { + path: `/login`, + verb: 'POST' + } + }); + + Self.login = async(ctx, pin, options) => { + const myOptions = {}; + const $t = ctx.req.__; + if (typeof options == 'object') + Object.assign(myOptions, options); + + const query = `CALL vn.workerTimeControl_login(?)`; + const [[user]] = await Self.rawSql(query, [pin], myOptions); + if (!user) throw new UserError($t('Incorrect pin')); + return user; + }; +}; diff --git a/modules/worker/back/methods/worker-time-control/resendWeeklyHourEmail.js b/modules/worker/back/methods/worker-time-control/resendWeeklyHourEmail.js index 2452a29f9..896458455 100644 --- a/modules/worker/back/methods/worker-time-control/resendWeeklyHourEmail.js +++ b/modules/worker/back/methods/worker-time-control/resendWeeklyHourEmail.js @@ -1,6 +1,6 @@ module.exports = Self => { Self.remoteMethodCtx('resendWeeklyHourEmail', { - description: 'Adds a new hour registry', + description: 'Send the records for the week of the date provided', accessType: 'WRITE', accepts: [{ arg: 'id', diff --git a/modules/worker/back/methods/worker-time-control/specs/clockIn.spec.js b/modules/worker/back/methods/worker-time-control/specs/clockIn.spec.js new file mode 100644 index 000000000..9cd3ed1c0 --- /dev/null +++ b/modules/worker/back/methods/worker-time-control/specs/clockIn.spec.js @@ -0,0 +1,581 @@ +const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); + +describe('workerTimeControl clockIn()', () => { + const workerId = 9; + const salesBossId = 19; + const hankPymId = 1107; + const jessicaJonesId = 1110; + const HHRRId = 37; + const teamBossId = 13; + const monday = 1; + const tuesday = 2; + const thursday = 4; + const friday = 5; + const sunday = 7; + const inTime = '2001-01-01T00:00:00.000Z'; + const activeCtx = { + accessToken: {userId: 50}, + }; + const ctx = {req: activeCtx}; + + beforeAll(() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + }); + + it('should correctly clock in', async() => { + const tx = await models.WorkerTimeControl.beginTransaction({}); + + try { + const options = {transaction: tx}; + await models.WorkerTimeControl.clockIn(workerId, inTime, 'in', options); + const isClockIn = await models.WorkerTimeControl.findOne({ + where: { + userFk: workerId + } + }, options); + + expect(isClockIn).toBeDefined(); + expect(isClockIn.direction).toBe('in'); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + describe('as Role errors', () => { + it('should add if the current user is team boss and the target user is himself', async() => { + activeCtx.accessToken.userId = teamBossId; + const workerId = teamBossId; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + try { + const options = {transaction: tx}; + + const todayAtOne = Date.vnNew(); + todayAtOne.setHours(1, 0, 0, 0); + + ctx.args = {timed: todayAtOne, direction: 'in'}; + const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + expect(createdTimeEntry.id).toBeDefined(); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should delete the created time entry for the team boss as himself', async() => { + activeCtx.accessToken.userId = teamBossId; + const workerId = teamBossId; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + try { + const options = {transaction: tx}; + + const todayAtOne = Date.vnNew(); + todayAtOne.setHours(1, 0, 0, 0); + + ctx.args = {timed: todayAtOne, direction: 'in'}; + const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + expect(createdTimeEntry.id).toBeDefined(); + + await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options); + + const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options); + + expect(deletedTimeEntry).toBeNull(); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should edit the created time entry for the team boss as HHRR', async() => { + activeCtx.accessToken.userId = HHRRId; + const workerId = teamBossId; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + try { + const options = {transaction: tx}; + + const todayAtOne = Date.vnNew(); + todayAtOne.setHours(1, 0, 0, 0); + + ctx.args = {timed: todayAtOne, direction: 'in'}; + const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + expect(createdTimeEntry.id).toBeDefined(); + + ctx.args = {direction: 'out'}; + const updatedTimeEntry = await models.WorkerTimeControl.updateTimeEntry( + ctx, createdTimeEntry.id, options + ); + + expect(updatedTimeEntry.direction).toEqual('out'); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + }); + + describe('as saleBoss editor', () => { + let workerId; + beforeEach(() => { + activeCtx.accessToken.userId = salesBossId; + workerId = hankPymId; + }); + + it('should fail to add a time entry if the target user has an absence that day', async() => { + const date = Date.vnNew(); + date.setHours(8, 0, 0); + date.setDate(date.getDate() - 16); + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + try { + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`No está permitido trabajar`); + }); + + it('should fail to add a time entry for a worker without an existing contract', async() => { + const date = Date.vnNew(); + date.setFullYear(date.getFullYear() - 2); + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + try { + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`No hay un contrato en vigor`); + }); + + it('should fail to add a time entry for a worker without an existing contract and exceeding time', async() => { + let date = Date.vnNew(); + date.setDate(date.getDate() - 2); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + date.setHours(0, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date.setHours(20, 0, 1); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Superado el tiempo máximo entre entrada y salida`); + }); + + describe('direction errors', () => { + let date = Date.vnNew(); + date.setDate(date.getDate() - 1); + let error; + it('should throw an error when trying "in" direction twice', async() => { + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date.setHours(10, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Dirección incorrecta`); + }); + + it('should throw an error when trying "in" direction after insert "in" and "middle"', async() => { + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + date.setHours(9, 0, 0); + ctx.args = {timed: date, direction: 'middle'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date.setHours(10, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Dirección incorrecta`); + }); + + it('Should throw an error when trying "out" before closing a "middle" couple', async() => { + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(9, 0, 0); + ctx.args = {timed: date, direction: 'middle'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date.setHours(10, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Dirección incorrecta`); + }); + + it('should throw an error when trying "middle" after "out"', async() => { + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(9, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date.setHours(10, 0, 0); + ctx.args = {timed: date, direction: 'middle'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Dirección incorrecta`); + }); + + it('should throw an error when trying "out" direction twice', async() => { + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(9, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date.setHours(10, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Dirección incorrecta`); + }); + }); + + describe('12h rest', () => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; + it('should throw an error when the 12h rest is not fulfilled yet', async() => { + let date = Date.vnNew(); + date.setDate(date.getDate() - 2); + date = weekDay(date, monday); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(16, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date = weekDay(date, tuesday); + date.setHours(4, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Descanso diario`); + }); + + it('should not fail as the 12h rest is fulfilled', async() => { + let date = Date.vnNew(); + date.setDate(date.getDate() - 2); + date = weekDay(date, monday); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(16, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date = weekDay(date, tuesday); + date.setHours(4, 1, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error).not.toBeDefined; + }); + }); + + describe('for 3500kg drivers with enforced 9h rest', () => { + activeCtx.accessToken.userId = salesBossId; + const workerId = jessicaJonesId; + it('should throw an error when the 9h enforced rest is not fulfilled', async() => { + let date = Date.vnNew(); + date.setDate(date.getDate() - 2); + date = weekDay(date, monday); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(16, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date = weekDay(date, tuesday); + date.setHours(1, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Descanso diario`); + }); + + it('should not fail when the 9h enforced rest is fulfilled', async() => { + let date = Date.vnNew(); + date.setDate(date.getDate() - 2); + date = weekDay(date, monday); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(16, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date = weekDay(date, tuesday); + date.setHours(1, 1, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error).not.toBeDefined; + }); + }); + + describe('for 72h weekly rest', () => { + + it('should throw an error when work 11 consecutive days', async() => { + let date = Date.vnNew(); + date.setMonth(date.getMonth() - 1); + date.setDate(1); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + await populateWeek(date, monday, sunday, ctx, workerId, options); + date = nextWeek(date); + await populateWeek(date, monday, thursday, ctx, workerId, options); + try { + date = weekDay(date, friday); + date.setHours(10, 0, 1); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Descanso semanal`); + }); + + it('should throw an error when the 72h weekly rest is not fulfilled', async() => { + + let date = Date.vnNew(); + date.setMonth(date.getMonth() - 1); + date.setDate(1); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + await populateWeek(date, monday, sunday, ctx, workerId, options); + date = nextWeek(date); + await populateWeek(date, monday, thursday, ctx, workerId, options); + + try { + date = weekDay(date, sunday); + date.setHours(17, 59, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Descanso semanal`); + }); + + it('should throw an error when the 72h weekly rest is fulfilled', async() => { + + let date = Date.vnNew(); + date.setMonth(date.getMonth() - 1); + date.setDate(1); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + await populateWeek(date, monday, sunday, ctx, workerId, options); + date = nextWeek(date); + await populateWeek(date, monday, thursday, ctx, workerId, options); + + try { + date = weekDay(date, sunday); + date.setHours(18, 00, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error).not.toBeDefined; + }); + }); + }); +}); + +function weekDay(date, dayToSet) { + const currentDay = date.getDay(); + const distance = dayToSet - currentDay; + + date.setDate(date.getDate() + distance); + return date; +} + +function nextWeek(date) { + const sunday = 7; + const currentDay = date.getDay(); + let newDate = date; + if (currentDay != 0) + newDate = weekDay(date, sunday); + + newDate.setDate(newDate.getDate() + 1); + return newDate; +} + +async function populateWeek(date, dayStart, dayEnd, ctx, workerId, options) { + const dateStart = new Date(weekDay(date, dayStart)); + const dateEnd = new Date(dateStart); + dateEnd.setDate(dateStart.getDate() + dayEnd); + + for (let i = dayStart; i <= dayEnd; i++) { + dateStart.setHours(10, 0, 0); + ctx.args = {timed: dateStart, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + dateStart.setHours(18, 0, 0); + ctx.args = {timed: dateStart, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + dateStart.setDate(dateStart.getDate() + 1); + } +} diff --git a/modules/worker/back/methods/worker-time-control/specs/getClockIn.spec.js b/modules/worker/back/methods/worker-time-control/specs/getClockIn.spec.js new file mode 100644 index 000000000..d75ffac70 --- /dev/null +++ b/modules/worker/back/methods/worker-time-control/specs/getClockIn.spec.js @@ -0,0 +1,16 @@ +const models = require('vn-loopback/server/server').models; + +describe('workerTimeControl getClockIn()', () => { + it('should correctly get the timetable of a worker', async() => { + const response = await models.WorkerTimeControl.getClockIn(1106, {}); + + expect(response.length).toEqual(4); + const [inHrs, middleOutHrs, middleInHrs, outHrs] = response; + + expect(inHrs['0daysAgo']).toEqual('07:00'); + expect(middleOutHrs['0daysAgo']).toEqual('10:00'); + expect(middleInHrs['0daysAgo']).toEqual('10:20'); + expect(outHrs['0daysAgo']).toEqual('14:50'); + }); +}); + diff --git a/modules/worker/back/methods/worker-time-control/specs/login.spec.js b/modules/worker/back/methods/worker-time-control/specs/login.spec.js new file mode 100644 index 000000000..d9f2dbb39 --- /dev/null +++ b/modules/worker/back/methods/worker-time-control/specs/login.spec.js @@ -0,0 +1,34 @@ +const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); +const UserError = require('vn-loopback/util/user-error'); + +describe('workerTimeControl login()', () => { + let ctx; + beforeAll(async() => { + ctx = { + accessToken: {userId: 9}, + req: { + headers: {origin: 'http://localhost'}, + __: key => key + } + }; + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: ctx + }); + }); + + it('should correctly login', async() => { + const response = await models.WorkerTimeControl.login(ctx, 9); + + expect(response.name).toBe('developer'); + }); + + it('should throw UserError if pin is not provided', async() => { + try { + await models.WorkerTimeControl.login(ctx); + } catch (error) { + expect(error).toBeInstanceOf(UserError); + expect(error.message).toBe('Incorrect pin'); + } + }); +}); diff --git a/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js b/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js index 42ec6290a..92c01792f 100644 --- a/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js +++ b/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js @@ -3,18 +3,10 @@ const models = require('vn-loopback/server/server').models; const LoopBackContext = require('loopback-context'); describe('workerTimeControl add/delete timeEntry()', () => { - const HHRRId = 37; - const teamBossId = 13; const employeeId = 1; - const salesPersonId = 1106; const salesBossId = 19; const hankPymId = 1107; - const jessicaJonesId = 1110; const monday = 1; - const tuesday = 2; - const thursday = 4; - const friday = 5; - const sunday = 7; const activeCtx = { accessToken: {userId: 50}, }; @@ -61,560 +53,11 @@ describe('workerTimeControl add/delete timeEntry()', () => { expect(error.statusCode).toBe(400); expect(error.message).toBe(`You don't have enough privileges`); }); - - it('should add if the current user is team boss and the target user is himself', async() => { - activeCtx.accessToken.userId = teamBossId; - const workerId = teamBossId; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - try { - const options = {transaction: tx}; - - const todayAtOne = Date.vnNew(); - todayAtOne.setHours(1, 0, 0, 0); - - ctx.args = {timed: todayAtOne, direction: 'in'}; - const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - expect(createdTimeEntry.id).toBeDefined(); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); - - it('should try but fail to delete his own time entry', async() => { - activeCtx.accessToken.userId = salesBossId; - const workerId = salesBossId; - - let error; - const tx = await models.WorkerTimeControl.beginTransaction({}); - try { - const options = {transaction: tx}; - - const todayAtOne = Date.vnNew(); - todayAtOne.setHours(1, 0, 0, 0); - - ctx.args = {timed: todayAtOne, direction: 'in'}; - const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - activeCtx.accessToken.userId = salesPersonId; - await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options); - - await tx.rollback(); - } catch (e) { - error = e; - await tx.rollback(); - } - - expect(error).toBeDefined(); - expect(error.statusCode).toBe(400); - expect(error.message).toBe(`You don't have enough privileges`); - }); - - it('should delete the created time entry for the team boss as himself', async() => { - activeCtx.accessToken.userId = teamBossId; - const workerId = teamBossId; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - try { - const options = {transaction: tx}; - - const todayAtOne = Date.vnNew(); - todayAtOne.setHours(1, 0, 0, 0); - - ctx.args = {timed: todayAtOne, direction: 'in'}; - const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - expect(createdTimeEntry.id).toBeDefined(); - - await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options); - - const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options); - - expect(deletedTimeEntry).toBeNull(); - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); - - it('should delete the created time entry for the team boss as HHRR', async() => { - activeCtx.accessToken.userId = HHRRId; - const workerId = teamBossId; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - try { - const options = {transaction: tx}; - - const todayAtOne = Date.vnNew(); - todayAtOne.setHours(1, 0, 0, 0); - - ctx.args = {timed: todayAtOne, direction: 'in'}; - const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - expect(createdTimeEntry.id).toBeDefined(); - - await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options); - - const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options); - - expect(deletedTimeEntry).toBeNull(); - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); - - it('should edit the created time entry for the team boss as HHRR', async() => { - activeCtx.accessToken.userId = HHRRId; - const workerId = teamBossId; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - try { - const options = {transaction: tx}; - - const todayAtOne = Date.vnNew(); - todayAtOne.setHours(1, 0, 0, 0); - - ctx.args = {timed: todayAtOne, direction: 'in'}; - const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - expect(createdTimeEntry.id).toBeDefined(); - - ctx.args = {direction: 'out'}; - const updatedTimeEntry = await models.WorkerTimeControl.updateTimeEntry(ctx, createdTimeEntry.id, options); - - expect(updatedTimeEntry.direction).toEqual('out'); - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); }); describe('WorkerTimeControl_clockIn calls', () => { - let workerId; - beforeEach(() => { - activeCtx.accessToken.userId = salesBossId; - workerId = hankPymId; - }); - it('should fail to add a time entry if the target user has an absence that day', async() => { - const date = Date.vnNew(); - date.setHours(8, 0, 0); - date.setDate(date.getDate() - 16); - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - try { - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + beforeEach(() => activeCtx.accessToken.userId = salesBossId); - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`No está permitido trabajar`); - }); - - it('should fail to add a time entry for a worker without an existing contract', async() => { - const date = Date.vnNew(); - date.setFullYear(date.getFullYear() - 2); - - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - try { - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`No hay un contrato en vigor`); - }); - - it('should fail to add a time entry for a worker without an existing contract', async() => { - let date = Date.vnNew(); - date.setDate(date.getDate() - 2); - let error; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - date.setHours(0, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - try { - date.setHours(20,0, 1); - ctx.args = {timed: date, direction: 'out'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`Superado el tiempo máximo entre entrada y salida`); - }); - - describe('direction errors', () => { - let date = Date.vnNew(); - date.setDate(date.getDate() - 1); - let error; - it('should throw an error when trying "in" direction twice', async() => { - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - date.setHours(8, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - try { - date.setHours(10, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`Dirección incorrecta`); - }); - - it('should throw an error when trying "in" direction after insert "in" and "middle"', async() => { - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - date.setHours(8, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - date.setHours(9, 0, 0); - ctx.args = {timed: date, direction: 'middle'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - try { - date.setHours(10, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`Dirección incorrecta`); - }); - - it('Should throw an error when trying "out" before closing a "middle" couple', async() => { - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - date.setHours(8, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - date.setHours(9, 0, 0); - ctx.args = {timed: date, direction: 'middle'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - try { - date.setHours(10, 0, 0); - ctx.args = {timed: date, direction: 'out'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`Dirección incorrecta`); - }); - - it('should throw an error when trying "middle" after "out"', async() => { - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - date.setHours(8, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - date.setHours(9, 0, 0); - ctx.args = {timed: date, direction: 'out'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - try { - date.setHours(10, 0, 0); - ctx.args = {timed: date, direction: 'middle'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`Dirección incorrecta`); - }); - - it('should throw an error when trying "out" direction twice', async() => { - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - date.setHours(8, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - date.setHours(9, 0, 0); - ctx.args = {timed: date, direction: 'out'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - try { - date.setHours(10, 0, 0); - ctx.args = {timed: date, direction: 'out'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`Dirección incorrecta`); - }); - }); - - describe('12h rest', () => { - activeCtx.accessToken.userId = salesBossId; - const workerId = hankPymId; - it('should throw an error when the 12h rest is not fulfilled yet', async() => { - - let date = Date.vnNew(); - date.setDate(date.getDate() - 2); - date = weekDay(date, monday); - let error; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - date.setHours(8, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - date.setHours(16, 0, 0); - ctx.args = {timed: date, direction: 'out'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - try { - date = weekDay(date, tuesday); - date.setHours(4, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`Descanso diario`); - }); - - it('should not fail as the 12h rest is fulfilled', async() => { - let date = Date.vnNew(); - date.setDate(date.getDate() - 2); - date = weekDay(date, monday); - let error; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - date.setHours(8, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - date.setHours(16, 0, 0); - ctx.args = {timed: date, direction: 'out'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - try { - date = weekDay(date, tuesday); - date.setHours(4, 1, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error).not.toBeDefined; - }); - }); - - describe('for 3500kg drivers with enforced 9h rest', () => { - activeCtx.accessToken.userId = salesBossId; - const workerId = jessicaJonesId; - it('should throw an error when the 9h enforced rest is not fulfilled', async() => { - - let date = Date.vnNew(); - date.setDate(date.getDate() - 2); - date = weekDay(date, monday); - let error; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - date.setHours(8, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - date.setHours(16, 0, 0); - ctx.args = {timed: date, direction: 'out'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - try { - date = weekDay(date, tuesday); - date.setHours(1, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`Descanso diario`); - }); - - it('should not fail when the 9h enforced rest is fulfilled', async() => { - - let date = Date.vnNew(); - date.setDate(date.getDate() - 2); - date = weekDay(date, monday); - let error; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - date.setHours(8, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - date.setHours(16, 0, 0); - ctx.args = {timed: date, direction: 'out'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - try { - date = weekDay(date, tuesday); - date.setHours(1, 1, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error).not.toBeDefined; - }); - }); - - describe('for 72h weekly rest', () => { - - it('should throw an error when work 11 consecutive days', async() => { - let date = Date.vnNew(); - date.setMonth(date.getMonth() - 1); - date.setDate(1); - let error; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - await populateWeek(date, monday, sunday, ctx, workerId, options); - date = nextWeek(date); - await populateWeek(date, monday, thursday, ctx, workerId, options); - try { - date = weekDay(date, friday); - date.setHours(10, 0, 1); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`Descanso semanal`); - }); - - it('should throw an error when the 72h weekly rest is not fulfilled', async() => { - - let date = Date.vnNew(); - date.setMonth(date.getMonth() - 1); - date.setDate(1); - let error; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - await populateWeek(date, monday, sunday, ctx, workerId, options); - date = nextWeek(date); - await populateWeek(date, monday, thursday, ctx, workerId, options); - - try { - date = weekDay(date, sunday); - date.setHours(17, 59, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`Descanso semanal`); - }); - - it('should throw an error when the 72h weekly rest is fulfilled', async() => { - - let date = Date.vnNew(); - date.setMonth(date.getMonth() - 1); - date.setDate(1); - let error; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - await populateWeek(date, monday, sunday, ctx, workerId, options); - date = nextWeek(date); - await populateWeek(date, monday, thursday, ctx, workerId, options); - - try { - date = weekDay(date, sunday); - date.setHours(18, 00, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error).not.toBeDefined; - }); - }); - describe('WorkerTimeControl_calculate calls', () => { let dated = Date.vnNew(); dated.setDate(dated.getDate() - 7); @@ -836,25 +279,6 @@ function weekDay(date, dayToSet) { return date; } -function nextWeek(date) { - const sunday = 7; - const currentDay = date.getDay(); - let newDate = date; - if (currentDay != 0) - newDate = weekDay(date, sunday); - - newDate.setDate(newDate.getDate() + 1); - return newDate; -} - -function lastWeek(date) { - const monday = 1; - newDate = weekDay(date, monday); - - newDate.setDate(newDate.getDate() - 1); - return newDate; -} - async function populateWeek(date, dayStart, dayEnd, ctx, workerId, options) { const dateStart = new Date(weekDay(date, dayStart)); const dateEnd = new Date(dateStart); diff --git a/modules/worker/back/methods/worker/specs/activeWithInheritedRole.spec.js b/modules/worker/back/methods/worker/specs/activeWithInheritedRole.spec.js index da54f6adb..580e07351 100644 --- a/modules/worker/back/methods/worker/specs/activeWithInheritedRole.spec.js +++ b/modules/worker/back/methods/worker/specs/activeWithInheritedRole.spec.js @@ -3,7 +3,7 @@ const app = require('vn-loopback/server/server'); describe('Worker activeWithInheritedRole', () => { let allRolesCount; beforeAll(async() => { - allRolesCount = await app.models.Role.count(); + allRolesCount = await app.models.VnRole.count(); }); it('should return the workers with an inherited role of salesPerson', async() => { diff --git a/modules/worker/back/methods/worker/uploadFile.js b/modules/worker/back/methods/worker/uploadFile.js index 588cfe4bd..c3526cbb1 100644 --- a/modules/worker/back/methods/worker/uploadFile.js +++ b/modules/worker/back/methods/worker/uploadFile.js @@ -47,30 +47,33 @@ module.exports = Self => { }); Self.uploadFile = async(ctx, id) => { - const models = Self.app.models; - const promises = []; - const tx = await Self.beginTransaction({}); + const {Dms, WorkerDms} = Self.app.models; + const myOptions = {}; + let tx; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } try { - const options = {transaction: tx}; - - const uploadedFiles = await models.Dms.uploadFile(ctx, options); - uploadedFiles.forEach(dms => { - const newWorkerDms = models.WorkerDms.create({ + const uploadedFiles = await Dms.uploadFile(ctx, myOptions); + const promises = uploadedFiles.map(dms => + WorkerDms.create({ workerFk: id, dmsFk: dms.id - }, options); + }, myOptions)); + await Promise.all(promises); - promises.push(newWorkerDms); - }); - const resolvedPromises = await Promise.all(promises); + if (tx) await tx.commit(); - await tx.commit(); - - return resolvedPromises; - } catch (err) { - await tx.rollback(); - throw err; + return uploadedFiles; + } catch (e) { + if (tx) await tx.rollback(); + throw e; } }; }; diff --git a/modules/worker/back/models/device-production-user.json b/modules/worker/back/models/device-production-user.json index 3eeaae137..35a90fb50 100644 --- a/modules/worker/back/models/device-production-user.json +++ b/modules/worker/back/models/device-production-user.json @@ -1,6 +1,9 @@ { "name": "DeviceProductionUser", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "log": { "model": "DeviceProductionLog", "relation": "deviceProduction" diff --git a/modules/worker/back/models/device-production.json b/modules/worker/back/models/device-production.json index 35787cccc..f6e5105ad 100644 --- a/modules/worker/back/models/device-production.json +++ b/modules/worker/back/models/device-production.json @@ -1,6 +1,9 @@ { "name": "DeviceProduction", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "log": { "model": "DeviceProductionLog" }, diff --git a/modules/worker/back/models/worker-dms.json b/modules/worker/back/models/worker-dms.json index e9a9f1773..a5c0f30b2 100644 --- a/modules/worker/back/models/worker-dms.json +++ b/modules/worker/back/models/worker-dms.json @@ -1,6 +1,9 @@ { "name": "WorkerDms", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "workerDocument" diff --git a/modules/worker/back/models/worker-time-control.js b/modules/worker/back/models/worker-time-control.js index d5da680cf..1457c7a46 100644 --- a/modules/worker/back/models/worker-time-control.js +++ b/modules/worker/back/models/worker-time-control.js @@ -10,6 +10,9 @@ module.exports = Self => { require('../methods/worker-time-control/weeklyHourRecordEmail')(Self); require('../methods/worker-time-control/getMailStates')(Self); require('../methods/worker-time-control/resendWeeklyHourEmail')(Self); + require('../methods/worker-time-control/login')(Self); + require('../methods/worker-time-control/getClockIn')(Self); + require('../methods/worker-time-control/clockIn')(Self); Self.rewriteDbError(function(err) { if (err.code === 'ER_DUP_ENTRY') diff --git a/modules/worker/back/models/worker.json b/modules/worker/back/models/worker.json index 1a777fffe..ed430f133 100644 --- a/modules/worker/back/models/worker.json +++ b/modules/worker/back/models/worker.json @@ -1,7 +1,10 @@ { "name": "Worker", "description": "Company employees", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "worker" diff --git a/modules/zone/back/methods/zone/deleteZone.js b/modules/zone/back/methods/zone/deleteZone.js index 13d45428c..38e724cd3 100644 --- a/modules/zone/back/methods/zone/deleteZone.js +++ b/modules/zone/back/methods/zone/deleteZone.js @@ -64,7 +64,7 @@ module.exports = Self => { promises.push(models.TicketTracking.create({ ticketFk: ticket.id, stateFk: fixingState.id, - workerFk: worker.id + userFk: worker.id }, myOptions)); } } diff --git a/modules/zone/back/models/zone-event.json b/modules/zone/back/models/zone-event.json index e477dad6a..366bdec9d 100644 --- a/modules/zone/back/models/zone-event.json +++ b/modules/zone/back/models/zone-event.json @@ -1,6 +1,9 @@ { "name": "ZoneEvent", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "zoneEvent" diff --git a/modules/zone/back/models/zone-exclusion.json b/modules/zone/back/models/zone-exclusion.json index 00c9145cd..6e91a0a01 100644 --- a/modules/zone/back/models/zone-exclusion.json +++ b/modules/zone/back/models/zone-exclusion.json @@ -1,6 +1,9 @@ { "name": "ZoneExclusion", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "zoneExclusion" diff --git a/modules/zone/back/models/zone-included.json b/modules/zone/back/models/zone-included.json index deba73f34..a34e51091 100644 --- a/modules/zone/back/models/zone-included.json +++ b/modules/zone/back/models/zone-included.json @@ -1,6 +1,9 @@ { "name": "ZoneIncluded", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "zoneIncluded" diff --git a/modules/zone/back/models/zone-warehouse.json b/modules/zone/back/models/zone-warehouse.json index b222e95e7..c2cc989f0 100644 --- a/modules/zone/back/models/zone-warehouse.json +++ b/modules/zone/back/models/zone-warehouse.json @@ -1,6 +1,9 @@ { "name": "ZoneWarehouse", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "zoneWarehouse" diff --git a/modules/zone/back/models/zone.json b/modules/zone/back/models/zone.json index c86da3d3e..cf7371053 100644 --- a/modules/zone/back/models/zone.json +++ b/modules/zone/back/models/zone.json @@ -1,6 +1,9 @@ { "name": "Zone", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "zone" diff --git a/package-lock.json b/package-lock.json index 78ef93987..36e11dc8f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "salix-back", - "version": "23.50.01", + "version": "24.04.01", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "salix-back", - "version": "23.50.01", + "version": "24.04.01", "license": "GPL-3.0", "dependencies": { "axios": "^1.2.2", diff --git a/package.json b/package.json index 66a5cd2fa..f13c44162 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-back", - "version": "23.52.01", + "version": "24.04.01", "author": "Verdnatura Levante SL", "description": "Salix backend", "license": "GPL-3.0", diff --git a/print/templates/reports/invoice/invoice.html b/print/templates/reports/invoice/invoice.html index 45b6c3934..af1aaa423 100644 --- a/print/templates/reports/invoice/invoice.html +++ b/print/templates/reports/invoice/invoice.html @@ -16,6 +16,7 @@ {{$t('clientId')}} {{client.id}} + {{$t('invoice')}} @@ -80,6 +81,9 @@ {{formatDate(ticket.shipped, '%d-%m-%Y')}} + +

{{ticket.street}}

+

{{ticket.nickname}}

diff --git a/print/templates/reports/invoice/sql/tickets.sql b/print/templates/reports/invoice/sql/tickets.sql index a8385599c..35828c5de 100644 --- a/print/templates/reports/invoice/sql/tickets.sql +++ b/print/templates/reports/invoice/sql/tickets.sql @@ -2,9 +2,12 @@ SELECT t.id, t.shipped, t.nickname, - tto.description + tto.description, + t.addressFk, + a.street FROM invoiceOut io JOIN ticket t ON t.refFk = io.REF + JOIN `address` a ON a.id = t.addressFk LEFT JOIN observationType ot ON ot.code = 'invoiceOut' LEFT JOIN ticketObservation tto ON tto.ticketFk = t.id AND tto.observationTypeFk = ot.id