diff --git a/.vscode/settings.json b/.vscode/settings.json index 899dfc7884..03479d27ac 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,7 @@ // Carácter predeterminado de final de línea. "files.eol": "\n", "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, "search.useIgnoreFiles": false, "editor.defaultFormatter": "dbaeumer.vscode-eslint", @@ -11,8 +11,12 @@ "[javascript]": { "editor.defaultFormatter": "dbaeumer.vscode-eslint" }, + "[json]": { + "editor.defaultFormatter": "vscode.json-language-features" + }, "cSpell.words": [ "salix", - "fdescribe" + "fdescribe", + "Loggable" ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index d8d040fd67..69e93a309b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,23 +5,40 @@ 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). -## [2350.01] - 2023-12-14 +## [2404.01] - 2024-01-25 ### Added ### Changed ### Fixed +## [2402.01] - 2024-01-11 + +### Added +### Changed +### Fixed + +## [2400.01] - 2024-01-04 + +### Added +### Changed +### Fixed + +## [2350.01] - 2023-12-14 + +### Características Añadidas 🆕 +- **Tickets → Expediciones:** Añadido soporte para Viaexpress + + ## [2348.01] - 2023-11-30 -### Added -- (Ticket -> Adelantar) Permite mover lineas sin generar negativos -- (Ticket -> Adelantar) Permite modificar la fecha de los tickets -- (Trabajadores -> Notificaciones) Nueva sección (lilium) +### Características Añadidas 🆕 +- **Tickets → Adelantar:** Permite mover lineas sin generar negativos +- **Tickets → Adelantar:** Permite modificar la fecha de los tickets +- **Trabajadores → Notificaciones:** Nueva sección (lilium) -### Changed -### Fixed -- (Ticket -> RocketChat) Arreglada detección de cambios +### Correcciones 🛠️ +- **Tickets → RocketChat:** Arreglada detección de cambios ## [2346.01] - 2023-11-16 diff --git a/back/methods/dms/removeFile.js b/back/methods/dms/removeFile.js index a9ff368834..dc55b4d385 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 3b8c55a504..866499b665 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 7055bf8d5c..27be72295e 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 08cfaaae88..9f936c29ba 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 9850267d6b..d00085d8a9 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/sign-in.js b/back/methods/vn-user/sign-in.js index 9c2d568f4f..782046641e 100644 --- a/back/methods/vn-user/sign-in.js +++ b/back/methods/vn-user/sign-in.js @@ -49,13 +49,7 @@ module.exports = Self => { if (vnUser.twoFactor) throw new ForbiddenError(null, 'REQUIRES_2FA'); } - const validateLogin = await Self.validateLogin(user, password); - await Self.app.models.SignInLog.create({ - token: validateLogin.token, - userFk: vnUser.id, - ip: ctx.req.ip - }); - return validateLogin; + return Self.validateLogin(user, password, ctx); }; Self.passExpired = async vnUser => { diff --git a/back/methods/vn-user/specs/privileges.spec.js b/back/methods/vn-user/specs/privileges.spec.js index 3d25eecf97..04d9c09ff3 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 0000000000..8d9bbf11ce --- /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 ac2dfe2b23..a14dd301ef 100644 --- a/back/methods/vn-user/specs/sign-in.spec.js +++ b/back/methods/vn-user/specs/sign-in.spec.js @@ -2,7 +2,7 @@ const {models} = require('vn-loopback/server/server'); describe('VnUser Sign-in()', () => { const employeeId = 1; - const unauthCtx = { + const unAuthCtx = { req: { headers: {}, connection: { @@ -15,20 +15,18 @@ describe('VnUser Sign-in()', () => { const {VnUser, AccessToken, SignInLog} = models; describe('when credentials are correct', () => { it('should return the token if user uses email', async() => { - let login = await VnUser.signIn(unauthCtx, 'salesAssistant@mydomain.com', 'nightmare'); + let login = await VnUser.signIn(unAuthCtx, 'salesAssistant@mydomain.com', 'nightmare'); let accessToken = await AccessToken.findById(login.token); let ctx = {req: {accessToken: accessToken}}; let signInLog = await SignInLog.find({where: {token: accessToken.id}}); - expect(signInLog.length).toEqual(1); - expect(signInLog[0].userFk).toEqual(accessToken.userId); - expect(login.token).toBeDefined(); + expect(signInLog.length).toEqual(0); await VnUser.logout(ctx.req.accessToken.id); }); it('should return the token', async() => { - let login = await VnUser.signIn(unauthCtx, 'salesAssistant', 'nightmare'); + let login = await VnUser.signIn(unAuthCtx, 'salesAssistant', 'nightmare'); let accessToken = await AccessToken.findById(login.token); let ctx = {req: {accessToken: accessToken}}; @@ -38,7 +36,7 @@ describe('VnUser Sign-in()', () => { }); it('should return the token if the user doesnt exist but the client does', async() => { - let login = await VnUser.signIn(unauthCtx, 'PetterParker', 'nightmare'); + let login = await VnUser.signIn(unAuthCtx, 'PetterParker', 'nightmare'); let accessToken = await AccessToken.findById(login.token); let ctx = {req: {accessToken: accessToken}}; @@ -53,7 +51,7 @@ describe('VnUser Sign-in()', () => { let error; try { - await VnUser.signIn(unauthCtx, 'IDontExist', 'TotallyWrongPassword'); + await VnUser.signIn(unAuthCtx, 'IDontExist', 'TotallyWrongPassword'); } catch (e) { error = e; } @@ -74,7 +72,7 @@ describe('VnUser Sign-in()', () => { const options = {transaction: tx}; await employee.updateAttribute('twoFactor', 'email', options); - await VnUser.signIn(unauthCtx, 'employee', 'nightmare', options); + await VnUser.signIn(unAuthCtx, 'employee', 'nightmare', options); await tx.rollback(); } catch (e) { await tx.rollback(); @@ -99,7 +97,7 @@ describe('VnUser Sign-in()', () => { const options = {transaction: tx}; await employee.updateAttribute('passExpired', yesterday, options); - await VnUser.signIn(unauthCtx, 'employee', 'nightmare', options); + await VnUser.signIn(unAuthCtx, 'employee', 'nightmare', options); await tx.rollback(); } catch (e) { await tx.rollback(); diff --git a/back/methods/vn-user/validate-token.js b/back/methods/vn-user/validate-token.js deleted file mode 100644 index 7bccfe0b1d..0000000000 --- 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 ebc0e321b0..27a94498cf 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 de3d564b43..d3e96a986d 100644 --- a/back/models/dms-type.json +++ b/back/models/dms-type.json @@ -17,10 +17,6 @@ "type": "string", "required": true }, - "path": { - "type": "string", - "required": true - }, "code": { "type": "string", "required": true @@ -29,12 +25,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 0000000000..dbbf62f56e --- /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 186ab02084..ae0e0adcdd 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 a201879610..9ab85530f7 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 0000000000..c13cc7ae84 --- /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 52125dc012..5c5df1b9ef 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 0000000000..c7d7e172b8 --- /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 719e96cbf7..39e7008caf 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); @@ -124,20 +123,43 @@ module.exports = function(Self) { return email.send(); }); - Self.signInValidate = (user, userToken) => { + + /** + * Sign-in validate + * @param {String} user The user + * @param {Object} userToken Options + * @param {Object} token accessToken + * @param {Object} ctx context + */ + Self.signInValidate = async(user, userToken, token, ctx) => { const [[key, value]] = Object.entries(Self.userUses(user)); - if (userToken[key].toLowerCase().trim() !== value.toLowerCase().trim()) { - console.error('ERROR!!! - Signin with other user', userToken, user); - throw new UserError('Try again'); + const isOwner = Self.rawSql(`SELECT ? = ? `, [userToken[key], value]); + 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'); } }; - Self.validateLogin = async function(user, password) { + /** + * Validate login params + * @param {String} user The user + * @param {String} password + * @param {Object} ctx context + */ + Self.validateLogin = async function(user, password, ctx) { const loginInfo = Object.assign({password}, Self.userUses(user)); const token = await Self.login(loginInfo, 'user'); const userToken = await token.user.get(); - Self.signInValidate(user, userToken); + + if (ctx) + await Self.signInValidate(user, userToken, token, ctx); try { await Self.app.models.Account.sync(userToken.name, password); @@ -187,8 +209,8 @@ module.exports = function(Self) { }; Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls = - Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls - .filter(acl => acl.property != 'changePassword'); + Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls + .filter(acl => acl.property != 'changePassword'); Self.userSecurity = async(ctx, userId, options) => { const models = Self.app.models; @@ -226,26 +248,30 @@ module.exports = function(Self) { const env = process.env.NODE_ENV; const liliumUrl = await Self.app.models.Url.findOne({ - where: {and: [ - {appName: 'lilium'}, - {environment: env} - ]} + where: { + and: [ + {appName: 'lilium'}, + {environment: env} + ] + } }); class Mailer { async send(verifyOptions, cb) { - const url = new URL(verifyOptions.verifyHref); - if (process.env.NODE_ENV) url.port = ''; + try { + const url = new URL(verifyOptions.verifyHref); + if (process.env.NODE_ENV) url.port = ''; - const params = { - url: url.href, - recipient: verifyOptions.to - }; + const email = new Email('email-verify', { + url: url.href, + recipient: verifyOptions.to + }); + await email.send(); - const email = new Email('email-verify', params); - email.send(); - - cb(null, verifyOptions.to); + cb(null, verifyOptions.to); + } catch (err) { + cb(err); + } } } diff --git a/back/models/vn-user.json b/back/models/vn-user.json index 0f6daff5ac..6396036433 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 97e548d334..2678f6744b 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(); }); @@ -55,8 +59,8 @@ async function test() { const JunitReporter = require('jasmine-reporters'); jasmine.addReporter(new JunitReporter.JUnitXmlReporter()); - jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 90000; jasmine.exitOnCompletion = true; + jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 900000; } const backSpecs = [ diff --git a/db/Dockerfile b/db/Dockerfile index 8eeed35e59..0020e89502 100644 --- a/db/Dockerfile +++ b/db/Dockerfile @@ -1,4 +1,4 @@ -FROM mariadb:10.7.7 +FROM mariadb:10.11.6 ENV MYSQL_ROOT_PASSWORD root ENV TZ Europe/Madrid diff --git a/db/changes/234601/00-updateCourtesyTime.sql b/db/changes/234601/00-updateCourtesyTime.sql new file mode 100644 index 0000000000..4751b2e032 --- /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/234604/00-createSignInLogTable.sql b/db/changes/234802/00-createSignInLogTable.sql similarity index 91% rename from db/changes/234604/00-createSignInLogTable.sql rename to db/changes/234802/00-createSignInLogTable.sql index 5253481357..942f651c97 100644 --- a/db/changes/234604/00-createSignInLogTable.sql +++ b/db/changes/234802/00-createSignInLogTable.sql @@ -1,5 +1,4 @@ - -- -- Table structure for table `signInLog` -- Description: log to debug cross-login error @@ -13,7 +12,9 @@ CREATE TABLE `account`.`signInLog` ( `token` varchar(255) NOT NULL , `userFk` int(10) unsigned DEFAULT NULL, `creationDate` timestamp NULL DEFAULT current_timestamp(), + `userName` varchar(30) NOT NULL, `ip` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL, + `owner` tinyint(1) DEFAULT 1, KEY `userFk` (`userFk`), CONSTRAINT `signInLog_ibfk_1` FOREIGN KEY (`userFk`) REFERENCES `user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ); 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 0000000000..b08a44138e --- /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/240001/00-alterTable.sql b/db/changes/240001/00-alterTable.sql new file mode 100644 index 0000000000..b6974b715a --- /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 0000000000..2bc0f830dd --- /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/01-procedures.sql b/db/changes/240001/01-procedures.sql new file mode 100644 index 0000000000..3777708d57 --- /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/02-views.sql b/db/changes/240001/02-views.sql new file mode 100644 index 0000000000..86a1049a79 --- /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/240002/00-silexToSalix.sql b/db/changes/240002/00-silexToSalix.sql new file mode 100644 index 0000000000..bad430ac24 --- /dev/null +++ b/db/changes/240002/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/240201/.gitkeep b/db/changes/240201/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/db/changes/240201/00-aclsMails.sql b/db/changes/240201/00-aclsMails.sql new file mode 100644 index 0000000000..5cfea40301 --- /dev/null +++ b/db/changes/240201/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/240201/00-fixInvoiceCorrectionConstraintsName.sql b/db/changes/240201/00-fixInvoiceCorrectionConstraintsName.sql new file mode 100644 index 0000000000..426afea900 --- /dev/null +++ b/db/changes/240201/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/240201/00-getTaxBases.sql b/db/changes/240201/00-getTaxBases.sql new file mode 100644 index 0000000000..8bd1b745ac --- /dev/null +++ b/db/changes/240201/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/240201/00-tabletDocuware.sql b/db/changes/240201/00-tabletDocuware.sql new file mode 100644 index 0000000000..ffa0226b33 --- /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 0000000000..cd3cf7dd3a --- /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 0000000000..c3ddf5d96c --- /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/240201/00-truncateWhereSignInLog.sql b/db/changes/240201/00-truncateWhereSignInLog.sql new file mode 100644 index 0000000000..db5d8e25dd --- /dev/null +++ b/db/changes/240201/00-truncateWhereSignInLog.sql @@ -0,0 +1,2 @@ +DELETE FROM `account`.`signInLog` where owner <> FALSE; + diff --git a/db/changes/240201/01-functions.sql b/db/changes/240201/01-functions.sql new file mode 100644 index 0000000000..7bbe1f442c --- /dev/null +++ b/db/changes/240201/01-functions.sql @@ -0,0 +1,81 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` FUNCTION `vn`.`ticketPositionInPath`(vTicketId INT) + RETURNS varchar(10) CHARSET utf8mb3 COLLATE utf8mb3_general_ci + DETERMINISTIC +BEGIN + + DECLARE vRestTicketsMaxOrder INT; + DECLARE vRestTicketsMinOrder INT; + DECLARE vRestTicketsPacking INT; + DECLARE vMyProductionOrder INT; + DECLARE vPosition VARCHAR(10) DEFAULT 'MID'; + DECLARE vMyPath INT; + DECLARE vMyWarehouse INT; + DECLARE PACKING_ORDER INT; + DECLARE vExpeditionsCount INT; + DECLARE vIsValenciaPath BOOLEAN DEFAULT FALSE; + + + +SELECT `order` + INTO PACKING_ORDER + FROM state + WHERE code = 'PACKING'; + +SELECT t.routeFk, t.warehouseFk, IFNULL(ts.productionOrder,0) + INTO vMyPath, vMyWarehouse, vMyProductionOrder + FROM ticket t + LEFT JOIN ticketState ts on ts.ticketFk = t.id + WHERE t.id = vTicketId; + +SELECT (ag.`name` = 'VN_VALENCIA') + INTO vIsValenciaPath + FROM vn2008.Rutas r + JOIN vn2008.Agencias a on a.Id_Agencia = r.Id_Agencia + JOIN vn2008.agency ag on ag.agency_id = a.agency_id + WHERE r.Id_Ruta = vMyPath; + +IF vIsValenciaPath THEN -- Rutas Valencia + + SELECT COUNT(*) + INTO vExpeditionsCount + FROM expedition e + JOIN ticket t ON t.id = e.ticketFk + WHERE t.routeFk = vMyPath; + + SELECT MAX(ts.productionOrder), MIN(ts.productionOrder) + INTO vRestTicketsMaxOrder, vRestTicketsMinOrder + FROM ticket t + LEFT JOIN ticketState ts on t.id = ts.ticketFk + WHERE t.routeFk = vMyPath + AND t.warehouseFk = vMyWarehouse + AND t.id != vTicketid; + + SELECT COUNT(*) + INTO vRestTicketsPacking + FROM ticket t + LEFT JOIN ticketState ts on t.id = ts.ticketFk + WHERE ts.productionOrder = PACKING_ORDER + AND t.routeFk = vMyPath + AND t.warehouseFk = vMyWarehouse + AND t.id != vTicketid; + + IF vExpeditionsCount = 1 THEN + SET vPosition = 'FIRST'; + ELSEIF vRestTicketsMinOrder > PACKING_ORDER THEN + SET vPosition = 'LAST'; + ELSEIF vRestTicketsPacking THEN + SET vPosition = 'SHARED'; + ELSE + SET vPosition = 'MID'; + END IF; + +ELSE + SET vPosition = 'MID'; + +END IF; + +RETURN vPosition; + +END$$ +DELIMITER ; diff --git a/db/changes/240201/01-newHasAnyPositiveBase.sql b/db/changes/240201/01-newHasAnyPositiveBase.sql new file mode 100644 index 0000000000..c4edfaed0e --- /dev/null +++ b/db/changes/240201/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/240201/01-procedures.sql b/db/changes/240201/01-procedures.sql new file mode 100644 index 0000000000..ab52dbd1b4 --- /dev/null +++ b/db/changes/240201/01-procedures.sql @@ -0,0 +1,1788 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `hedera`.`item_getVisible`( + vWarehouse TINYINT, + vDate DATE, + vType INT, + vPrefix VARCHAR(255)) +BEGIN + +/** + * Gets visible items of the specified type at specified date. + * + * @param vWarehouse The warehouse id + * @param vDate The visible date + * @param vType The type id + * @param vPrefix The article prefix to filter or %NULL for all + * @return tmp.itemVisible Visible items + */ + DECLARE vPrefixLen SMALLINT; + DECLARE vFilter VARCHAR(255) DEFAULT NULL; + DECLARE vDateInv DATE DEFAULT vn.getInventoryDate(); + DECLARE EXIT HANDLER FOR 1114 + BEGIN + GET DIAGNOSTICS CONDITION 1 + @message = MESSAGE_TEXT; + CALL vn.mail_insert( + 'cau@verdnatura.es', + NULL, + CONCAT('hedera.item_getVisible error: ', @message), + CONCAT( + 'warehouse: ', IFNULL(vWarehouse, ''), + ', Fecha:', IFNULL(vDate, ''), + ', tipo: ', IFNULL(vType,''), + ', prefijo: ', IFNULL(vPrefix,''))); + RESIGNAL; + END; + SET vPrefixLen = IFNULL(LENGTH(vPrefix), 0) + 1; + + IF vPrefixLen > 1 THEN + SET vFilter = CONCAT(vPrefix, '%'); + END IF; + + DROP TEMPORARY TABLE IF EXISTS `filter`; + CREATE TEMPORARY TABLE `filter` + (INDEX (itemFk)) + ENGINE = MEMORY + SELECT id itemFk FROM vn.item + WHERE typeFk = vType + AND (vFilter IS NULL OR `name` LIKE vFilter); + + DROP TEMPORARY TABLE IF EXISTS currentStock; + CREATE TEMPORARY TABLE currentStock + (INDEX (itemFk)) + ENGINE = MEMORY + SELECT itemFk, SUM(quantity) quantity + FROM ( + SELECT b.itemFk, b.quantity + FROM vn.buy b + JOIN vn.entry e ON e.id = b.entryFk + JOIN vn.travel t ON t.id = e.travelFk + WHERE t.landed BETWEEN vDateInv AND vDate + AND t.warehouseInFk = vWarehouse + AND NOT e.isRaid + UNION ALL + SELECT b.itemFk, -b.quantity + FROM vn.buy b + JOIN vn.entry e ON e.id = b.entryFk + JOIN vn.travel t ON t.id = e.travelFk + WHERE t.shipped BETWEEN vDateInv AND util.VN_CURDATE() + AND t.warehouseOutFk = vWarehouse + AND NOT e.isRaid + AND t.isDelivered + UNION ALL + SELECT m.itemFk, -m.quantity + FROM vn.sale m + JOIN vn.ticket t ON t.id = m.ticketFk + JOIN vn.ticketState s ON s.ticketFk = t.id + WHERE t.shipped BETWEEN vDateInv AND util.VN_CURDATE() + AND t.warehouseFk = vWarehouse + AND s.alertLevel = 3 + ) t + GROUP BY itemFk + HAVING quantity > 0; + + DROP TEMPORARY TABLE IF EXISTS tmp; + CREATE TEMPORARY TABLE tmp + (INDEX (itemFk)) + ENGINE = MEMORY + SELECT * + FROM ( + SELECT b.itemFk, b.packagingFk, b.packing + FROM vn.buy b + JOIN vn.entry e ON e.id = b.entryFk + JOIN vn.travel t ON t.id = e.travelFk + WHERE t.landed BETWEEN vDateInv AND vDate + AND NOT b.isIgnored + AND b.price2 >= 0 + AND b.packagingFk IS NOT NULL + ORDER BY t.warehouseInFk = vWarehouse DESC, t.landed DESC + LIMIT 10000000000000000000 + ) t GROUP BY itemFk; + + DROP TEMPORARY TABLE IF EXISTS tmp.itemVisible; + CREATE TEMPORARY TABLE tmp.itemVisible + ENGINE = MEMORY + SELECT i.id Id_Article, + SUBSTRING(i.`name`, vPrefixLen) Article, + t.packing, p.id Id_Cubo, + IF(p.depth > 0, p.depth, 0) depth, p.width, p.height, + CEIL(s.quantity / t.packing) etiquetas + FROM vn.item i + JOIN `filter` f ON f.itemFk = i.id + JOIN currentStock s ON s.itemFk = i.id + LEFT JOIN tmp t ON t.itemFk = i.id + LEFT JOIN vn.packaging p ON p.id = t.packagingFk + WHERE CEIL(s.quantity / t.packing) > 0 + -- FIXME: Column Cubos.box not included in view vn.packaging + /* AND p.box */ ; + + DROP TEMPORARY TABLE + `filter`, + currentStock, + tmp; +END$$ +DELIMITER ; + +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.ticketFk = 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, + workerFk = 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.ticketFk + 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`.`itemShelvingRadar`(vSectorFk INT) +proc:BEGIN + + DECLARE vCalcVisibleFk INT; + DECLARE vCalcAvailableFk INT; + DECLARE hasFatherSector BOOLEAN; + DECLARE vBuyerFk INT DEFAULT 0; + DECLARE vWarehouseFk INT DEFAULT 0; + DECLARE vSonSectorFk INT; + DECLARE vWorkerFk INT; + + SELECT s.workerFk + INTO vWorkerFk + FROM vn.sector s + WHERE s.id = vSectorFk; + + SELECT w.id, s.warehouseFk INTO vBuyerFk, vWarehouseFk + FROM vn.worker w + JOIN vn.sector s ON s.code = w.code + WHERE s.id = vSectorFk; + + SELECT s.id INTO vSectorFk + FROM vn.sector s + WHERE s.warehouseFk = vWarehouseFk + AND s.isMain; + + SELECT COUNT(*) INTO hasFatherSector + FROM vn.sector + WHERE sonFk = vSectorFk; + + SELECT warehouseFk, sonFk INTO vWarehouseFk, vSonSectorFk + FROM vn.sector + WHERE id = vSectorFk; + + CALL cache.visible_refresh(vCalcVisibleFk, TRUE, vWarehouseFk); + CALL cache.available_refresh(vCalcAvailableFk, FALSE, vWarehouseFk, util.VN_CURDATE()); + + DROP TEMPORARY TABLE IF EXISTS tmp.itemShelvingRadar; + + IF hasFatherSector THEN + CREATE TEMPORARY TABLE tmp.itemShelvingRadar + (PRIMARY KEY (itemFk)) + ENGINE = MEMORY + SELECT * + FROM ( + SELECT iss.itemFk, + i.longName, + i.size, + i.subName producer, + IFNULL(a.available,0) available, + SUM(IF(s.sonFk = vSectorFk, IFNULL(iss.visible,0), 0)) upstairs, + SUM(IF(iss.sectorFk = vSectorFk, IFNULL(iss.visible,0), 0)) downstairs, + IF(it.isPackaging, NULL, IFNULL(v.visible,0)) as visible, + vSectorFk sectorFk + FROM vn.itemShelvingStock iss + JOIN vn.sector s ON s.id = iss.sectorFk + JOIN vn.item i on i.id = iss.itemFk + JOIN vn.itemType it ON it.id = i.typeFk AND vBuyerFk IN (0,it.workerFk) + LEFT JOIN cache.available a ON a.item_id = iss.itemFk AND a.calc_id = vCalcAvailableFk + LEFT JOIN cache.visible v ON v.item_id = iss.itemFk AND v.calc_id = vCalcVisibleFk + WHERE vSectorFk IN (iss.sectorFk, s.sonFk) + GROUP BY iss.itemFk + + UNION ALL + + SELECT v.item_id, + i.longName, + i.size, + i.subName producer, + IFNULL(a.available,0) as available, + 0 upstairs, + 0 downstairs, + IF(it.isPackaging, NULL, v.visible) visible, + vSectorFk as sectorFk + FROM cache.visible v + JOIN vn.item i on i.id = v.item_id + JOIN vn.itemType it ON it.id = i.typeFk AND vBuyerFk IN (0,it.workerFk) + LEFT JOIN vn.itemShelvingStock iss ON iss.itemFk = v.item_id AND iss.warehouseFk = vWarehouseFk + LEFT JOIN cache.available a ON a.item_id = v.item_id AND a.calc_id = vCalcAvailableFk + WHERE v.calc_id = vCalcVisibleFk + AND iss.itemFk IS NULL + AND it.isInventory + ) sub GROUP BY itemFk; + + SELECT ishr.*, + CAST(visible - upstairs - downstairs AS DECIMAL(10,0)) AS nicho, + CAST(downstairs - IFNULL(notPickedYed,0) AS DECIMAL(10,0)) as pendiente + FROM tmp.itemShelvingRadar ishr + JOIN vn.item i ON i.id = ishr.itemFk + LEFT JOIN (SELECT s.itemFk, sum(s.quantity) as notPickedYed + FROM vn.ticket t + JOIN vn.ticketStateToday tst ON tst.ticketFk = t.id + JOIN vn.sale s ON s.ticketFk = t.id + WHERE t.warehouseFk = vWarehouseFk + AND tst.alertLevel = 0 + GROUP BY s.itemFk + ) sub ON sub.itemFk = ishr.itemFk + ORDER BY i.typeFk, i.longName; + ELSE + CREATE TEMPORARY TABLE tmp.itemShelvingRadar + (PRIMARY KEY (itemFk)) + ENGINE = MEMORY + SELECT iss.itemFk, + 0 `hour`, + 0 `minute`, + '--' itemPlacementCode, + i.longName, + i.size, + i.subName producer, + i.upToDown, + IFNULL(a.available,0) available, + IFNULL(v.visible - iss.visible,0) dayEndVisible, + IFNULL(v.visible - iss.visible,0) firstNegative, + IFNULL(v.visible - iss.visible,0) itemPlacementVisible, + IFNULL(i.minimum * b.packing,0) itemPlacementSize, + ips.onTheWay, + iss.visible itemShelvingStock, + IFNULL(v.visible,0) visible, + b.isPickedOff, + iss.sectorFk + FROM vn.itemShelvingStock iss + JOIN vn.item i on i.id = iss.itemFk + LEFT JOIN cache.last_buy lb ON lb.item_id = iss.itemFk AND lb.warehouse_id = vWarehouseFk + LEFT JOIN vn.buy b ON b.id = lb.buy_id + LEFT JOIN cache.available a ON a.item_id = iss.itemFk AND a.calc_id = vCalcAvailableFk + LEFT JOIN cache.visible v ON v.item_id = iss.itemFk AND v.calc_id = vCalcVisibleFk + LEFT JOIN (SELECT itemFk, sum(saldo) as onTheWay + FROM vn.itemPlacementSupplyList + WHERE saldo > 0 + GROUP BY itemFk + ) ips ON ips.itemFk = i.id + WHERE IFNULL(iss.sectorFk,0) IN (0, vSectorFk) + OR iss.sectorFk = vSectorFk; + + DROP TEMPORARY TABLE IF EXISTS tmp.itemOutTime; + CREATE TEMPORARY TABLE tmp.itemOutTime + SELECT *,SUM(amount) quantity + FROM + (SELECT item_id itemFk, + amount, + IF(HOUR(t.shipped), HOUR(t.shipped), HOUR(z.`hour`)) as hours, + IF(MINUTE(t.shipped), MINUTE(t.shipped), MINUTE(z.`hour`)) as minutes + FROM vn2008.item_out io + JOIN tmp.itemShelvingRadar isr ON isr.itemFk = io.item_id + JOIN vn.ticket t on t.id= io.ticketFk + JOIN vn.ticketState ts on ts.ticketFk = io.ticketFk + JOIN vn.state s ON s.id = ts.stateFk + LEFT JOIN vn.zone z ON z.id = t.zoneFk + LEFT JOIN (SELECT DISTINCT saleFk + FROM vn.saleTracking st + WHERE st.created > util.VN_CURDATE() + AND st.isChecked + ) stPrevious ON `stPrevious`.`saleFk` = io.saleFk + WHERE t.warehouseFk = vWarehouseFk + AND s.isPicked = 0 + AND NOT io.Reservado + AND stPrevious.saleFk IS NULL + AND io.dat >= util.VN_CURDATE() + AND io.dat < util.VN_CURDATE() + INTERVAL 1 DAY + ) sub + GROUP BY itemFk, hours, minutes; + + INSERT INTO tmp.itemShelvingRadar (itemFk) + SELECT itemFk FROM tmp.itemOutTime + ON DUPLICATE KEY UPDATE dayEndVisible = dayEndVisible + quantity, + firstNegative = if (firstNegative < 0, firstNegative, firstNegative + quantity), + `hour` = ifnull(if (firstNegative > 0 , `hour`, hours),0), + `minute` = ifnull(if (firstNegative > 0, `minute`, minutes),0); + + UPDATE tmp.itemShelvingRadar isr + JOIN (SELECT s.itemFk, sum(s.quantity) amount + FROM sale s + JOIN ticket t ON t.id = s.ticketFk + JOIN ticketLastState tls ON tls.ticketFk = t.id + WHERE t.shipped BETWEEN util.VN_CURDATE() AND util.dayend(util.VN_CURDATE()) + AND tls.name = 'Prep Camara' + GROUP BY s.itemFk) sub ON sub.itemFk = isr.itemFk + SET isr.dayEndVisible = dayEndVisible + sub.amount, + firstNegative = firstNegative + sub.amount; + + SELECT * FROM tmp.itemShelvingRadar; + END IF; + + DROP TEMPORARY TABLE tmp.itemShelvingRadar; + +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`item_getBalance`( + vItemFk INT, + vWarehouseFk INT, + vDate DATETIME +) +BEGIN +/** + * @vItemFk item a buscar + * @vWarehouseFk almacen donde buscar + * @vDate Si la fecha es null, muestra el histórico desde el inventario. + * Si la fecha no es null, muestra histórico desde la fecha pasada. + */ + DECLARE vDateInventory DATETIME; + + IF vDate IS NULL THEN + SELECT inventoried INTO vDateInventory + FROM config; + ELSE + SELECT mockUtcTime INTO vDateInventory + FROM util.config; + END IF; + + CREATE OR REPLACE TEMPORARY TABLE tItemDiary( + shipped DATE, + `in` INT(11), + `out` INT(11), + alertLevel INT(11), + stateName VARCHAR(20), + `name` VARCHAR(50), + reference VARCHAR(50), + origin INT(11), + clientFk INT(11), + isPicked INT(11), + isTicket TINYINT(1), + lineFk INT(11), + `order` TINYINT(3) UNSIGNED, + clientType VARCHAR(20), + claimFk INT(10) UNSIGNED, + inventorySupplierFk INT(10) + ); + + INSERT INTO tItemDiary + SELECT tr.landed shipped, + b.quantity `in`, + NULL `out`, + st.alertLevel , + st.name stateName, + s.name `name`, + e.invoiceNumber reference, + e.id origin, + s.id clientFk, + IF(st.`code` = 'DELIVERED', TRUE, FALSE) isPicked, + FALSE isTicket, + b.id lineFk, + NULL `order`, + NULL clientType, + NULL claimFk, + ec.inventorySupplierFk + FROM buy b + JOIN entry e ON e.id = b.entryFk + JOIN travel tr ON tr.id = e.travelFk + JOIN supplier s ON s.id = e.supplierFk + JOIN state st ON st.`code` = IF( tr.landed < util.VN_CURDATE() + OR (util.VN_CURDATE() AND tr.isReceived), + 'DELIVERED', + 'FREE') + JOIN entryConfig ec + WHERE tr.landed >= vDateInventory + AND vWarehouseFk = tr.warehouseInFk + AND (s.id <> ec.inventorySupplierFk OR vDate IS NULL) + AND b.itemFk = vItemFk + AND e.isExcludedFromAvailable = FALSE + AND e.isRaid = FALSE + UNION ALL + SELECT tr.shipped, + NULL, + b.quantity, + st.alertLevel, + st.name, + s.name, + e.invoiceNumber, + e.id, + s.id, + IF(st.`code` = 'DELIVERED' , TRUE, FALSE), + FALSE, + b.id, + NULL, + NULL, + NULL, + ec.inventorySupplierFk + FROM buy b + JOIN entry e ON e.id = b.entryFk + JOIN travel tr ON tr.id = e.travelFk + JOIN warehouse w ON w.id = tr.warehouseOutFk + JOIN supplier s ON s.id = e.supplierFk + JOIN state st ON st.`code` = IF(tr.shipped < util.VN_CURDATE() + OR (tr.shipped = util.VN_CURDATE() AND tr.isReceived), + 'DELIVERED', + 'FREE') + JOIN entryConfig ec + WHERE tr.shipped >= vDateInventory + AND vWarehouseFk = tr.warehouseOutFk + AND (s.id <> ec.inventorySupplierFk OR vDate IS NULL) + AND b.itemFk = vItemFk + AND e.isExcludedFromAvailable = FALSE + AND w.isFeedStock = FALSE + AND e.isRaid = FALSE + UNION ALL + SELECT DATE(t.shipped), + NULL, + s.quantity, + st2.alertLevel, + st2.name, + t.nickname, + t.refFk, + t.id, + t.clientFk, + stk.id, + TRUE, + s.id, + st.`order`, + ct.`code`, + cb.claimFk, + NULL + FROM sale s + JOIN ticket t ON t.id = s.ticketFk + LEFT JOIN ticketState ts ON ts.ticketFk = t.id + LEFT JOIN state st ON st.`code` = ts.`code` + JOIN client c ON c.id = t.clientFk + JOIN clientType ct ON ct.id = c.clientTypeFk + JOIN state st2 ON st2.`code` = IF(t.shipped < util.VN_CURDATE(), + 'DELIVERED', + IF (t.shipped > util.dayEnd(util.VN_CURDATE()), + 'FREE', + IFNULL(ts.code, 'FREE'))) + LEFT JOIN state stPrep ON stPrep.`code` = 'PREPARED' + LEFT JOIN saleTracking stk ON stk.saleFk = s.id + AND stk.stateFk = stPrep.id + LEFT JOIN claimBeginning cb ON s.id = cb.saleFk + WHERE t.shipped >= vDateInventory + AND s.itemFk = vItemFk + AND vWarehouseFk =t.warehouseFk + ORDER BY shipped, + (inventorySupplierFk = clientFk) DESC, + alertLevel DESC, + isTicket, + `order` DESC, + isPicked DESC, + `in` DESC, + `out` DESC; + + IF vDate IS NULL THEN + + SET @a := 0; + SET @currentLineFk := 0; + SET @shipped := ''; + + SELECT DATE(@shipped:= shipped) shipped, + alertLevel, + stateName, + origin, + reference, + clientFk, + name, + `in` invalue, + `out`, + @a := @a + IFNULL(`in`, 0) - IFNULL(`out`, 0) balance, + @currentLineFk := IF (@shipped < util.VN_CURDATE() + OR (@shipped = util.VN_CURDATE() AND (isPicked OR a.`code` >= 'ON_PREPARATION')), + lineFk, + @currentLineFk) lastPreparedLineFk, + isTicket, + lineFk, + isPicked, + clientType, + claimFk + FROM tItemDiary + LEFT JOIN alertLevel a ON a.id = tItemDiary.alertLevel; + + ELSE + SELECT SUM(`in`) - SUM(`out`) INTO @a + FROM tItemDiary + WHERE shipped < vDate; + + SELECT vDate shipped, + 0 alertLevel, + 0 stateName, + 0 origin, + '' reference, + 0 clientFk, + 'Inventario calculado', + @a invalue, + NULL `out`, + @a balance, + 0 lastPreparedLineFk, + 0 isTicket, + 0 lineFk, + 0 isPicked, + 0 clientType, + 0 claimFk + UNION ALL + SELECT shipped, + alertlevel, + stateName, + origin, + reference, + clientFk, + name, `in`, + `out`, + @a := @a + IFNULL(`in`, 0) - IFNULL(`out`, 0), + 0, + isTicket, + lineFk, + isPicked, + clientType, + claimFk + FROM tItemDiary + WHERE shipped >= vDate; + END IF; + + DROP TEMPORARY TABLE tItemDiary; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`productionControl`( + vWarehouseFk INT, + vScopeDays INT +) +proc: BEGIN +/** + * Devuelve un listado de tickets con parámetros relativos a la producción de los días en rango. + * + * @param vWarehouseFk Identificador de warehouse + * @param vScopeDays Número de días desde hoy en adelante que entran en el cálculo. + * + * @return Table tmp.productionBuffer + */ + DECLARE vEndingDate DATETIME; + DECLARE vIsTodayRelative BOOLEAN; + + SELECT util.dayEnd(util.VN_CURDATE()) + INTERVAL LEAST(vScopeDays, maxProductionScopeDays) DAY + INTO vEndingDate + FROM productionConfig; + + SELECT isTodayRelative INTO vIsTodayRelative + FROM worker + WHERE id = getUser(); -- Cambiar por account.myUser_getId(), falta dar permisos + + CALL prepareTicketList(util.yesterday(), vEndingDate); + + CREATE OR REPLACE TEMPORARY TABLE tmp.ticket + SELECT * FROM tmp.productionTicket; + + CALL prepareClientList(); + + CREATE OR REPLACE TEMPORARY TABLE tmp.sale_getProblems + (INDEX (ticketFk)) ENGINE = MEMORY + SELECT tt.ticketFk, tt.clientFk, t.warehouseFk, t.shipped + FROM tmp.productionTicket tt + JOIN ticket t ON t.id = tt.ticketFk; + + CALL ticket_getProblems(vIsTodayRelative); + + CREATE OR REPLACE TEMPORARY TABLE tmp.productionBuffer + (PRIMARY KEY(ticketFk), previaParking VARCHAR(255)) + ENGINE = MEMORY + SELECT tt.ticketFk, + tt.clientFk, + t.warehouseFk, + t.nickname, + t.packages, + IF(HOUR(t.shipped), HOUR(t.shipped), COALESCE(HOUR(zc.hour),HOUR(z.hour))) HH, + COALESCE(HOUR(zc.hour), HOUR(z.hour)) Departure, + COALESCE(MINUTE(t.shipped), MINUTE(zc.hour), MINUTE(z.hour)) mm, + t.routeFk, + IF(dm.code = 'DELIVERY', z.`id`, 0) zona, + t.nickname addressNickname, + a.postalCode, + a.city, + p.name province, + CONCAT(z.`name`,' ',IFNULL(RIGHT(t.routeFk,3),'')) agency, + am.id agencyModeFk, + 0 `lines`, + CAST( 0 AS DECIMAL(5,2)) m3, + CAST( 0 AS DECIMAL(5,2)) preparationRate, + "" problem, + IFNULL(tls.state,2) state, + w.code workerCode, + DATE(t.shipped) shipped, + wk.code salesPersonCode, + p.id provinceFk, + tls.productionOrder, + IFNULL(tls.alertLevel, 0) alertLevel, + t.isBoxed palletized, + IF(rm.isPickingAllowed, rm.bufferFk, NULL) ubicacion, + tlu.lastUpdated, + IFNULL(st.graphCategory, 0) graphCategory, + pk.code parking, + 0 H, + 0 V, + 0 N, + st.isOk, + ag.isOwn, + rm.bufferFk + FROM tmp.productionTicket tt + JOIN ticket t ON tt.ticketFk = t.id + LEFT JOIN ticketStateToday tst ON tst.ticket = t.id + LEFT JOIN state st ON st.id = tst.state + LEFT JOIN client c ON c.id = t.clientFk + LEFT JOIN worker wk ON wk.id = c.salesPersonFk + JOIN address a ON a.id = t.addressFk + LEFT JOIN province p ON p.id = a.provinceFk + JOIN agencyMode am ON am.id = t.agencyModeFk + JOIN deliveryMethod dm ON dm.id = am.deliveryMethodFk + JOIN agency ag ON ag.id = am.agencyFk + LEFT JOIN ticketState tls ON tls.ticketFk = tt.ticketFk + LEFT JOIN ticketLastUpdated tlu ON tlu.ticketFk = tt.ticketFk + LEFT JOIN worker w ON w.id = tls.userFk + LEFT JOIN routesMonitor rm ON rm.routeFk = t.routeFk + LEFT JOIN `zone` z ON z.id = t.zoneFk + LEFT JOIN zoneClosure zc ON zc.zoneFk = t.zoneFk + AND DATE(t.shipped) = zc.dated + LEFT JOIN ticketParking tp ON tp.ticketFk = t.id + LEFT JOIN parking pk ON pk.id = tp.parkingFk + WHERE t.warehouseFk = vWarehouseFk + AND dm.code IN ('AGENCY', 'DELIVERY', 'PICKUP'); + + UPDATE tmp.productionBuffer pb + JOIN ( + SELECT pb.ticketFk, GROUP_CONCAT(p.code) previaParking + FROM tmp.productionBuffer pb + JOIN sale s ON s.ticketFk = pb.ticketFk + JOIN saleGroupDetail sgd ON sgd.saleFk = s.id + JOIN saleGroup sg ON sg.id = sgd.saleGroupFk + JOIN parking p ON p.id = sg.parkingFk + GROUP BY pb.ticketFk + ) t ON t.ticketFk = pb.ticketFk + SET pb.previaParking = t.previaParking; + + -- Problemas por ticket + ALTER TABLE tmp.productionBuffer + CHANGE COLUMN `problem` `problem` VARCHAR(255), + ADD COLUMN `collectionH` INT, + ADD COLUMN `collectionV` INT, + ADD COLUMN `collectionN` INT; + + UPDATE tmp.productionBuffer pb + JOIN tmp.ticket_problems tp ON tp.ticketFk = pb.ticketFk + SET pb.problem = TRIM(CAST(CONCAT( IFNULL(tp.itemShortage, ''), + IFNULL(tp.itemDelay, ''), + IFNULL(tp.itemLost, ''), + IF(tp.isFreezed, ' CONGELADO',''), + IF(tp.hasHighRisk, ' RIESGO',''), + IF(tp.hasTicketRequest, ' COD 100',''), + IF(tp.isTaxDataChecked, '',' FICHA INCOMPLETA'), + IF(tp.hasComponentLack, ' COMPONENTES', ''), + IF(HOUR(util.VN_NOW()) < pb.HH AND tp.isTooLittle, ' PEQUEÑO', '') + ) AS char(255))); + + -- Clientes Nuevos o Recuperados + UPDATE tmp.productionBuffer pb + LEFT JOIN bs.clientNewBorn cnb ON cnb.clientFk = pb.clientFk + JOIN productionConfig pc + SET pb.problem = TRIM(CAST(CONCAT('NUEVO ', pb.problem) AS CHAR(255))) + WHERE (cnb.clientFk IS NULL OR cnb.isRookie) + AND pc.rookieDays; + + -- Líneas y volumen por ticket + UPDATE tmp.productionBuffer pb + JOIN ( + SELECT tt.ticketFk, + COUNT(*) `lines`, + SUM(sv.volume) m3, + IFNULL(SUM(IF(sv.isPicked, sv.volume, 0)) / SUM(sv.volume), 0) rate + FROM tmp.productionTicket tt + JOIN saleVolume sv ON sv.ticketFk = tt.ticketFk + GROUP BY tt.ticketFk + ) m ON m.ticketFk = pb.ticketFk + SET pb.`lines` = m.`lines`, + pb.m3 = m.m3, + pb.preparationRate = m.rate; + + DELETE FROM tmp.productionBuffer + WHERE NOT `lines`; + + -- Lineas por linea de encajado + UPDATE tmp.productionBuffer pb + JOIN ( + SELECT ticketFk, + SUM(sub.H) H, + SUM(sub.V) V, + SUM(sub.N) N + FROM ( + SELECT t.ticketFk, + SUM(i.itemPackingTypeFk = 'H') H, + SUM(i.itemPackingTypeFk = 'V') V, + SUM(i.itemPackingTypeFk IS NULL) N + FROM tmp.productionTicket t + JOIN sale s ON s.ticketFk = t.ticketFk + JOIN item i ON i.id = s.itemFk + GROUP BY t.ticketFk, i.itemPackingTypeFk + ) sub + GROUP BY ticketFk + ) sub2 ON sub2.ticketFk = pb.ticketFk + SET pb.H = sub2.H, + pb.V = sub2.V, + pb.N = sub2.N; + + -- Colecciones segun tipo de encajado + UPDATE tmp.productionBuffer pb + JOIN ticketCollection tc ON pb.ticketFk = tc.ticketFk + SET pb.collectionH = IF(pb.H, tc.collectionFk, NULL), + pb.collectionV = IF(pb.V, tc.collectionFk, NULL), + pb.collectionN = IF(pb.N, tc.collectionFk, NULL); + + -- Previa pendiente + ALTER TABLE tmp.productionBuffer + ADD previousWithoutParking BOOL DEFAULT FALSE; + + CREATE OR REPLACE TEMPORARY TABLE tmp.ticketWithPrevia + (ticketFk INT PRIMARY KEY, + salesCount INT DEFAULT 0, + salesInParkingCount INT DEFAULT 0) + ENGINE = MEMORY; + + -- Insertamos todos los tickets que tienen productos parkineados + -- en sectores de previa, segun el sector + CREATE OR REPLACE TEMPORARY TABLE tItemShelvingStock + (PRIMARY KEY(itemFk, sectorFk)) + ENGINE = MEMORY + SELECT ish.itemFk, + p.sectorFk, + sc.isPreviousPrepared, + sc.itemPackingTypeFk + FROM itemShelving ish + JOIN shelving sh ON sh.code = ish.shelvingFk + JOIN parking p ON p.id = sh.parkingFk + JOIN sector sc ON sc.id = p.sectorFk + WHERE p.sectorFk + AND ish.visible + GROUP BY ish.itemFk, p.sectorFk; + + INSERT INTO tmp.ticketWithPrevia(ticketFk, salesCount) + SELECT pb.ticketFk, COUNT(DISTINCT s.id) + FROM tmp.productionBuffer pb + JOIN sale s ON s.ticketFk = pb.ticketFk + JOIN tItemShelvingStock iss ON iss.itemFk = s.itemFk + JOIN sector sc ON sc.id = iss.sectorFk + JOIN item i ON i.id = iss.itemFk + WHERE iss.isPreviousPrepared + AND (sc.itemPackingTypeFk IS NULL + OR (i.itemPackingTypeFk IS NULL AND NOT pb.V) + OR sc.itemPackingTypeFk = i.itemPackingTypeFk) + AND s.quantity > 0 + GROUP BY pb.ticketFk; + + -- Se calcula la cantidad de productos que estan ya preparados porque su saleGroup está aparcado + UPDATE tmp.ticketWithPrevia twp + JOIN ( + SELECT pb.ticketFk, COUNT(DISTINCT s.id) salesInParkingCount + FROM tmp.productionBuffer pb + JOIN sale s ON s.ticketFk = pb.ticketFk + JOIN saleGroupDetail sgd ON sgd.saleFk = s.id + JOIN saleGroup sg ON sg.id = sgd.saleGroupFk + WHERE sg.parkingFk IS NOT NULL + AND s.quantity > 0 + GROUP BY pb.ticketFk + ) sub ON twp.ticketFk = sub.ticketFk + SET twp.salesInParkingCount = sub.salesInParkingCount; + + -- Marcamos como pendientes aquellos que no coinciden las cantidades + UPDATE tmp.productionBuffer pb + JOIN tmp.ticketWithPrevia twp ON twp.ticketFk = pb.ticketFk + SET pb.previousWithoutParking = TRUE + WHERE twp.salesCount > twp.salesInParkingCount; + + DROP TEMPORARY TABLE + tmp.productionTicket, + tmp.ticket, + tmp.risk, + tmp.ticket_problems, + tmp.ticketWithPrevia, + tItemShelvingStock; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`expedition_beforeInsert` + BEFORE INSERT ON `expedition` + FOR EACH ROW +BEGIN + DECLARE intcounter INT; + DECLARE vShipFk INT; + + SET NEW.editorFk = account.myUser_getId(); + + IF NEW.freightItemFk IS NOT NULL THEN + + UPDATE ticket SET packages = nz(packages) + 1 WHERE id = NEW.ticketFk; + + SELECT IFNULL(MAX(counter),0) +1 INTO intcounter + FROM expedition e + INNER JOIN ticket t1 ON e.ticketFk = t1.id + LEFT JOIN ticketState ts ON ts.ticketFk = t1.id + INNER JOIN ticket t2 ON t2.addressFk = t1.addressFk AND DATE(t2.shipped) = DATE(t1.shipped) + AND t1.warehouseFk = t2.warehouseFk + WHERE t2.id = NEW.ticketFk AND ts.alertLevel < 3 AND t1.companyFk = t2.companyFk + AND t1.agencyModeFk = t2.agencyModeFk; + + SET NEW.`counter` = intcounter; + END IF; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`sale_recalcComponent`(vOption INT) +proc: BEGIN +/** + * Este procedimiento recalcula los componentes de un conjunto de sales, + * eliminando los componentes existentes e insertandolos de nuevo + * + * @param vOption si no se quiere forzar llamar con NULL + * @table tmp.recalculateSales (id) + */ + DECLARE vShipped DATE; + DECLARE vWarehouseFk SMALLINT; + DECLARE vAgencyModeFk INT; + DECLARE vAddressFk INT; + DECLARE vTicketFk INT; + DECLARE vLanded DATE; + DECLARE vIsEditable BOOLEAN; + DECLARE vZoneFk INTEGER; + DECLARE vDone BOOL DEFAULT FALSE; + + DECLARE vCur CURSOR FOR + SELECT DISTINCT s.ticketFk + FROM tmp.recalculateSales rs + JOIN vn.sale s ON s.id = rs.id; + + DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE; + + OPEN vCur; + + l: LOOP + SET vDone = FALSE; + FETCH vCur INTO vTicketFk; + + IF vDone THEN + LEAVE l; + END IF; + + SELECT (hasToRecalcPrice OR ts.alertLevel IS NULL) AND t.refFk IS NULL, + t.zoneFk, + t.warehouseFk, + t.shipped, + t.addressFk, + t.agencyModeFk, + t.landed + INTO vIsEditable, + vZoneFk, + vWarehouseFk, + vShipped, + vAddressFk, + vAgencyModeFk, + vLanded + FROM ticket t + LEFT JOIN ticketState ts ON t.id = ts.ticketFk + LEFT JOIN alertLevel al ON al.id = ts.alertLevel + WHERE t.id = vTicketFk; + + CALL zone_getLanded(vShipped, vAddressFk, vAgencyModeFk, vWarehouseFk, TRUE); + + IF NOT EXISTS (SELECT TRUE FROM tmp.zoneGetLanded LIMIT 1) THEN + CALL util.throw(CONCAT('There is no zone for these parameters ', vTicketFk)); + END IF; + + IF vLanded IS NULL OR vZoneFk IS NULL THEN + + UPDATE ticket t + SET t.landed = (SELECT landed FROM tmp.zoneGetLanded LIMIT 1) + WHERE t.id = vTicketFk AND t.landed IS NULL; + + IF vZoneFk IS NULL THEN + SELECT zoneFk INTO vZoneFk FROM tmp.zoneGetLanded LIMIT 1; + UPDATE ticket t + SET t.zoneFk = vZoneFk + WHERE t.id = vTicketFk AND t.zoneFk IS NULL; + END IF; + + END IF; + + DROP TEMPORARY TABLE tmp.zoneGetLanded; + + -- rellena la tabla buyUltimate con la ultima compra + CALL buyUltimate (vWarehouseFk, vShipped); + + CREATE OR REPLACE TEMPORARY TABLE tmp.sale + (PRIMARY KEY (saleFk)) ENGINE = MEMORY + SELECT s.id saleFk, vWarehouseFk warehouseFk + FROM sale s + JOIN tmp.recalculateSales rs ON s.id = rs.id + WHERE s.ticketFk = vTicketFk; + + CREATE OR REPLACE TEMPORARY TABLE tmp.ticketLot + SELECT vWarehouseFk warehouseFk, NULL available, s.itemFk, bu.buyFk, vZoneFk zoneFk + FROM sale s + JOIN tmp.recalculateSales rs ON s.id = rs.id + LEFT JOIN tmp.buyUltimate bu ON bu.itemFk = s.itemFk + WHERE s.ticketFk = vTicketFk + GROUP BY s.itemFk; + + CALL catalog_componentPrepare(); + CALL catalog_componentCalculate(vZoneFk, vAddressFk, vShipped, vWarehouseFk); + + IF vOption IS NULL THEN + SET vOption = IF(vIsEditable, 1, 6); + END IF; + + CALL ticketComponentUpdateSale(vOption); + CALL catalog_componentPurge(); + + DROP TEMPORARY TABLE tmp.buyUltimate; + DROP TEMPORARY TABLE tmp.sale; + + END LOOP; + CLOSE vCur; + +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.ticketFk + 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 ; diff --git a/db/changes/240201/01-refactorHasAnyNegativeBase.sql b/db/changes/240201/01-refactorHasAnyNegativeBase.sql new file mode 100644 index 0000000000..a3eb2d9c7e --- /dev/null +++ b/db/changes/240201/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/240201/01-triggers.sql b/db/changes/240201/01-triggers.sql new file mode 100644 index 0000000000..a7fa029b46 --- /dev/null +++ b/db/changes/240201/01-triggers.sql @@ -0,0 +1,27 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`expedition_beforeInsert` + BEFORE INSERT ON `expedition` + FOR EACH ROW +BEGIN + DECLARE intcounter INT; + DECLARE vShipFk INT; + + SET NEW.editorFk = account.myUser_getId(); + + IF NEW.freightItemFk IS NOT NULL THEN + + UPDATE ticket SET packages = nz(packages) + 1 WHERE id = NEW.ticketFk; + + SELECT IFNULL(MAX(counter),0) +1 INTO intcounter + FROM expedition e + INNER JOIN ticket t1 ON e.ticketFk = t1.id + LEFT JOIN ticketState ts ON ts.ticketFk = t1.id + INNER JOIN ticket t2 ON t2.addressFk = t1.addressFk AND DATE(t2.shipped) = DATE(t1.shipped) + AND t1.warehouseFk = t2.warehouseFk + WHERE t2.id = NEW.ticketFk AND ts.alertLevel < 3 AND t1.companyFk = t2.companyFk + AND t1.agencyModeFk = t2.agencyModeFk; + + SET NEW.`counter` = intcounter; + END IF; +END$$ +DELIMITER ; diff --git a/db/changes/240201/01-views.sql b/db/changes/240201/01-views.sql new file mode 100644 index 0000000000..30b08b7764 --- /dev/null +++ b/db/changes/240201/01-views.sql @@ -0,0 +1,58 @@ +CREATE OR REPLACE DEFINER=`root`@`localhost` + SQL SECURITY DEFINER + VIEW `vn`.`expeditionRoute_freeTickets` AS +SELECT + `t`.`routeFk` AS `routeFk`, + `tss`.`ticketFk` AS `ticket`, + `s`.`name` AS `code`, + `w`.`name` AS `almacen`, + `tss`.`updated` AS `updated`, + `p`.`code` AS `parkingCode` + FROM `vn`.`ticketState` `tss` + JOIN `vn`.`ticket` `t` ON `t`.`id` = `tss`.`ticketFk` + JOIN `vn`.`warehouse` `w` ON `w`.`id` = `t`.`warehouseFk` + JOIN `vn`.`state` `s` ON `s`.`id` = `tss`.`state` + LEFT JOIN `vn`.`ticketParking` `tp` ON `tp`.`ticketFk` = `t`.`id` + LEFT JOIN `vn`.`parking` `p` ON `p`.`id` = `tp`.`parkingFk` + WHERE IFNULL(`t`.`packages`, 0) = 0; + +CREATE OR REPLACE DEFINER=`root`@`localhost` + SQL SECURITY DEFINER + VIEW `vn`.`ticketState` +AS SELECT `tt`.`created` AS `updated`, + `tt`.`stateFk` AS `stateFk`, + `tt`.`userFk` AS `userFk`, + `tls`.`ticketFk` AS `ticketFk`, + `s`.`id` AS `state`, + `s`.`order` AS `productionOrder`, + `s`.`alertLevel` AS `alertLevel`, + `s`.`code` AS `code`, + `s`.`isPreviousPreparable` AS `isPreviousPreparable`, + `s`.`isPicked` AS `isPicked` +FROM ( + ( + `vn`.`ticketLastState` `tls` + JOIN `vn`.`ticketTracking` `tt` ON(`tt`.`id` = `tls`.`ticketTrackingFk`) + ) + JOIN `vn`.`state` `s` ON(`s`.`id` = `tt`.`stateFk`) + ); + +CREATE OR REPLACE DEFINER=`root`@`localhost` + SQL SECURITY DEFINER + VIEW `vn`.`ticketStateToday` +AS +SELECT + `ts`.`ticketFk` AS `ticket`, + `ts`.`state` AS `state`, + `ts`.`productionOrder` AS `productionOrder`, + `ts`.`alertLevel` AS `alertLevel`, + `ts`.`userFk` AS `worker`, + `ts`.`code` AS `code`, + `ts`.`updated` AS `updated`, + `ts`.`isPicked` AS `isPicked` +FROM + `vn`.`ticketState` `ts` + JOIN `vn`.`ticket` `t` ON `t`.`id` = `ts`.`ticketFk` +WHERE + `t`.`shipped` BETWEEN `util`.`VN_CURDATE`() AND `util`.`VN_CURDATE`() + INTERVAL 1 DAY; + diff --git a/db/changes/240202/.gitkeep b/db/changes/240202/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/db/changes/240202/00-aclWorkerTimeControl.sql b/db/changes/240202/00-aclWorkerTimeControl.sql new file mode 100644 index 0000000000..5ccb3131dd --- /dev/null +++ b/db/changes/240202/00-aclWorkerTimeControl.sql @@ -0,0 +1,14 @@ +DELETE FROM salix.ACL + WHERE model = 'WorkerTimeControl' + AND property IN ('*','addTime'); + +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) + VALUES + ('WorkerTimeControl', 'addTimeEntry', 'WRITE', 'ALLOW', 'ROLE', 'employee'), + ('WorkerTimeControl', 'deleteTimeEntry', 'WRITE', 'ALLOW', 'ROLE', 'employee'), + ('WorkerTimeControl', 'updateTimeEntry', 'WRITE', 'ALLOW', 'ROLE', 'employee'), + ('WorkerTimeControl', 'sendMail', 'WRITE', 'ALLOW', 'ROLE', 'employee'), + ('WorkerTimeControl', 'updateWorkerTimeControlMail', 'WRITE', 'ALLOW', 'ROLE', 'employee'), + ('WorkerTimeControl', 'weeklyHourRecordEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'), + ('WorkerTimeControl', 'getMailStates', 'READ', 'ALLOW', 'ROLE', 'employee'), + ('WorkerTimeControl', 'resendWeeklyHourEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'); diff --git a/db/changes/240401/.gitkeep b/db/changes/240401/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/db/changes/240401/00-removeStars.sql b/db/changes/240401/00-removeStars.sql new file mode 100644 index 0000000000..ecc1664c82 --- /dev/null +++ b/db/changes/240401/00-removeStars.sql @@ -0,0 +1 @@ +ALTER TABLE `vn`.`item` DROP COLUMN stars; diff --git a/db/changes/240401/00-ticket_canbePostponed.sql b/db/changes/240401/00-ticket_canbePostponed.sql new file mode 100644 index 0000000000..e0fbb99cfb --- /dev/null +++ b/db/changes/240401/00-ticket_canbePostponed.sql @@ -0,0 +1,74 @@ +DELIMITER $$ +$$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_canbePostponed`(vOriginDated DATE, vFutureDated DATE, vWarehouseFk INT) +BEGIN +/** + * Devuelve un listado de tickets susceptibles de fusionarse con otros tickets en el futuro + * + * @param vOriginDated Fecha en cuestión + * @param vFutureDated Fecha en el futuro a sondear + * @param vWarehouseFk Identificador de vn.warehouse + */ + CREATE OR REPLACE TEMPORARY TABLE tmp.filter + (INDEX (id)) + SELECT sv.ticketFk id, + sub2.id futureId, + GROUP_CONCAT(DISTINCT i.itemPackingTypeFk ORDER BY i.itemPackingTypeFk) ipt, + CAST(sum(litros) AS DECIMAL(10,0)) liters, + CAST(count(*) AS DECIMAL(10,0)) `lines`, + st.name state, + sub2.iptd futureIpt, + sub2.state futureState, + t.clientFk, + t.warehouseFk, + ts.alertLevel, + t.shipped, + t.totalWithVat, + sub2.shipped futureShipped, + t.workerFk, + st.code stateCode, + sub2.code futureStateCode, + st.classColor, + sub2.classColor futureClassColor + FROM vn.saleVolume sv + JOIN vn.sale s ON s.id = sv.saleFk + JOIN vn.item i ON i.id = s.itemFk + JOIN vn.ticket t ON t.id = sv.ticketFk + JOIN vn.address a ON a.id = t.addressFk + JOIN vn.province p ON p.id = a.provinceFk + JOIN vn.country c ON c.id = p.countryFk + JOIN vn.ticketState ts ON ts.ticketFk = t.id + JOIN vn.state st ON st.id = ts.stateFk + JOIN vn.alertLevel al ON al.id = ts.alertLevel + LEFT JOIN vn.ticketParking tp ON tp.ticketFk = t.id + LEFT JOIN ( + SELECT * + FROM ( + SELECT + t.addressFk, + t.id, + t.shipped, + st.name state, + st.code, + st.classColor, + GROUP_CONCAT(DISTINCT i.itemPackingTypeFk ORDER BY i.itemPackingTypeFk) iptd + FROM vn.ticket t + JOIN vn.ticketState ts ON ts.ticketFk = t.id + JOIN vn.state st ON st.id = ts.stateFk + JOIN vn.sale s ON s.ticketFk = t.id + JOIN vn.item i ON i.id = s.itemFk + WHERE t.shipped BETWEEN vFutureDated + AND util.dayend(vFutureDated) + AND t.warehouseFk = vWarehouseFk + GROUP BY t.id + ) sub + GROUP BY sub.addressFk + ) sub2 ON sub2.addressFk = t.addressFk AND t.id != sub2.id + WHERE t.shipped BETWEEN vOriginDated AND util.dayend(vOriginDated) + AND t.warehouseFk = vWarehouseFk + AND al.code = 'FREE' + AND tp.ticketFk IS NULL + GROUP BY sv.ticketFk + HAVING futureId; +END$$ +DELIMITER ; diff --git a/db/changes/240401/01-saleGroupDetailDefaultSize.sql b/db/changes/240401/01-saleGroupDetailDefaultSize.sql new file mode 100644 index 0000000000..12f17c5e71 --- /dev/null +++ b/db/changes/240401/01-saleGroupDetailDefaultSize.sql @@ -0,0 +1,2 @@ +ALTER TABLE vn.productionConfig ADD itemPreviousDefaultSize int NULL COMMENT 'Altura por defecto para los artículos de previa'; +UPDATE IGNORE vn.productionConfig SET itemPreviousDefaultSize = 40 WHERE id = 1; diff --git a/db/changes/240401/02-invoiceOutConfig_refLen.sql b/db/changes/240401/02-invoiceOutConfig_refLen.sql new file mode 100644 index 0000000000..8b91889f24 --- /dev/null +++ b/db/changes/240401/02-invoiceOutConfig_refLen.sql @@ -0,0 +1,2 @@ +ALTER TABLE vn.invoiceOutConfig + ADD IF NOT EXISTS refLen TINYINT UNSIGNED DEFAULT 5 NOT NULL COMMENT 'Invoice reference identifier length'; diff --git a/db/changes/240401/03-fixInvoiceOutBeforeInsert.sql b/db/changes/240401/03-fixInvoiceOutBeforeInsert.sql new file mode 100644 index 0000000000..0081c88031 --- /dev/null +++ b/db/changes/240401/03-fixInvoiceOutBeforeInsert.sql @@ -0,0 +1,59 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`invoiceOut_beforeInsert` + BEFORE INSERT ON `invoiceOut` + FOR EACH ROW +BEGIN +/** + * Generates the next reference for the invoice serial. There cannot be gaps + * between identifiers of the same serial! + * + * Reference format: + * {0} Invoice serial + * {1} The company code + * {2-3} Last two digits of issue year + * {4-$} Autoincrement identifier + */ + DECLARE vRef INT DEFAULT 0; + DECLARE vRefLen INT; + DECLARE vRefPrefix VARCHAR(255); + DECLARE vLastRef VARCHAR(255); + DECLARE vCompanyCode INT; + DECLARE vYearLen INT DEFAULT 2; + DECLARE vPrefixLen INT; + + SELECT companyCode INTO vCompanyCode + FROM company + WHERE id = NEW.companyFk; + + IF vCompanyCode IS NULL THEN + CALL util.throw('companyCodeNotDefined'); + END IF; + + SELECT MAX(i.ref) INTO vLastRef + FROM invoiceOut i + WHERE i.serial = NEW.serial + AND i.issued BETWEEN util.firstDayOfYear(NEW.issued) AND util.lastDayOfYear(NEW.issued) + AND i.companyFk = NEW.companyFk; + + IF vLastRef IS NOT NULL THEN + SET vPrefixLen = LENGTH(NEW.serial) + LENGTH(vCompanyCode) + vYearLen; + SET vRefLen = LENGTH(vLastRef) - vPrefixLen; + SET vRefPrefix = LEFT(vLastRef, vPrefixLen); + SET vRef = RIGHT(vLastRef, vRefLen); + ELSE + SELECT refLen INTO vRefLen FROM invoiceOutConfig; + SET vRefPrefix = CONCAT( + NEW.serial, + vCompanyCode, + RIGHT(YEAR(NEW.issued), vYearLen) + ); + END IF; + + SET vRef = vRef + 1; + IF LENGTH(vRef) > vRefLen THEN + CALL util.throw('refIdLenExceeded'); + END IF; + + SET NEW.ref = CONCAT(vRefPrefix, LPAD(vRef, vRefLen, '0')); +END$$ +DELIMITER ; diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 93b7b796f1..b243692bb2 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -1,3 +1,7 @@ +CREATE ROLE 'salix'; +GRANT 'salix' TO 'root'@'%'; +SET DEFAULT ROLE 'salix' FOR 'root'@'%'; + CREATE SCHEMA IF NOT EXISTS `vn2008`; CREATE SCHEMA IF NOT EXISTS `tmp`; @@ -362,7 +366,7 @@ INSERT INTO `vn`.`contactChannel`(`id`, `name`) INSERT INTO `vn`.`client`(`id`,`name`,`fi`,`socialName`,`contact`,`street`,`city`,`postcode`,`phone`,`mobile`,`isRelevant`,`email`,`iban`,`dueDay`,`accountingAccount`,`isEqualizated`,`provinceFk`,`hasToInvoice`,`credit`,`countryFk`,`isActive`,`gestdocFk`,`quality`,`payMethodFk`,`created`,`isToBeMailed`,`contactChannelFk`,`hasSepaVnl`,`hasCoreVnl`,`hasCoreVnh`,`riskCalculated`,`clientTypeFk`, `hasToInvoiceByAddress`,`isTaxDataChecked`,`isFreezed`,`creditInsurance`,`isCreatedAsServed`,`hasInvoiceSimplified`,`salesPersonFk`,`isVies`,`eypbc`, `businessTypeFk`,`typeFk`) VALUES - (1101, 'Bruce Wayne', '84612325V', 'BATMAN', 'Alfred', '1007 MOUNTAIN DRIVE, GOTHAM', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceWayne@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist','loses'), + (1101, 'Bruce Wayne', '84612325V', 'BATMAN', 'Alfred', '1007 MOUNTAIN DRIVE, GOTHAM', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceWayne@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist','normal'), (1102, 'Petter Parker', '87945234L', 'SPIDER MAN', 'Aunt May', '20 INGRAM STREET, QUEENS, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'PetterParker@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist','normal'), (1103, 'Clark Kent', '06815934E', 'SUPER MAN', 'lois lane', '344 CLINTON STREET, APARTAMENT 3-D', 'Gotham', 46460, 1111111111, 222222222, 1, 'ClarkKent@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 0, 19, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist','normal'), (1104, 'Tony Stark', '06089160W', 'IRON MAN', 'Pepper Potts', '10880 MALIBU POINT, 90265', 'Gotham', 46460, 1111111111, 222222222, 1, 'TonyStark@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 0, NULL, 0, 0, 18, 0, 1, 'florist','normal'), @@ -372,8 +376,8 @@ INSERT INTO `vn`.`client`(`id`,`name`,`fi`,`socialName`,`contact`,`street`,`city (1108, 'Charles Xavier', '22641921P', 'PROFESSOR X', 'Beast', '3800 VICTORY PKWY, CINCINNATI, OH 45207, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'CharlesXavier@mydomain.com', NULL, 0, 1234567890, 0, 5, 1, 300, 13, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, 1, NULL, 0, 0, 19, 0, 1, 'florist','normal'), (1109, 'Bruce Banner', '16104829E', 'HULK', 'Black widow', 'SOMEWHERE IN NEW YORK', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceBanner@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, 9, 0, 1, 'florist','normal'), (1110, 'Jessica Jones', '58282869H', 'JESSICA JONES', 'Luke Cage', 'NYCC 2015 POSTER', 'Gotham', 46460, 1111111111, 222222222, 1, 'JessicaJones@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, 0, NULL, 0, 0, NULL, 0, 1, 'florist','normal'), - (1111, 'Missing', NULL, 'MISSING MAN', 'Anton', 'THE SPACE, UNIVERSE FAR AWAY', 'Gotham', 46460, 1111111111, 222222222, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, 0, 1, 0, NULL, 1, 0, NULL, 0, 1, 'others','normal'), - (1112, 'Trash', NULL, 'GARBAGE MAN', 'Unknown name', 'NEW YORK CITY, UNDERGROUND', 'Gotham', 46460, 1111111111, 222222222, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, 0, 1, 0, NULL, 1, 0, NULL, 0, 1, 'others','normal'); + (1111, 'Missing', NULL, 'MISSING MAN', 'Anton', 'THE SPACE, UNIVERSE FAR AWAY', 'Gotham', 46460, 1111111111, 222222222, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, 0, 1, 0, NULL, 1, 0, NULL, 0, 1, 'others','loses'), + (1112, 'Trash', NULL, 'GARBAGE MAN', 'Unknown name', 'NEW YORK CITY, UNDERGROUND', 'Gotham', 46460, 1111111111, 222222222, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 4, 0, 1, 0, NULL, 1, 0, NULL, 0, 1, 'others','loses'); INSERT INTO `vn`.`client`(`id`, `name`, `fi`, `socialName`, `contact`, `street`, `city`, `postcode`, `isRelevant`, `email`, `iban`,`dueDay`,`accountingAccount`, `isEqualizated`, `provinceFk`, `hasToInvoice`, `credit`, `countryFk`, `isActive`, `gestdocFk`, `quality`, `payMethodFk`,`created`, `isTaxDataChecked`) SELECT id, name, CONCAT(RPAD(CONCAT(id,9),8,id),'A'), CONCAT(name, 'Social'), CONCAT(name, 'Contact'), CONCAT(name, 'Street'), 'GOTHAM', 46460, 1, CONCAT(name,'@mydomain.com'), NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1,NULL, 10, 5, util.VN_CURDATE(), 1 @@ -489,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), @@ -596,20 +600,24 @@ INSERT INTO `vn`.`taxArea` (`code`, `claveOperacionFactura`, `CodigoTransaccion` ('NATIONAL', 0, 1), ('WORLD', 2, 15); +INSERT INTO vn.invoiceOutConfig + SET parallelism = 8; + 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; @@ -618,9 +626,6 @@ UPDATE `vn`.`invoiceOut` SET ref = 'T3333333' WHERE id = 3; UPDATE `vn`.`invoiceOut` SET ref = 'T4444444' WHERE id = 4; UPDATE `vn`.`invoiceOut` SET ref = 'A1111111' WHERE id = 5; -INSERT INTO vn.invoiceOutConfig - SET parallelism = 8; - INSERT INTO `vn`.`invoiceOutTax` (`invoiceOutFk`, `taxableBase`, `vat`, `pgcFk`) VALUES (1, 895.76, 89.58, 4722000010), @@ -715,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), @@ -767,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)), @@ -811,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'), @@ -923,25 +929,25 @@ INSERT INTO `vn`.`itemFamily`(`code`, `description`) ('VT', 'Sales'); INSERT INTO `vn`.`item`(`id`, `typeFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `expenseFk`, - `comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`, `isFloramondo`, `genericFk`, `itemPackingTypeFk`, `hasMinPrice`, `packingShelve`, `weightByPiece`) + `comment`, `relevancy`, `image`, `subName`, `minPrice`, `family`, `isFloramondo`, `genericFk`, `itemPackingTypeFk`, `hasMinPrice`, `packingShelve`, `weightByPiece`) VALUES - (1, 2, 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'EMB', 0, NULL, 'V', 0, 15,3), - (2, 2, 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '2', NULL, 0, 2, 'VT', 0, NULL, 'H', 0, 10,2), - (3, 1, 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '3', NULL, 0, 5, 'VT', 0, NULL, NULL, 0, 5,5), - (4, 1, 1, 1, 'Increases block', 1, 05080000, 4751000000, NULL, 0, '4', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL,NULL), - (5, 3, 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '5', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL,NULL), - (6, 5, 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '6', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL,NULL), - (7, 5, 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '7', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL,NULL), - (8, 2, 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '8', NULL, 0, 5, 'VT', 0, NULL, NULL, 0, NULL,NULL), - (9, 2, 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '9', NULL, 0, 4, 'VT', 1, NULL, NULL, 0, NULL,NULL), - (10, 1, 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '10', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL,NULL), - (11, 1, 1, 1, NULL, 1, 05080000, 4751000000, NULL, 0, '11', NULL, 0, 4, 'VT', 0, NULL, NULL, 0, NULL,NULL), - (12, 3, 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '12', NULL, 0, 3, 'VT', 0, NULL, NULL, 0, NULL,NULL), - (13, 5, 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '13', NULL, 1, 2, 'VT', 1, NULL, NULL, 1, NULL,NULL), - (14, 5, 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 4, 'VT', 1, NULL, NULL, 0, NULL,NULL), - (15, 4, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL, NULL, 0, NULL,NULL), - (16, 6, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL, NULL, 0, NULL,NULL), - (71, 6, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'VT', 0, NULL, NULL, 0, NULL,NULL); + (1, 2, 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 'EMB', 0, NULL, 'V', 0, 15,3), + (2, 2, 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '2', NULL, 0, 'VT', 0, NULL, 'H', 0, 10,2), + (3, 1, 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '3', NULL, 0, 'VT', 0, NULL, NULL, 0, 5,5), + (4, 1, 1, 1, 'Increases block', 1, 05080000, 4751000000, NULL, 0, '4', NULL, 0, 'VT', 0, NULL, NULL, 0, NULL,NULL), + (5, 3, 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '5', NULL, 0, 'VT', 0, NULL, NULL, 0, NULL,NULL), + (6, 5, 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '6', NULL, 0, 'VT', 0, NULL, NULL, 0, NULL,NULL), + (7, 5, 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '7', NULL, 0, 'VT', 0, NULL, NULL, 0, NULL,NULL), + (8, 2, 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '8', NULL, 0, 'VT', 0, NULL, NULL, 0, NULL,NULL), + (9, 2, 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '9', NULL, 0, 'VT', 1, NULL, NULL, 0, NULL,NULL), + (10, 1, 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '10', NULL, 0, 'VT', 0, NULL, NULL, 0, NULL,NULL), + (11, 1, 1, 1, NULL, 1, 05080000, 4751000000, NULL, 0, '11', NULL, 0, 'VT', 0, NULL, NULL, 0, NULL,NULL), + (12, 3, 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '12', NULL, 0, 'VT', 0, NULL, NULL, 0, NULL,NULL), + (13, 5, 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '13', NULL, 1, 'VT', 1, NULL, NULL, 1, NULL,NULL), + (14, 5, 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 'VT', 1, NULL, NULL, 0, NULL,NULL), + (15, 4, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 'EMB', 0, NULL, NULL, 0, NULL,NULL), + (16, 6, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 'EMB', 0, NULL, NULL, 0, NULL,NULL), + (71, 6, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 'VT', 0, NULL, NULL, 0, NULL,NULL); -- Update the taxClass after insert of the items UPDATE `vn`.`itemTaxCountry` SET `taxClassFk` = 2 @@ -2343,9 +2349,11 @@ INSERT INTO `vn`.`zoneEvent`(`zoneFk`, `type`, `weekDays`) (8, 'indefinitely', 'mon,tue,wed,thu,fri,sat,sun'), (10, 'indefinitely', 'mon,tue,wed,thu,fri,sat,sun'); -INSERT INTO `vn`.`zoneEvent`(`zoneFk`, `type`, `started`, `ended`) +INSERT INTO `vn`.`zoneEvent`(`zoneFk`, `type`, `started`, `ended`, `weekDays`) VALUES - (9, 'range', DATE_ADD(util.VN_CURDATE(), INTERVAL -1 YEAR), DATE_ADD(util.VN_CURDATE(), INTERVAL +1 YEAR)); + (9, 'range', DATE_ADD(util.VN_CURDATE(), INTERVAL -1 YEAR), DATE_ADD(util.VN_CURDATE(), INTERVAL +1 YEAR), 'mon'), + (9, 'range', util.VN_CURDATE(), NULL, 'tue'), + (9, 'range', NULL, util.VN_CURDATE(), 'wed'); INSERT INTO `vn`.`workerTimeControl`(`userFk`, `timed`, `manual`, `direction`, `isSendMail`) VALUES @@ -2903,8 +2911,7 @@ INSERT INTO `vn`.`workerConfig` (`id`, `businessUpdated`, `roleFk`, `payMethodFk INSERT INTO `vn`.`ticketRefund`(`refundTicketFk`, `originalTicketFk`) VALUES - (1, 12), - (8, 10); + (24, 7); INSERT INTO `vn`.`deviceProductionModels` (`code`) VALUES @@ -2960,9 +2967,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 @@ -3002,3 +3009,37 @@ INSERT INTO `vn`.`invoiceCorrectionType` (`id`, `description`) (1, 'Error in VAT calculation'), (2, 'Error in sales details'), (3, 'Error in customer data'); + +UPDATE `vn`.`client` + SET fi='65004204V' + WHERE id=1; + +UPDATE `vn`.`worker` + SET fi='59328808D' + WHERE id=1106; + + +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); diff --git a/db/dump/structure.sql b/db/dump/structure.sql index 1db4252f48..694f745efa 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 a059f1060c..0000000000 --- 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/helpers/selectors.js b/e2e/helpers/selectors.js index acd13800b5..dba430e663 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -735,7 +735,7 @@ export default { }, createStateView: { state: 'vn-autocomplete[ng-model="$ctrl.stateFk"]', - worker: 'vn-worker-autocomplete[ng-model="$ctrl.workerFk"]', + worker: 'vn-worker-autocomplete[ng-model="$ctrl.userFk"]', saveStateButton: `button[type=submit]` }, claimsIndex: { diff --git a/e2e/paths/03-worker/04_time_control.spec.js b/e2e/paths/03-worker/04_time_control.spec.js index 5f64aa6ce5..c6589d2e3a 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 5e82306cc6..9095eee0a3 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); @@ -236,7 +225,7 @@ describe('Ticket Edit sale path', () => { }); it('should show error trying to delete a ticket with a refund', async() => { - await page.accessToSearchResult('16'); + await page.accessToSearchResult('6'); await page.waitToClick(selectors.ticketDescriptor.moreMenu); await page.waitToClick(selectors.ticketDescriptor.moreMenuDeleteTicket); await page.waitToClick(selectors.globalItems.acceptButton); 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 e4c63855af..0000000000 --- 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 7e5201d11a..9c55184243 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 eb4ac5d710..0000000000 --- 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 0b8da98b46..31c0cfc18b 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 728f0130a9..09ac66ffce 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 0079e80235..e6c601d7dc 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 9277f3ee46..1979d517f4 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 f65efe176c..34781c2aac 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 96c34d98ca..17e955ff52 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 2be73e696c..8eb8a6f0dd 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 e9811e38f0..2c43ca3b2a 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 426fe2b731..c8cb4f6bb6 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 bed41c63b4..044d0d043b 100644 --- a/front/salix/locale/es.yml +++ b/front/salix/locale/es.yml @@ -18,6 +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 # Actions diff --git a/front/salix/module.js b/front/salix/module.js index f8fa0faf0b..0ce855308f 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 = '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 0000000000..760fdf60a0 --- /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 360c845661..0000000000 --- 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 9101532a34..0000000000 --- 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 7d5b5ed47f..568916bef4 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", @@ -68,8 +69,7 @@ "Sent units from ticket": "I sent *{{quantity}}* units of [{{concept}} ({{itemId}})]({{{itemUrl}}}) to *\"{{nickname}}\"* coming from ticket id [{{ticketId}}]({{{ticketUrl}}})", "Change quantity": "{{concept}} change of {{oldQuantity}} to {{newQuantity}}", "Claim will be picked": "The product from the claim [({{claimId}})]({{{claimUrl}}}) from the client *{{clientName}}* will be picked", - "Claim state has changed to incomplete": "The state of the claim [({{claimId}})]({{{claimUrl}}}) from client *{{clientName}}* has changed to *incomplete*", - "Claim state has changed to canceled": "The state of the claim [({{claimId}})]({{{claimUrl}}}) from client *{{clientName}}* has changed to *canceled*", + "Claim state has changed to": "The state of the claim [({{claimId}})]({{{claimUrl}}}) from client *{{clientName}}* has changed to *{{newState}}*", "Customs agent is required for a non UEE member": "Customs agent is required for a non UEE member", "Incoterms is required for a non UEE member": "Incoterms is required for a non UEE member", "Client checked as validated despite of duplication": "Client checked as validated despite of duplication from client id {{clientId}}", @@ -178,12 +178,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 +201,7 @@ "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}}" + "It was not able to remove the next expeditions:": "It was not able to remove the next expeditions: {{expeditions}}", + "Incorrect pin": "Incorrect pin.", + "The notification subscription of this worker cant be modified": "The notification subscription of this worker cant be modified" } \ No newline at end of file diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 01384efb42..5555ef8b05 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", @@ -135,8 +136,7 @@ "Sent units from ticket": "Envio *{{quantity}}* unidades de [{{concept}} ({{itemId}})]({{{itemUrl}}}) a *\"{{nickname}}\"* provenientes del ticket id [{{ticketId}}]({{{ticketUrl}}})", "Change quantity": "{{concept}} cambia de {{oldQuantity}} a {{newQuantity}}", "Claim will be picked": "Se recogerá el género de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}*", - "Claim state has changed to incomplete": "Se ha cambiado el estado de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}* a *incompleta*", - "Claim state has changed to canceled": "Se ha cambiado el estado de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}* a *anulado*", + "Claim state has changed to": "Se ha cambiado el estado de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}* a *{{newState}}*", "Client checked as validated despite of duplication": "Cliente comprobado a pesar de que existe el cliente id {{clientId}}", "ORDER_ROW_UNAVAILABLE": "No hay disponibilidad de este producto", "Distance must be lesser than 1000": "La distancia debe ser inferior a 1000", @@ -305,14 +305,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 +330,11 @@ "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 claim has been updated": "La reclamación con Id: {{claimId}}, ha sido actualizada", + "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 8107455625..d592dc416b 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 31a2f113b5..cfc6932175 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 33ef3797d6..56b5360e84 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 2fa3010afb..c799602128 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/methods/account/sync.js b/modules/account/back/methods/account/sync.js index 0eab0ef63a..1026c50208 100644 --- a/modules/account/back/methods/account/sync.js +++ b/modules/account/back/methods/account/sync.js @@ -26,24 +26,46 @@ module.exports = Self => { }); Self.sync = async function(userName, password, force, options) { + const models = Self.app.models; const myOptions = {}; - + let tx; + if (typeof options == 'object') Object.assign(myOptions, options); - const models = Self.app.models; - const user = await models.VnUser.findOne({ - fields: ['id', 'password'], - where: {name: userName} - }, myOptions); + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + }; - if (user && password && !await user.hasPassword(password)) - throw new ForbiddenError('Wrong password'); + try { + const user = await models.VnUser.findOne({ + fields: ['id', 'password'], + where: {name: userName} + }, myOptions); - const isSync = !await models.UserSync.exists(userName, myOptions); + if (user && password && !await user.hasPassword(password)) + throw new ForbiddenError('Wrong password'); - if (!force && isSync && user) return; - await models.AccountConfig.syncUser(userName, password); - await models.UserSync.destroyById(userName, myOptions); + const isSync = !await models.UserSync.exists(userName, myOptions); + + if (!force && isSync && user) { + if (tx) await tx.rollback(); + return; + } + + await Self.rawSql(` + SELECT id + FROM account.user + WHERE id = ? + FOR UPDATE`, [user.id], myOptions); + + await models.AccountConfig.syncUser(userName, password); + await models.UserSync.destroyById(userName, myOptions); + if (tx) await tx.commit(); + } catch (err) { + if (tx) await tx.rollback(); + throw err; + } }; }; diff --git a/modules/account/back/model-config.json b/modules/account/back/model-config.json index b4bd6dbafd..0cd43d0cec 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 3c22521cbe..6c27846966 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 b557d243c8..89f0add486 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 6f5213f24b..5eb5614081 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 0000000000..014b95d144 --- /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 4b69ffdc26..a89f47b77a 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 77df7a9204..e59351c598 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/back/models/samba-config.js b/modules/account/back/models/samba-config.js index f5672ca215..927510a291 100644 --- a/modules/account/back/models/samba-config.js +++ b/modules/account/back/models/samba-config.js @@ -7,7 +7,8 @@ const execFile = require('child_process').execFile; * https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/useraccountcontrol-manipulate-account-properties */ const UserAccountControlFlags = { - ACCOUNTDISABLE: 2 + ACCOUNTDISABLE: 0x2, + DONT_EXPIRE_PASSWD: 0x10000 }; module.exports = Self => { @@ -118,7 +119,8 @@ module.exports = Self => { } entry = { - userAccountControl: sambaUser.userAccountControl + userAccountControl: (sambaUser.userAccountControl + | UserAccountControlFlags.DONT_EXPIRE_PASSWD) & ~UserAccountControlFlags.ACCOUNTDISABLE, uidNumber: info.uidNumber, accountExpires: 0, diff --git a/modules/account/back/models/sign_in-log.json b/modules/account/back/models/sign_in-log.json index c5c014e60a..8656e92dc2 100644 --- a/modules/account/back/models/sign_in-log.json +++ b/modules/account/back/models/sign_in-log.json @@ -25,7 +25,15 @@ "type": "number" }, "ip": { - "type": "string" + "type": "string" + }, + "userName": { + "type": "string" + }, + "owner": { + "type": "boolean", + "required": true, + "default": true } }, "relations": { diff --git a/modules/account/front/acl/create/index.html b/modules/account/front/acl/create/index.html index 7f4fa9e462..14332f737a 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 b83b9c255c..a3efab440d 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 acc07d3468..70a5188857 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 7499271864..846f8b455e 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 6f888211de..3c7c758efc 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 f39840e5fa..f02c08f285 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 02900d5808..77d6fc2c1f 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 4cd4ac8229..d8bf4857a0 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 a1b5781336..17b585cb79 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 e2761c6396..eafb96727e 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 9d7e6e053a..cfef28e57a 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 bc554b9f93..eba1002b0e 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 f80b537aa4..a539d96576 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 be3baccd73..393c3b10d1 100644 --- a/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js +++ b/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js @@ -120,10 +120,10 @@ module.exports = Self => { observationTypeFk: obsevationType.id }, myOptions); - await models.TicketTracking.create({ + await models.Ticket.state(ctx, { 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 edc714235f..28e78c9d70 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/specs/updateClaim.spec.js b/modules/claim/back/methods/claim/specs/updateClaim.spec.js index 85ada869a9..e2d5fcfebf 100644 --- a/modules/claim/back/methods/claim/specs/updateClaim.spec.js +++ b/modules/claim/back/methods/claim/specs/updateClaim.spec.js @@ -1,8 +1,9 @@ const app = require('vn-loopback/server/server'); const LoopBackContext = require('loopback-context'); - +const i18n = require('i18n'); describe('Update Claim', () => { let url; + let claimStatesMap = {}; beforeAll(async() => { url = await app.models.Url.getUrl(); const activeCtx = { @@ -16,6 +17,8 @@ describe('Update Claim', () => { spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ active: activeCtx }); + const claimStates = await app.models.ClaimState.find(); + claimStatesMap = claimStates.reduce((acc, state) => ({...acc, [state.code]: state.id}), {}); }); const newDate = Date.vnNew(); const originalData = { @@ -62,6 +65,123 @@ describe('Update Claim', () => { expect(error.message).toEqual(`You don't have enough privileges to change that field`); }); + it(`should success to update the claimState to 'pending' and send a rocket message`, async() => { + const tx = await app.models.Claim.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const newClaim = await app.models.Claim.create(originalData, options); + + const chatModel = app.models.Chat; + spyOn(chatModel, 'sendCheckingPresence').and.callThrough(); + + const pendingState = claimStatesMap.pending; + const claimManagerId = 72; + const ctx = { + req: { + accessToken: {userId: claimManagerId}, + headers: {origin: url} + }, + args: { + observation: 'valid observation', + claimStateFk: pendingState, + hasToPickUp: false + } + }; + ctx.req.__ = i18n.__; + await app.models.Claim.updateClaim(ctx, newClaim.id, options); + + let updatedClaim = await app.models.Claim.findById(newClaim.id, null, options); + + expect(updatedClaim.observation).toEqual(ctx.args.observation); + expect(chatModel.sendCheckingPresence).toHaveBeenCalled(); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it(`should success to update the claimState to 'managed' and send a rocket message`, async() => { + const tx = await app.models.Claim.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const newClaim = await app.models.Claim.create(originalData, options); + + const chatModel = app.models.Chat; + spyOn(chatModel, 'sendCheckingPresence').and.callThrough(); + + const managedState = claimStatesMap.managed; + const claimManagerId = 72; + const ctx = { + req: { + accessToken: {userId: claimManagerId}, + headers: {origin: url} + }, + args: { + observation: 'valid observation', + claimStateFk: managedState, + hasToPickUp: false + } + }; + ctx.req.__ = i18n.__; + await app.models.Claim.updateClaim(ctx, newClaim.id, options); + + let updatedClaim = await app.models.Claim.findById(newClaim.id, null, options); + + expect(updatedClaim.observation).toEqual(ctx.args.observation); + expect(chatModel.sendCheckingPresence).toHaveBeenCalled(); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it(`should success to update the claimState to 'resolved' and send a rocket message`, async() => { + const tx = await app.models.Claim.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const newClaim = await app.models.Claim.create(originalData, options); + + const chatModel = app.models.Chat; + spyOn(chatModel, 'sendCheckingPresence').and.callThrough(); + + const resolvedState = claimStatesMap.resolved; + const claimManagerId = 72; + const ctx = { + req: { + accessToken: {userId: claimManagerId}, + headers: {origin: url} + }, + args: { + observation: 'valid observation', + claimStateFk: resolvedState, + hasToPickUp: false + } + }; + ctx.req.__ = i18n.__; + await app.models.Claim.updateClaim(ctx, newClaim.id, options); + + let updatedClaim = await app.models.Claim.findById(newClaim.id, null, options); + + expect(updatedClaim.observation).toEqual(ctx.args.observation); + expect(chatModel.sendCheckingPresence).toHaveBeenCalled(); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + it(`should success to update the claimState to 'canceled' and send a rocket message`, async() => { const tx = await app.models.Claim.beginTransaction({}); @@ -73,7 +193,7 @@ describe('Update Claim', () => { const chatModel = app.models.Chat; spyOn(chatModel, 'sendCheckingPresence').and.callThrough(); - const canceledState = 4; + const canceledState = claimStatesMap.canceled; const claimManagerId = 72; const ctx = { req: { @@ -86,9 +206,7 @@ describe('Update Claim', () => { hasToPickUp: false } }; - ctx.req.__ = (value, params) => { - return params.nickname; - }; + ctx.req.__ = i18n.__; await app.models.Claim.updateClaim(ctx, newClaim.id, options); let updatedClaim = await app.models.Claim.findById(newClaim.id, null, options); @@ -127,9 +245,7 @@ describe('Update Claim', () => { hasToPickUp: false } }; - ctx.req.__ = (value, params) => { - return params.nickname; - }; + ctx.req.__ = i18n.__; await app.models.Claim.updateClaim(ctx, newClaim.id, options); let updatedClaim = await app.models.Claim.findById(newClaim.id, null, options); @@ -168,9 +284,7 @@ describe('Update Claim', () => { hasToPickUp: true } }; - ctx.req.__ = (value, params) => { - return params.nickname; - }; + ctx.req.__ = i18n.__; await app.models.Claim.updateClaim(ctx, newClaim.id, options); let updatedClaim = await app.models.Claim.findById(newClaim.id, null, options); diff --git a/modules/claim/back/methods/claim/updateClaim.js b/modules/claim/back/methods/claim/updateClaim.js index d99528413c..68fff7846e 100644 --- a/modules/claim/back/methods/claim/updateClaim.js +++ b/modules/claim/back/methods/claim/updateClaim.js @@ -96,12 +96,9 @@ module.exports = Self => { // When claimState has been changed if (args.claimStateFk) { const newState = await models.ClaimState.findById(args.claimStateFk, null, myOptions); - if (newState.hasToNotify) { - if (newState.code == 'incomplete') - await notifyStateChange(ctx, salesPerson.id, claim, newState.code); - if (newState.code == 'canceled') - await notifyStateChange(ctx, claim.workerFk, claim, newState.code); - } + await notifyStateChange(ctx, salesPerson.id, claim, newState.code); + if (newState.code == 'canceled') + await notifyStateChange(ctx, claim.workerFk, claim, newState.code); } if (tx) await tx.commit(); @@ -113,15 +110,16 @@ module.exports = Self => { } }; - async function notifyStateChange(ctx, workerId, claim, state) { + async function notifyStateChange(ctx, workerId, claim, newState) { const models = Self.app.models; const url = await models.Url.getUrl(); const $t = ctx.req.__; // $translate - const message = $t(`Claim state has changed to ${state}`, { + const message = $t(`Claim state has changed to`, { claimId: claim.id, clientName: claim.client().name, - claimUrl: `${url}claim/${claim.id}/summary` + claimUrl: `${url}claim/${claim.id}/summary`, + newState }); await models.Chat.sendCheckingPresence(ctx, workerId, message); } diff --git a/modules/claim/back/methods/claim/uploadFile.js b/modules/claim/back/methods/claim/uploadFile.js index 3d0737cf81..4fd041456a 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 d355881e85..d224586da4 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 b0f352f50e..732955660c 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 26c90fd690..ed12c925b0 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 9f12ff93a0..ef5477f50e 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 2d418b76e9..1e4cb6a0f6 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 f5bde4168c..c50fdebdfc 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 a7db1f3e1f..b85b9e073c 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/claim/front/note/create/index.html b/modules/claim/front/note/create/index.html index 304a8c0046..8a882a4f5b 100644 --- a/modules/claim/front/note/create/index.html +++ b/modules/claim/front/note/create/index.html @@ -27,4 +27,4 @@ label="Cancel"> - \ No newline at end of file + diff --git a/modules/client/back/methods/client-dms/removeFile.js b/modules/client/back/methods/client-dms/removeFile.js index 786a329289..bc9a0f7194 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 843e9549fc..cdb8655005 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 47d5f6d2f5..f805c4be95 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 47a495d799..85dbb74229 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 99ede27c67..1a59659557 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 0cc5df9a2f..fc1254dd80 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 5f962677d8..e8bf8d8a06 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 3f71ab79e6..55cc9d4369 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 14b19498e3..6dbcd0140c 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 0c652484e9..5e536faffb 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 95d00d374f..b204ebeb4a 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 a32f308ab1..4cd55d9dfd 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 b2244ebbb0..df1b587378 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 72b7027798..a9e14effa6 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 f32915bb5c..bfde05162a 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 884cbd34ed..f57744f8a0 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 5ea89197da..89ec544945 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 740f0cf534..e36180dfc5 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 03f592f51e..0000000000 --- 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 e48b39fdc0..acab99d915 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 e2bc0785e3..7fb3b870e0 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 6ad64282e7..8fa130248c 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 30379eaf68..fa804f4d80 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 cdf0c5e6ed..6a1592037a 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 a7508b4e8a..0f3e389b6f 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/methods/invoice-in/summary.js b/modules/invoiceIn/back/methods/invoice-in/summary.js index 0e55eeaace..fe198b2b40 100644 --- a/modules/invoiceIn/back/methods/invoice-in/summary.js +++ b/modules/invoiceIn/back/methods/invoice-in/summary.js @@ -112,7 +112,7 @@ module.exports = Self => { { relation: 'taxTypeSage', scope: { - fields: ['vat'] + fields: ['vat', 'rate'] } }] } diff --git a/modules/invoiceIn/back/models/invoice-in-tax.json b/modules/invoiceIn/back/models/invoice-in-tax.json index 5bfbbe2a8d..53b5548b63 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 5be55c8510..59c179e768 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 18e6903d68..043dfbead2 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 fa22dab1e0..530b02353c 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 ae9c404afd..96c7893160 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 d70a8fce52..87e9a67eae 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 800a4ea835..11575999ac 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 dde535c998..782eaf6d81 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}}}; @@ -78,24 +78,20 @@ module.exports = Self => { const sales = await models.Sale.find(filterTicket, myOptions); const salesIds = sales.map(sale => sale.id); - const clonedTickets = await models.Sale.clone(ctx, salesIds, servicesIds, null, false, false, myOptions); + const clonedTickets = await models.Sale.clone(ctx, salesIds, servicesIds, null, false, myOptions); 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 43e4f07ef5..58f6f63b72 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 17b3126178..58d50a12c5 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 0052f0c037..435db36124 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 d3862a753c..2c28599e7c 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 0f74b5fece..aaeefd9cc4 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 c3362a319d..5872911e46 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">
{ const result = await models.SalesMonitor.salesFilter(ctx, filter, options); const firstRow = result[0]; - expect(result.length).toEqual(15); + expect(result.length).toEqual(12); expect(firstRow.alertLevel).not.toEqual(0); await tx.rollback(); diff --git a/modules/order/back/methods/order/catalogFilter.js b/modules/order/back/methods/order/catalogFilter.js index 722f3e0966..dc0e2f60b3 100644 --- a/modules/order/back/methods/order/catalogFilter.js +++ b/modules/order/back/methods/order/catalogFilter.js @@ -50,7 +50,7 @@ module.exports = Self => { stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.item'); stmt = new ParameterizedSQL( - `CREATE TEMPORARY TABLE tmp.item + `CREATE TEMPORARY TABLE tmp.item (PRIMARY KEY (itemFk)) ENGINE = MEMORY SELECT DISTINCT i.id AS itemFk, @@ -112,7 +112,6 @@ module.exports = Self => { i.value7, i.tag8, i.value8, - i.stars, tci.price, tci.available, w.lastName, @@ -131,7 +130,7 @@ module.exports = Self => { if (orderBy.isTag) { stmt.merge({ sql: ` - LEFT JOIN vn.itemTag itg + LEFT JOIN vn.itemTag itg LEFT JOIN vn.tag t ON t.id = itg.tagFk ON itg.itemFk = tci.itemFk AND itg.tagFk = ?`, params: [orderBy.field], @@ -140,7 +139,7 @@ module.exports = Self => { const way = orderBy.way == 'DESC' ? 'DESC' : 'ASC'; const tag = await Self.app.models.Tag.findById(orderBy.field, null, myOptions); const orderSql = ` - ORDER BY + ORDER BY itg.value IS NULL, ${tag.isQuantitative ? 'CAST(itg.value AS SIGNED)' : 'itg.value'} ${way}`; @@ -158,7 +157,7 @@ module.exports = Self => { // Apply item prices const pricesIndex = stmts.push( - `SELECT + `SELECT tcp.itemFk, tcp.grouping, tcp.price, diff --git a/modules/order/front/catalog-view/index.html b/modules/order/front/catalog-view/index.html index 5d60211ed7..081ce05a02 100644 --- a/modules/order/front/catalog-view/index.html +++ b/modules/order/front/catalog-view/index.html @@ -37,11 +37,6 @@ value="{{::item.value7}}"> - - - - @@ -54,7 +49,6 @@ {{::item.minQuantity}} - + +

{{ticket.street}}

+

{{ticket.nickname}}

diff --git a/print/templates/reports/invoice/sql/tickets.sql b/print/templates/reports/invoice/sql/tickets.sql index a8385599c1..35828c5de0 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