diff --git a/.vscode/settings.json b/.vscode/settings.json index 36b7e21d84..03479d27ac 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -16,6 +16,7 @@ }, "cSpell.words": [ "salix", - "fdescribe" + "fdescribe", + "Loggable" ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 1907f46bd2..29df758278 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2406.01] - 2024-02-08 + +### Added +### Changed +### Fixed + + +## [2404.01] - 2024-01-25 + +### Added +### Changed +### Fixed + + ## [2402.01] - 2024-01-11 ### Added diff --git a/back/methods/osticket/sendToSupport.js b/back/methods/osticket/sendToSupport.js index 5b9ebb4e3b..135774919c 100644 --- a/back/methods/osticket/sendToSupport.js +++ b/back/methods/osticket/sendToSupport.js @@ -35,10 +35,17 @@ module.exports = Self => { let html = `Motivo:
${reason}
`; html += `Usuario:
${ctx.req.accessToken.userId} ${emailUser.email}
`; + delete additionalData.backError.config.headers.Authorization; + const httpRequest = JSON.parse(additionalData?.httpRequest); + + if (httpRequest) + delete httpRequest.config.headers.Authorization; + additionalData.httpRequest = httpRequest; + for (const data in additionalData) html += `${data}:
${tryParse(additionalData[data])}
`; - const subjectReason = JSON.parse(additionalData?.httpRequest)?.data?.error; + const subjectReason = httpRequest?.data?.error; smtp.send({ to: `${config.app.reportEmail}, ${emailUser.email}`, subject: diff --git a/back/methods/postcode/filter.js b/back/methods/postcode/filter.js new file mode 100644 index 0000000000..9986a16c9e --- /dev/null +++ b/back/methods/postcode/filter.js @@ -0,0 +1,77 @@ +const {ParameterizedSQL} = require('loopback-connector'); +const {buildFilter, mergeFilters} = require('vn-loopback/util/filter'); +// const {models} = require('vn-loopback/server/server'); + +module.exports = Self => { + Self.remoteMethod('filter', { + description: + 'Find all postcodes of the model matched by postcode, town, province or country.', + accessType: 'READ', + returns: { + type: ['object'], + root: true, + }, + accepts: [ + { + arg: 'filter', + type: 'object', + description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string', + http: {source: 'query'} + }, + { + arg: 'search', + type: 'string', + description: 'Value to filter', + http: {source: 'query'} + }, + ], + http: { + path: `/filter`, + verb: 'GET', + }, + }); + Self.filter = async(ctx, filter, options) => { + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const conn = Self.dataSource.connector; + const where = buildFilter(ctx.args, (param, value) => { + switch (param) { + case 'search': + return {or: [ + {'pc.code': {like: `%${value}%`}}, + {'t.name': {like: `%${value}%`}}, + {'p.name': {like: `%${value}%`}}, + {'c.country': {like: `%${value}%`}} + ] + }; + } + }) ?? {}; + + filter = mergeFilters(ctx.args?.filter ?? {}, {where}); + + const stmts = []; + let stmt; + stmt = new ParameterizedSQL(` + SELECT + pc.code, + t.name as town, + p.name as province, + c.country + FROM + postCode pc + JOIN town t on t.id = pc.townFk + JOIN province p on p.id = t.provinceFk + JOIN country c on c.id = p.countryFk + `); + + stmt.merge(conn.makeSuffix(filter)); + const itemsIndex = stmts.push(stmt) - 1; + + const sql = ParameterizedSQL.join(stmts, ';'); + const result = await conn.executeStmt(sql, myOptions); + return itemsIndex === 0 ? result : result[itemsIndex]; + }; +}; diff --git a/back/methods/postcode/specs/filter.spec.js b/back/methods/postcode/specs/filter.spec.js new file mode 100644 index 0000000000..c393b629a0 --- /dev/null +++ b/back/methods/postcode/specs/filter.spec.js @@ -0,0 +1,103 @@ +const {models} = require('vn-loopback/server/server'); + +describe('Postcode filter()', () => { + it('should retrieve with no filter', async() => { + const tx = await models.Postcode.beginTransaction({}); + const options = {transaction: tx}; + + try { + const ctx = { + args: { + + }, + }; + const results = await models.Postcode.filter(ctx, options); + + expect(results.length).toBeGreaterThan(0); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should retrieve with filter as postcode', async() => { + const tx = await models.Postcode.beginTransaction({}); + const options = {transaction: tx}; + + try { + const ctx = { + args: { + search: 46, + }, + }; + const results = await models.Postcode.filter(ctx, options); + + expect(results.length).toEqual(4); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should retrieve with filter as city', async() => { + const tx = await models.Postcode.beginTransaction({}); + const options = {transaction: tx}; + + try { + const ctx = { + args: { + search: 'Alz', + }, + }; + const results = await models.Postcode.filter(ctx, options); + + expect(results.length).toEqual(1); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should retrieve with filter as province', async() => { + const tx = await models.Postcode.beginTransaction({}); + const options = {transaction: tx}; + + try { + const ctx = { + args: { + search: 'one', + }, + }; + const results = await models.Postcode.filter(ctx, options); + + expect(results.length).toEqual(4); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should retrieve with filter as country', async() => { + const tx = await models.Postcode.beginTransaction({}); + const options = {transaction: tx}; + + try { + const ctx = { + args: { + search: 'Ec', + }, + }; + const results = await models.Postcode.filter(ctx, options); + + expect(results.length).toEqual(1); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/back/methods/viaexpress-config/internationalExpedition.js b/back/methods/viaexpress-config/internationalExpedition.js index 698bb1dac4..df50b21e5f 100644 --- a/back/methods/viaexpress-config/internationalExpedition.js +++ b/back/methods/viaexpress-config/internationalExpedition.js @@ -20,7 +20,7 @@ module.exports = Self => { } }); - Self.internationalExpedition = async expeditionFk => { + Self.internationalExpedition = async (expeditionFk) => { const models = Self.app.models; const viaexpressConfig = await models.ViaexpressConfig.findOne({ diff --git a/back/methods/viaexpress-config/renderer.js b/back/methods/viaexpress-config/renderer.js index e9abce5ca7..c8533ea6b6 100644 --- a/back/methods/viaexpress-config/renderer.js +++ b/back/methods/viaexpress-config/renderer.js @@ -20,11 +20,11 @@ module.exports = Self => { } }); - Self.renderer = async expeditionFk => { + Self.renderer = async (expeditionFk) => { const models = Self.app.models; const viaexpressConfig = await models.ViaexpressConfig.findOne({ - fields: ['client', 'user', 'password', 'defaultWeight', 'deliveryType'] + fields: ['client', 'user', 'password', 'defaultWeight', 'deliveryType', 'agencyModeFk'] }); const expedition = await models.Expedition.findOne({ @@ -34,7 +34,7 @@ module.exports = Self => { { relation: 'ticket', scope: { - fields: ['shipped', 'addressFk', 'clientFk', 'companyFk'], + fields: ['shipped', 'addressFk', 'clientFk', 'companyFk', 'agencyModeFk'], include: [ { relation: 'client', @@ -102,7 +102,6 @@ module.exports = Self => { } ] } - } ] }); @@ -110,13 +109,15 @@ module.exports = Self => { const ticket = expedition.ticket(); const sender = ticket.company().client(); const shipped = ticket.shipped.toISOString(); + const isInterdia = (ticket.agencyModeFk === viaexpressConfig.agencyModeFk) const data = { viaexpressConfig, sender, senderAddress: sender.defaultAddress(), client: ticket.client(), address: ticket.address(), - shipped + shipped, + isInterdia }; const template = fs.readFileSync(__dirname + '/template.ejs', 'utf-8'); diff --git a/back/methods/viaexpress-config/template.ejs b/back/methods/viaexpress-config/template.ejs index 0b6eb468c6..f9acfe316d 100644 --- a/back/methods/viaexpress-config/template.ejs +++ b/back/methods/viaexpress-config/template.ejs @@ -13,7 +13,7 @@ 0 0 0 - 0 + <%= isInterdia %> 0 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 194747949b..d00085d8a9 100644 --- a/back/methods/vn-user/renew-token.js +++ b/back/methods/vn-user/renew-token.js @@ -1,14 +1,5 @@ -const UserError = require('vn-loopback/util/user-error'); const {models} = require('vn-loopback/server/server'); -const handlePromiseLogout = (Self, {id}, courtesyTime) => { - new Promise(res => { - setTimeout(() => { - res(Self.logout(id)); - } - , courtesyTime * 1000); - }); -}; module.exports = Self => { Self.remoteMethodCtx('renewToken', { description: 'Checks if the token has more than renewPeriod seconds to live and if so, renews it', @@ -28,14 +19,26 @@ module.exports = Self => { const {accessToken: token} = ctx.req; // Check if current token is valid - const isValid = await validateToken(token); - if (isValid) + + 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 {courtesyTime} = await models.AccessTokenConfig.findOne({fields: ['courtesyTime']}); - // Schedule to remove current token - handlePromiseLogout(Self, token, courtesyTime); + setTimeout(async() => { + try { + await Self.logout(token.id); + } catch (err) { + // eslint-disable-next-line no-console + console.error(err); + } + }, courtesyTime * 1000); // Create new accessToken const user = await Self.findById(token.userId); @@ -43,14 +46,4 @@ module.exports = Self => { return {id: accessToken.id, ttl: accessToken.ttl}; }; - - async function validateToken(token) { - const accessTokenConfig = await models.AccessTokenConfig.findOne({fields: ['renewPeriod', 'courtesyTime']}); - const now = Date.now(); - const differenceMilliseconds = now - token.created; - const differenceSeconds = Math.floor(differenceMilliseconds / 1000); - const isValid = differenceSeconds < accessTokenConfig.renewPeriod - accessTokenConfig.courtesyTime; - - return isValid; - } }; 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/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/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/postcode.js b/back/models/postcode.js index b08fdaa40b..63fd0657fc 100644 --- a/back/models/postcode.js +++ b/back/models/postcode.js @@ -1,6 +1,7 @@ let UserError = require('vn-loopback/util/user-error'); module.exports = Self => { + require('../methods/postcode/filter.js')(Self); Self.rewriteDbError(function(err) { if (err.code === 'ER_DUP_ENTRY') return new UserError(`This postcode already exists`); 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/viaexpress-config.json b/back/models/viaexpress-config.json index 8df24201be..acab1f7e61 100644 --- a/back/models/viaexpress-config.json +++ b/back/models/viaexpress-config.json @@ -29,6 +29,9 @@ }, "deliveryType": { "type": "string" + }, + "agencyModeFk": { + "type": "number" } } } 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 b1d09f0c0b..39e7008caf 100644 --- a/back/models/vn-user.js +++ b/back/models/vn-user.js @@ -258,18 +258,20 @@ module.exports = function(Self) { 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 86ffac2bbc..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,27 +98,30 @@ "principalType": "ROLE", "principalId": "$everyone", "permission": "ALLOW" - }, - { - "property": "recoverPassword", - "accessType": "EXECUTE", - "principalType": "ROLE", - "principalId": "$everyone", - "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 efade4d7dd..2678f6744b 100644 --- a/back/tests.js +++ b/back/tests.js @@ -59,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/240001/00-truncate-where-signInLog.sql b/db/changes/240001/00-truncate-where-signInLog.sql deleted file mode 100644 index 93d80d7161..0000000000 --- a/db/changes/240001/00-truncate-where-signInLog.sql +++ /dev/null @@ -1 +0,0 @@ -DELETE FROM `account`.`signInLog` where owner <> FALSE diff --git a/db/changes/235001/00-silexToSalix.sql b/db/changes/240002/00-silexToSalix.sql similarity index 100% rename from db/changes/235001/00-silexToSalix.sql rename to db/changes/240002/00-silexToSalix.sql 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-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/240202/00-saleComponent.sql b/db/changes/240202/00-saleComponent.sql new file mode 100644 index 0000000000..2e10210008 --- /dev/null +++ b/db/changes/240202/00-saleComponent.sql @@ -0,0 +1,589 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`sale_calculateComponent`(vSelf INT, vOption VARCHAR(25)) +proc: BEGIN +/** + * Crea tabla temporal para vn.sale_recalcComponent() para recalcular los componentes + * + * @param vSelf Id de la venta + * @param vOption indica en que componente pone el descuadre, NULL en casos habituales + */ + CREATE OR REPLACE TEMPORARY TABLE tmp.recalculateSales + SELECT s.id + FROM sale s + WHERE s.id = vSelf; + + CALL sale_recalcComponent(vOption); + + DROP TEMPORARY TABLE tmp.recalculateSales; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`sale_checkNoComponents`(vCreatedFrom DATETIME, vCreatedTo DATETIME) +BEGIN +/** + * Comprueba que las ventas creadas entre un rango de fechas tienen componentes + * + * @param vCreatedFrom inicio del rango + * @param vCreatedTo fin del rango + */ + DECLARE v_done BOOL DEFAULT FALSE; + DECLARE vSaleFk INTEGER; + DECLARE vTicketFk INTEGER; + DECLARE vConcept VARCHAR(50); + DECLARE vCur CURSOR FOR + SELECT s.id + FROM sale s + JOIN ticket t ON t.id = s.ticketFk + JOIN item i ON i.id = s.itemFk + JOIN itemType tp ON tp.id = i.typeFk + JOIN itemCategory ic ON ic.id = tp.categoryFk + LEFT JOIN tmp.coste c ON c.id = s.id + WHERE s.created >= vCreatedFrom AND s.created <= vCreatedTo + AND c.id IS NULL + AND t.agencyModeFk IS NOT NULL + AND t.isDeleted IS FALSE + AND t.warehouseFk = 60 + AND ic.merchandise != FALSE + GROUP BY s.id; + + DECLARE CONTINUE HANDLER FOR NOT FOUND + SET v_done = TRUE; + + DROP TEMPORARY TABLE IF EXISTS tmp.coste; + + DROP TEMPORARY TABLE IF EXISTS tmp.coste; + CREATE TEMPORARY TABLE tmp.coste + (PRIMARY KEY (id)) ENGINE = MEMORY + SELECT s.id + FROM sale s + JOIN item i ON i.id = s.itemFk + JOIN itemType tp ON tp.id = i.typeFk + JOIN itemCategory ic ON ic.id = tp.categoryFk + JOIN saleComponent sc ON sc.saleFk = s.id + JOIN component c ON c.id = sc.componentFk + JOIN componentType ct ON ct.id = c.typeFk AND ct.id = 6 + WHERE s.created >= vCreatedFrom + AND ic.merchandise != FALSE; + + OPEN vCur; + + l: LOOP + SET v_done = FALSE; + FETCH vCur INTO vSaleFk; + + IF v_done THEN + LEAVE l; + END IF; + + SELECT ticketFk, concept + INTO vTicketFk, vConcept + FROM sale + WHERE id = vSaleFk; + + CALL sale_calculateComponent(vSaleFk, 'renewPrices'); + END LOOP; + + CLOSE vCur; + DROP TEMPORARY TABLE tmp.coste; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`sale_recalcComponent`(vOption VARCHAR(25)) +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, 'renewPrices', 'imbalance'); + 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`.`ticketCalculateClon`(IN vTicketNew INT, vTicketOld INT) +BEGIN +/* + * Recalcula los componentes un ticket clonado, + * las lineas a precio cero fuerza para que tengan precio, el resto lo respeta + * @param vTicketNew nuevo ticket clonado + * @param vTicketOld icket original, a partir del qual se clonara el nuevo +*/ + + REPLACE INTO orderTicket(orderFk,ticketFk) + SELECT orderFk, vTicketNew + FROM orderTicket + WHERE ticketFk = vTicketOld; + + -- Bionizamos lineas con Preu = 0 + CREATE OR REPLACE TEMPORARY TABLE tmp.recalculateSales + (PRIMARY KEY (id)) ENGINE = MEMORY + SELECT id + FROM sale + WHERE ticketFk = vTicketNew AND price = 0; + + CALL sale_recalcComponent('renewPrices'); + + -- Bionizamos lineas con Preu > 0 + CREATE OR REPLACE TEMPORARY TABLE tmp.recalculateSales + (PRIMARY KEY (id)) ENGINE = MEMORY + SELECT id + FROM sale + WHERE ticketFk = vTicketNew AND price > 0; + + CALL sale_recalcComponent('imbalance'); + + DROP TEMPORARY TABLE IF EXISTS tmp.recalculateSales; + +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticketComponentUpdate`( + vTicketFk INT, + vClientFk INT, + vAgencyModeFk INT, + vAddressFk INT, + vWarehouseFk TINYINT, + vCompanyFk SMALLINT, + vShipped DATETIME, + vLanded DATE, + vIsDeleted BOOLEAN, + vHasToBeUnrouted BOOLEAN, + vOption VARCHAR(25)) +BEGIN + + DECLARE EXIT HANDLER FOR SQLEXCEPTION + BEGIN + ROLLBACK; + RESIGNAL; + END; + + START TRANSACTION; + + IF (SELECT addressFk FROM ticket WHERE id = vTicketFk) <> vAddressFk THEN + + UPDATE ticket t + JOIN address a ON a.id = vAddressFk + SET t.nickname = a.nickname + WHERE t.id = vTicketFk; + END IF; + + UPDATE ticket t + SET + t.clientFk = vClientFk, + t.agencyModeFk = vAgencyModeFk, + t.addressFk = vAddressFk, + t.warehouseFk = vWarehouseFk, + t.companyFk = vCompanyFk, + t.landed = vLanded, + t.shipped = vShipped, + t.isDeleted = vIsDeleted + WHERE + t.id = vTicketFk; + + IF vHasToBeUnrouted THEN + UPDATE ticket t SET t.routeFk = NULL + WHERE t.id = vTicketFk; + END IF; + + DROP TEMPORARY TABLE IF EXISTS tmp.sale; + CREATE TEMPORARY TABLE tmp.sale + (PRIMARY KEY (saleFk)) + ENGINE = MEMORY + SELECT id AS saleFk, vWarehouseFk warehouseFk + FROM sale s WHERE s.ticketFk = vTicketFk; + + CALL ticketComponentUpdateSale (vOption); + + DROP TEMPORARY TABLE tmp.sale; + COMMIT; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticketComponentUpdateSale`(vCode VARCHAR(25)) +BEGIN +/** + * A partir de la tabla tmp.sale, crea los Movimientos_componentes + * y modifica el campo Preu de la tabla Movimientos + * + * @param i_option integer tipo de actualizacion + * @param table tmp.sale tabla memory con el campo saleFk, warehouseFk + **/ + DECLARE vComponentFk INT; + + IF vCode <> 'renewPrices' THEN + SELECT id INTO vComponentFk FROM component WHERE `code` = vCode; + END IF; + + DELETE sc.* + FROM tmp.sale tmps + JOIN saleComponent sc ON sc.saleFk = tmps.saleFk + JOIN `component` c ON c.id = sc.componentFk + WHERE c.isRenewable; + + REPLACE INTO saleComponent(saleFk, componentFk, value) + SELECT s.id, tc.componentFk, tc.cost + FROM sale s + JOIN tmp.sale tmps ON tmps.saleFk = s.id + JOIN tmp.ticketComponent tc ON tc.itemFk = s.itemFk AND tc.warehouseFk = tmps.warehouseFk + LEFT JOIN saleComponent sc ON sc.saleFk = s.id + AND sc.componentFk = tc.componentFk + LEFT JOIN `component` c ON c.id = tc.componentFk + WHERE IF(sc.componentFk IS NULL AND NOT c.isRenewable, FALSE, TRUE); + + -- Añadir componente venta por paquete + REPLACE INTO saleComponent(saleFk, componentFk, value) + SELECT t.id, t.componentFk, t.cost + FROM ( + SELECT s.id, tc.componentFk, tc.cost, MOD(s.quantity, b.packing) as resto + FROM vn.sale s + JOIN tmp.sale tmps ON tmps.saleFk = s.id + JOIN cache.last_buy lb ON lb.item_id = s.itemFk AND tmps.warehouseFk = lb.warehouse_id + JOIN vn.buy b ON b.id = buy_id + JOIN tmp.ticketComponent tc ON tc.itemFk = s.itemFk AND tc.warehouseFk = tmps.warehouseFk + JOIN `component` c ON c.id = tc.componentFk AND c.code = 'salePerPackage' + LEFT JOIN ( + SELECT s.id + FROM vn.sale s + JOIN tmp.sale tmps ON tmps.saleFk = s.id + JOIN tmp.ticketComponent tc ON tc.itemFk = s.itemFk AND tc.warehouseFk = tmps.warehouseFk + JOIN saleComponent sc ON sc.saleFk = s.id AND sc.componentFk = tc.componentFk + JOIN `component` c ON c.id = sc.componentFk AND c.code = 'lastUnitsDiscount' + ) tp ON tp.id = s.id + WHERE tp.id IS NULL + HAVING resto <> 0) t; + + IF vCode <> 'renewPrices' THEN + REPLACE INTO saleComponent(saleFk, componentFk, value) + SELECT s.id, vComponentFk, ROUND((s.price * (100 - s.discount) / 100) - SUM(sc.value), 3) dif + FROM sale s + JOIN tmp.sale tmps ON tmps.saleFk = s.id + LEFT JOIN saleComponent sc ON sc.saleFk = s.id + WHERE sc.saleFk <> vComponentFk + GROUP BY s.id + HAVING dif <> 0; + ELSE + UPDATE sale s + JOIN item i on i.id = s.itemFk + JOIN itemType it on it.id = i.typeFk + JOIN (SELECT SUM(sc.value) sumValue, sc.saleFk + FROM saleComponent sc + JOIN tmp.sale tmps ON tmps.saleFk = sc.saleFk + GROUP BY sc.saleFk) sc ON sc.saleFk = s.id + SET s.price = sumValue / ((100 - s.discount) / 100) + WHERE it.code != 'PRT' ; + + REPLACE INTO saleComponent(saleFk, componentFk, value) + SELECT s.id, 21, ROUND((s.price * (100 - s.discount) / 100) - SUM(value), 3) saleValue + FROM sale s + JOIN tmp.sale tmps ON tmps.saleFk = s.id + LEFT JOIN saleComponent sc ON sc.saleFk = s.id + WHERE sc.componentFk != 21 + GROUP BY s.id + HAVING ROUND(saleValue, 4) <> 0; + END IF; + + UPDATE sale s + JOIN ( + SELECT SUM(sc.value) sumValue, sc.saleFk + FROM saleComponent sc + JOIN tmp.sale tmps ON tmps.saleFk = sc.saleFk + JOIN `component` c ON c.id = sc.componentFk + JOIN componentType ct on ct.id = c.typeFk AND ct.isBase + GROUP BY sc.saleFk) sc ON sc.saleFk = s.id + SET s.priceFixed = sumValue, s.isPriceFixed = 1; + + DELETE sc.* + FROM saleComponent sc + JOIN tmp.sale tmps ON tmps.saleFk = sc.saleFk + JOIN sale s on s.id = sc.saleFk + JOIN item i ON i.id = s.itemFk + JOIN itemType it ON it.id = i.typeFk + WHERE it.code = 'PRT'; + + INSERT INTO saleComponent(saleFk, componentFk, value) + SELECT s.id, 15, s.price + FROM sale s + JOIN tmp.sale tmps ON tmps.saleFk = s.id + JOIN item i ON i.id = s.itemFK + JOIN itemType it ON it.id = i.typeFk + WHERE it.code = 'PRT' AND s.price > 0; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_checkNoComponents`(vShippedFrom DATETIME, vShippedTo DATETIME) +BEGIN + +/** + * Comprueba que los tickets entre un rango de fechas tienen componentes + * y recalcula sus componentes + * + * @param vShippedFrom rango inicial de fecha + * @param vShippedTo rango final de fecha + */ + + CREATE OR REPLACE TEMPORARY TABLE tmp.coste + (primary key (id)) ENGINE = MEMORY + SELECT s.id + FROM ticket t + JOIN sale s ON s.ticketFk = t.id + JOIN item i ON i.id = s.itemFk + JOIN itemType tp ON tp.id = i.typeFk + JOIN itemCategory ic ON ic.id = tp.categoryFk + JOIN saleComponent sc ON sc.saleFk = s.id + JOIN component c ON c.id = sc.componentFk + JOIN componentType ct ON ct.id = c.typeFk AND ct.id = 1 + WHERE t.shipped BETWEEN vShippedFrom AND vShippedTo + AND ic.merchandise; + + CREATE OR REPLACE TEMPORARY TABLE tmp.recalculateSales + (primary key (id)) ENGINE = MEMORY + SELECT DISTINCT s.id + FROM ticket t + JOIN sale s ON s.ticketFk = t.id + JOIN item i ON i.id = s.itemFk + JOIN itemType tp ON tp.id = i.typeFk + JOIN itemCategory ic ON ic.id = tp.categoryFk + LEFT JOIN tmp.coste c ON c.id = s.id + WHERE t.shipped >= vShippedFrom AND t.shipped <= vShippedTo + AND c.id IS NULL + AND ic.merchandise; + + CALL sale_recalcComponent('renewPrices'); + + DROP TEMPORARY TABLE tmp.recalculateSales; + DROP TEMPORARY TABLE tmp.coste; + END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_componentMakeUpdate`( + vTicketFk INT, + vClientFk INT, + vNickname VARCHAR(50), + vAgencyModeFk INT, + vAddressFk INT, + vZoneFk INT, + vWarehouseFk INT, + vCompanyFk INT, + vShipped DATETIME, + vLanded DATE, + vIsDeleted BOOLEAN, + vHasToBeUnrouted BOOLEAN, + vOption VARCHAR(25)) +BEGIN + + /** + * Modifica en el ticket los campos que se le pasan por parámetro + * y cambia sus componentes + * + * @param vTicketFk Id del ticket a modificar + * @param vClientFk nuevo cliente + * @param vNickname nuevo alias + * @param vAgencyModeFk nueva agencia + * @param vAddressFk nuevo consignatario + * @param vZoneFk nueva zona + * @param vWarehouseFk nuevo almacen + * @param vCompanyFk nueva empresa + * @param vShipped nueva fecha del envio de mercancia + * @param vLanded nueva fecha de recepcion de mercancia + * @param vIsDeleted si se borra el ticket + * @param vHasToBeUnrouted si se le elimina la ruta al ticket + * @param vOption opcion para el case del proc ticketComponentUpdateSale + */ + + DECLARE vPrice DECIMAL(10,2); + DECLARE vBonus DECIMAL(10,2); + + CALL ticket_componentPreview (vTicketFk, vLanded, vAddressFk, vZoneFk, vWarehouseFk); + + IF (SELECT addressFk FROM ticket WHERE id = vTicketFk) <> vAddressFk THEN + + UPDATE ticket t + JOIN address a ON a.id = vAddressFk + SET t.nickname = a.nickname + WHERE t.id = vTicketFk; + + END IF; + + CALL zone_getShipped(vLanded, vAddressFk, vAgencyModeFk, TRUE); + + SELECT zoneFk, price, bonus INTO vZoneFk, vPrice, vBonus + FROM tmp.zoneGetShipped + WHERE shipped BETWEEN DATE(vShipped) AND util.dayEnd(vShipped) AND warehouseFk = vWarehouseFk LIMIT 1; + + UPDATE ticket t + SET + t.clientFk = vClientFk, + t.nickname = vNickname, + t.agencyModeFk = vAgencyModeFk, + t.addressFk = vAddressFk, + t.zoneFk = vZoneFk, + t.zonePrice = vPrice, + t.zoneBonus = vBonus, + t.warehouseFk = vWarehouseFk, + t.companyFk = vCompanyFk, + t.landed = vLanded, + t.shipped = vShipped, + t.isDeleted = vIsDeleted + WHERE + t.id = vTicketFk; + + IF vHasToBeUnrouted THEN + UPDATE ticket t SET t.routeFk = NULL + WHERE t.id = vTicketFk; + END IF; + + DROP TEMPORARY TABLE IF EXISTS tmp.sale; + CREATE TEMPORARY TABLE tmp.sale + (PRIMARY KEY (saleFk)) + ENGINE = MEMORY + SELECT id AS saleFk, vWarehouseFk warehouseFk + FROM sale s WHERE s.ticketFk = vTicketFk; + + DROP TEMPORARY TABLE IF EXISTS tmp.ticketComponent; + CREATE TEMPORARY TABLE tmp.ticketComponent + SELECT * FROM tmp.ticketComponentPreview; + + CALL ticketComponentUpdateSale (vOption); + + DROP TEMPORARY TABLE tmp.sale; + DROP TEMPORARY TABLE IF EXISTS tmp.ticketComponent; + + DROP TEMPORARY TABLE tmp.zoneGetShipped, tmp.ticketComponentPreview; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_recalcComponents`(vSelf INT, vOption VARCHAR(25)) +proc: BEGIN + +/** + * Crea tabla temporal para sale_recalcComponent() para recalcular los componentes + * + * @param vSelf Id del ticket + * @param vOption si no se quiere forzar llamar con NULL + */ + + CREATE OR REPLACE TEMPORARY TABLE tmp.recalculateSales + SELECT s.id + FROM sale s + WHERE s.ticketFk = vSelf; + + CALL sale_recalcComponent(vOption); + + DROP TEMPORARY TABLE tmp.recalculateSales; +END$$ +DELIMITER ; + +TRUNCATE TABLE `vn`.`ticketUpdateAction`; +INSERT INTO `vn`.`ticketUpdateAction` (id, description, code) VALUES(1, 'Cambiar los precios en el ticket', 'renewPrice'); +INSERT INTO `vn`.`ticketUpdateAction` (id, description, code) VALUES(2, 'Convertir en maná', 'mana'); \ No newline at end of file diff --git a/db/changes/240203/.gitkeep b/db/changes/240203/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/db/changes/240203/00-viaExpressConfig.sql b/db/changes/240203/00-viaExpressConfig.sql new file mode 100644 index 0000000000..57832e7cda --- /dev/null +++ b/db/changes/240203/00-viaExpressConfig.sql @@ -0,0 +1,2 @@ +ALTER TABLE `vn`.`viaexpressConfig` ADD agencyModeFk int DEFAULT NULL NULL COMMENT 'Indica el agencyMode que es interdia'; +ALTER TABLE `vn`.`viaexpressConfig` ADD CONSTRAINT viaexpressConfig_agencyMode_Fk FOREIGN KEY (agencyModeFK) REFERENCES vn.agencyMode(id) ON DELETE RESTRICT ON UPDATE RESTRICT; 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-revokeItem.sql b/db/changes/240401/00-revokeItem.sql new file mode 100644 index 0000000000..5f6a30620d --- /dev/null +++ b/db/changes/240401/00-revokeItem.sql @@ -0,0 +1,4 @@ +REVOKE UPDATE ON TABLE `vn`.`item` FROM `employee`; + + +GRANT UPDATE(id, equivalent, stems, minPrice, isToPrint, family, box, category, doPhoto, image, inkFk, intrastatFk, hasMinPrice, created, comment, typeFk, generic, producerFk, description, density, relevancy, expenseFk, isActive, subName, tag5, value5, tag6, value6, tag7, value7, tag8, value8, tag9, value9, tag10, value10, minimum, upToDown, supplyResponseFk, hasKgPrice, isFloramondo, isFragile, numberOfItemsPerCask, embalageCode, quality, stemMultiplier, itemPackingTypeFk, packingOut, genericFk, packingShelve, isLaid, lastUsed, weightByPiece, weightByPiece, editorFk, recycledPlastic, nonRecycledPlastic, minQuantity) ON TABLE `vn`.`item` TO `employee`; diff --git a/db/changes/240401/00-supplier.sql b/db/changes/240401/00-supplier.sql new file mode 100644 index 0000000000..e60707ecac --- /dev/null +++ b/db/changes/240401/00-supplier.sql @@ -0,0 +1,12 @@ +ALTER TABLE `vn`.`company` MODIFY COLUMN `supplierAccountFk` mediumint(8) unsigned DEFAULT NULL NULL COMMENT 'Cuenta por defecto para ingresos desde este pais'; + + +ALTER TABLE `vn`.`supplierAccount` +ADD COLUMN `countryFk` mediumint(8) unsigned DEFAULT NULL, +ADD CONSTRAINT `fk_supplierAccount_country` + FOREIGN KEY (`countryFk`) REFERENCES `country` (`id`) ON UPDATE CASCADE; + +ALTER TABLE `vn`.`supplierAccount` +ADD UNIQUE KEY `uk_supplier_country` (`supplierFk`, `countryFk`); + + diff --git a/db/changes/240401/00-ticketSmsToClientSms.sql b/db/changes/240401/00-ticketSmsToClientSms.sql new file mode 100644 index 0000000000..cd3cf7dd3a --- /dev/null +++ b/db/changes/240401/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/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/00-updateACL_Role_VnRole.sql b/db/changes/240401/00-updateACL_Role_VnRole.sql new file mode 100644 index 0000000000..5d108ac446 --- /dev/null +++ b/db/changes/240401/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/240401/01-saleGroupDetailDefaultSize.sql b/db/changes/240401/01-saleGroupDetailDefaultSize.sql new file mode 100644 index 0000000000..34529ba788 --- /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/01-supplierAccount.sql b/db/changes/240401/01-supplierAccount.sql new file mode 100644 index 0000000000..21ce7c71e1 --- /dev/null +++ b/db/changes/240401/01-supplierAccount.sql @@ -0,0 +1,9 @@ +UPDATE `vn`.`supplierAccount` sa + JOIN `vn`.`country` c ON sa.countryFk = c.id AND c.code = 'FR' + SET countryFk = c.id + WHERE iban = 'FR7630003012690002801121597'; + +UPDATE `vn`.`supplierAccount` sa + JOIN `vn`.`country` c ON sa.countryFk = c.id AND c.code = 'PT' + SET countryFk = c.id + WHERE iban = 'PT50001000005813059150168'; diff --git a/db/changes/240401/02-invoiceOutConfig_refLen.sql b/db/changes/240401/02-invoiceOutConfig_refLen.sql new file mode 100644 index 0000000000..a5f5c20887 --- /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/changes/240601/.gitkeep b/db/changes/240601/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 479704dd96..b243692bb2 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -600,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; @@ -622,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), @@ -719,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), @@ -928,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 @@ -2910,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 @@ -3010,7 +3010,36 @@ INSERT INTO `vn`.`invoiceCorrectionType` (`id`, `description`) (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/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/04-item/02_basic_data.spec.js b/e2e/paths/04-item/02_basic_data.spec.js index f177a98df2..3bad183037 100644 --- a/e2e/paths/04-item/02_basic_data.spec.js +++ b/e2e/paths/04-item/02_basic_data.spec.js @@ -27,11 +27,8 @@ describe('Item Edit basic data path', () => { it(`should edit the item basic data and confirm the item data was edited`, async() => { const values = { - name: 'Rose of Purity', - longName: 'RS Rose of Purity', type: 'Anthurium', intrastat: 'Coral y materiales similares', - origin: 'Spain', relevancy: 1, generic: 'Pallet', isActive: false, 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..8be3072690 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('7'); 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/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/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 c5e8d4fcf4..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,7 +178,8 @@ "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", @@ -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 a197197ba1..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,7 +305,8 @@ "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", @@ -330,5 +331,10 @@ "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}}", - "This user does not have an assigned tablet": "Este usuario no tiene tablet asignada" + "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/connectors/vn-mysql.js b/loopback/server/connectors/vn-mysql.js index 40ad78bde0..1f71695015 100644 --- a/loopback/server/connectors/vn-mysql.js +++ b/loopback/server/connectors/vn-mysql.js @@ -270,8 +270,8 @@ class VnMySQL extends MySQL { isLoggable(model) { const Model = this.getModelDefinition(model).model; - const settings = Model.definition.settings; - return settings.base && settings.base === 'Loggable'; + const {settings} = Model.definition; + return settings?.mixins?.Loggable; } invokeMethod(method, args, model, ctx, opts, cb) { @@ -291,7 +291,7 @@ class VnMySQL extends MySQL { } try { - const userId = opts.httpCtx && opts.httpCtx.active.accessToken.userId; + const userId = opts.httpCtx && opts.httpCtx.active?.accessToken?.userId; if (userId) { const user = await Model.app.models.VnUser.findById(userId, {fields: ['name']}, opts); await this.executeP(`CALL account.myUser_loginWithName(?)`, [user.name], opts); diff --git a/loopback/server/middleware/error-handler.js b/loopback/server/middleware/error-handler.js index 725826ae7f..cc7b81618b 100644 --- a/loopback/server/middleware/error-handler.js +++ b/loopback/server/middleware/error-handler.js @@ -1,10 +1,11 @@ +const SalixError = require('../../util/salixError'); const UserError = require('../../util/user-error'); const logToConsole = require('strong-error-handler/lib/logger'); module.exports = function() { return function(err, req, res, next) { // Thrown user errors - if (err instanceof UserError) { + if (err instanceof SalixError) { err.message = req.__(err.message, ...err.translateArgs); return next(err); } @@ -13,7 +14,7 @@ module.exports = function() { if (err.statusCode == 422) { try { let code; - let messages = err.details.messages; + let {messages} = err.details; for (code in messages) break; err.message = req.__(messages[code][0]); return next(err); 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/loopback/util/forbiddenError.js b/loopback/util/forbiddenError.js index 998cb45933..8f65610593 100644 --- a/loopback/util/forbiddenError.js +++ b/loopback/util/forbiddenError.js @@ -1,7 +1,8 @@ -module.exports = class ForbiddenError extends Error { +const SalixError = require('./salixError'); +module.exports = class ForbiddenError extends SalixError { constructor(message, code, ...translateArgs) { super(message); - this.name = 'ForbiddenError'; + this.name = ForbiddenError.name; this.statusCode = 403; this.code = code; this.translateArgs = translateArgs; diff --git a/loopback/util/salixError.js b/loopback/util/salixError.js new file mode 100644 index 0000000000..427b871abd --- /dev/null +++ b/loopback/util/salixError.js @@ -0,0 +1,5 @@ +module.exports = class SalixError extends Error { + constructor(message) { + super(message); + } +}; diff --git a/loopback/util/user-error.js b/loopback/util/user-error.js index c2d01e0804..feee208b35 100644 --- a/loopback/util/user-error.js +++ b/loopback/util/user-error.js @@ -4,10 +4,11 @@ * the final user, so they cannot contain sensitive data and must * be understandable by people who do not have a technical profile. */ -module.exports = class UserError extends Error { +const SalixError = require('./salixError'); +module.exports = class UserError extends SalixError { constructor(message, code, ...translateArgs) { super(message); - this.name = 'UserError'; + this.name = UserError.name; this.statusCode = 400; this.code = code; this.translateArgs = translateArgs; 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/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 a01590f58e..393c3b10d1 100644 --- a/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js +++ b/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js @@ -120,7 +120,7 @@ module.exports = Self => { observationTypeFk: obsevationType.id }, myOptions); - await models.TicketTracking.create({ + await models.Ticket.state(ctx, { ticketFk: newRefundTicket.id, stateFk: state.id, userFk: worker.id 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/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/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/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/model-config.json b/modules/client/back/model-config.json index a214077324..fc1254dd80 100644 --- a/modules/client/back/model-config.json +++ b/modules/client/back/model-config.json @@ -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.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/role-credit-limit.json b/modules/client/back/models/role-credit-limit.json index 4ea28b1a47..e36180dfc5 100644 --- a/modules/client/back/models/role-credit-limit.json +++ b/modules/client/back/models/role-credit-limit.json @@ -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/address/index/index.html b/modules/client/front/address/index/index.html index 8fdfce2bb8..ef3da40519 100644 --- a/modules/client/front/address/index/index.html +++ b/modules/client/front/address/index/index.html @@ -42,14 +42,15 @@ translate-attr="{title: 'Set as default'}"> -
{{::address.nickname}} - #{{::address.id}}
{{::address.street}}
- {{::address.postalCode}} - - {{::address.city}}, - {{::address.province.name}} + {{::address.postalCode}} - + {{::address.city}}, + {{::address.province.name}}, + {{::address.province.country.country}}
{{::address.phone}}, @@ -72,7 +73,7 @@ class="vn-hide-narrow vn-px-md border-solid-left" style="height: 6em; overflow: auto;"> - {{::observation.observationType.description}}: + {{::observation.observationType.description}}: {{::observation.description}} diff --git a/modules/client/front/address/index/index.js b/modules/client/front/address/index/index.js index 608bbbc202..4bad9d4c84 100644 --- a/modules/client/front/address/index/index.js +++ b/modules/client/front/address/index/index.js @@ -33,7 +33,13 @@ class Controller extends Section { }, { relation: 'province', scope: { - fields: ['id', 'name'] + fields: ['id', 'name', 'countryFk'], + include: { + relation: 'country', + scope: { + fields: ['id', 'country'] + } + } } } ] 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/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/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">
- - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + { 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}} - diff --git a/modules/ticket/front/basic-data/step-two/index.js b/modules/ticket/front/basic-data/step-two/index.js index 8c8a3a079e..9717d1ea42 100644 --- a/modules/ticket/front/basic-data/step-two/index.js +++ b/modules/ticket/front/basic-data/step-two/index.js @@ -25,12 +25,7 @@ class Controller extends Component { loadDefaultTicketAction() { const isSalesAssistant = this.aclService.hasAny(['salesAssistant']); - const defaultOption = isSalesAssistant ? 'turnInMana' : 'changePrice'; - const filter = {where: {code: defaultOption}}; - - this.$http.get(`TicketUpdateActions`, {filter}).then(response => { - return this.ticket.option = response.data[0].id; - }); + this.ticket.option = isSalesAssistant ? 'mana' : 'buyerDiscount'; } onStepChange() { @@ -112,7 +107,7 @@ class Controller extends Component { shipped: this.ticket.shipped, landed: this.ticket.landed, isDeleted: this.ticket.isDeleted, - option: parseInt(this.ticket.option), + option: this.ticket.option, isWithoutNegatives: this.ticket.withoutNegatives, withWarningAccept: this.ticket.withWarningAccept, keepPrice: false diff --git a/modules/ticket/front/descriptor-menu/index.js b/modules/ticket/front/descriptor-menu/index.js index 18db8c1474..cd819e6235 100644 --- a/modules/ticket/front/descriptor-menu/index.js +++ b/modules/ticket/front/descriptor-menu/index.js @@ -292,7 +292,7 @@ class Controller extends Section { const query = 'Tickets/refund'; return this.$http.post(query, params) .then(res => { - const refundTicket = res.data; + const [refundTicket] = res.data; this.vnApp.showSuccess(this.$t('The following refund ticket have been created', { ticketId: refundTicket.id })); diff --git a/modules/ticket/front/descriptor-menu/index.spec.js b/modules/ticket/front/descriptor-menu/index.spec.js index 1bb2701654..c755b14c3d 100644 --- a/modules/ticket/front/descriptor-menu/index.spec.js +++ b/modules/ticket/front/descriptor-menu/index.spec.js @@ -262,11 +262,12 @@ describe('Ticket Component vnTicketDescriptorMenu', () => { const params = { ticketsIds: [16] }; - $httpBackend.expectPOST('Tickets/refund', params).respond({id: 99}); + const response = {id: 99}; + $httpBackend.expectPOST('Tickets/refund', params).respond([response]); controller.refund(); $httpBackend.flush(); - expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', {id: 99}); + expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', response); }); }); diff --git a/modules/ticket/front/future/index.html b/modules/ticket/front/future/index.html index 5119d103b5..cceca266f7 100644 --- a/modules/ticket/front/future/index.html +++ b/modules/ticket/front/future/index.html @@ -61,6 +61,9 @@ Liters + + Import + Available Lines @@ -76,6 +79,7 @@ State + @@ -148,6 +152,13 @@ {{::ticket.liters}} + + + {{::(ticket.totalWithVat ? ticket.totalWithVat : 0) | currency: 'EUR': 2}} + + {{::ticket.lines}} 0 && parseInt(totalWithVat) < 50); + } exprBuilder(param, value) { switch (param) { @@ -145,6 +159,8 @@ export default class Controller extends Section { return {'ipt': {like: `%${value}%`}}; case 'futureIpt': return {'futureIpt': {like: `%${value}%`}}; + case 'totalWithVat': + return {'totalWithVat': value}; } } } diff --git a/modules/ticket/front/sale/index.js b/modules/ticket/front/sale/index.js index 4f6a9e7573..ed6d9b10a3 100644 --- a/modules/ticket/front/sale/index.js +++ b/modules/ticket/front/sale/index.js @@ -526,7 +526,7 @@ class Controller extends Section { const params = {salesIds: salesIds, withWarehouse: withWarehouse}; const query = 'Sales/refund'; this.$http.post(query, params).then(res => { - const refundTicket = res.data; + const [refundTicket] = res.data; this.vnApp.showSuccess(this.$t('The following refund ticket have been created', { ticketId: refundTicket.id })); diff --git a/modules/ticket/front/sale/index.spec.js b/modules/ticket/front/sale/index.spec.js index 70781eb581..36be32f52d 100644 --- a/modules/ticket/front/sale/index.spec.js +++ b/modules/ticket/front/sale/index.spec.js @@ -729,7 +729,7 @@ describe('Ticket', () => { salesIds: [1, 4], }; const refundTicket = {id: 99}; - $httpBackend.expect('POST', 'Sales/refund', params).respond(200, refundTicket); + $httpBackend.expect('POST', 'Sales/refund', params).respond(200, [refundTicket]); controller.createRefund(); $httpBackend.flush(); diff --git a/modules/ticket/front/tracking/edit/index.html b/modules/ticket/front/tracking/edit/index.html index 47f3670075..3b42cb2c26 100644 --- a/modules/ticket/front/tracking/edit/index.html +++ b/modules/ticket/front/tracking/edit/index.html @@ -21,7 +21,7 @@ + ng-model="$ctrl.userFk"> diff --git a/modules/ticket/front/tracking/edit/index.js b/modules/ticket/front/tracking/edit/index.js index 3057c492b1..fc9315d4cf 100644 --- a/modules/ticket/front/tracking/edit/index.js +++ b/modules/ticket/front/tracking/edit/index.js @@ -17,19 +17,19 @@ class Controller extends Section { set stateFk(value) { this.params.stateFk = value; this.isPickerDesignedState = this.getIsPickerDesignedState(value); - this.workerFk = window.localStorage.currentUserWorkerId; + this.userFk = window.localStorage.currentUserWorkerId; } get stateFk() { return this.params.stateFk; } - set workerFk(value) { - this.params.workerFk = value; + set userFk(value) { + this.params.userFk = value; } - get workerFk() { - return this.params.workerFk; + get userFk() { + return this.params.userFk; } getPickerDesignedState() { diff --git a/modules/ticket/front/tracking/edit/index.spec.js b/modules/ticket/front/tracking/edit/index.spec.js index 9d9aa7983e..de3b85ca3f 100644 --- a/modules/ticket/front/tracking/edit/index.spec.js +++ b/modules/ticket/front/tracking/edit/index.spec.js @@ -29,11 +29,11 @@ describe('Ticket', () => { }); }); - describe('workerFk setter', () => { - it('should set params.workerFk', () => { - controller.workerFk = 1; + describe('userFk setter', () => { + it('should set params.userFk', () => { + controller.userFk = 1; - expect(controller.params.workerFk).toEqual(1); + expect(controller.params.userFk).toEqual(1); }); }); diff --git a/modules/ticket/front/tracking/index/index.html b/modules/ticket/front/tracking/index/index.html index 12c4778c99..539f5e538d 100644 --- a/modules/ticket/front/tracking/index/index.html +++ b/modules/ticket/front/tracking/index/index.html @@ -22,10 +22,10 @@ {{::tracking.state.name}} - - {{::tracking.worker.user.name || 'System' | translate}} + + {{::tracking.user.name || 'System' | translate}} {{::tracking.created | date:'dd/MM/yyyy HH:mm'}} diff --git a/modules/ticket/front/tracking/index/index.js b/modules/ticket/front/tracking/index/index.js index ff3dc881b1..c697412b5a 100644 --- a/modules/ticket/front/tracking/index/index.js +++ b/modules/ticket/front/tracking/index/index.js @@ -9,7 +9,13 @@ class Controller extends Section { { relation: 'user', scope: { - fields: ['name'] + fields: ['id', 'name'], + include: { + relation: 'worker', + scope: { + fields: ['id'] + } + } } }, { relation: 'state', diff --git a/modules/travel/back/models/travel-thermograph.json b/modules/travel/back/models/travel-thermograph.json index 08eec2847c..cc8e60aaf7 100644 --- a/modules/travel/back/models/travel-thermograph.json +++ b/modules/travel/back/models/travel-thermograph.json @@ -1,6 +1,9 @@ { "name": "TravelThermograph", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "travelThermograph" diff --git a/modules/travel/back/models/travel.json b/modules/travel/back/models/travel.json index 95d4581214..701894a766 100644 --- a/modules/travel/back/models/travel.json +++ b/modules/travel/back/models/travel.json @@ -1,6 +1,9 @@ { "name": "Travel", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "travel" diff --git a/modules/worker/back/methods/calendar/absences.js b/modules/worker/back/methods/calendar/absences.js index 8420ed7705..8a5d189871 100644 --- a/modules/worker/back/methods/calendar/absences.js +++ b/modules/worker/back/methods/calendar/absences.js @@ -39,6 +39,7 @@ module.exports = Self => { started.setFullYear(year); started.setMonth(0); started.setDate(1); + started.setHours(0, 0, 0, 0); const ended = Date.vnNew(); ended.setFullYear(year); diff --git a/modules/worker/back/methods/worker-time-control/addTimeEntry.js b/modules/worker/back/methods/worker-time-control/addTimeEntry.js index cc652fb90b..5dbac51ca2 100644 --- a/modules/worker/back/methods/worker-time-control/addTimeEntry.js +++ b/modules/worker/back/methods/worker-time-control/addTimeEntry.js @@ -43,16 +43,9 @@ module.exports = Self => { const isTeamBoss = await models.ACL.checkAccessAcl(ctx, 'Worker', 'isTeamBoss', 'WRITE'); const isHimself = userId == workerId; - if (!isSubordinate || (isSubordinate && isHimself && !isTeamBoss)) + if (!isSubordinate || (isHimself && !isTeamBoss)) throw new UserError(`You don't have enough privileges`); - query = `CALL vn.workerTimeControl_clockIn(?,?,?)`; - const [response] = await Self.rawSql(query, [workerId, args.timed, args.direction], myOptions); - if (response[0] && response[0].error) - throw new UserError(response[0].error); - - await models.WorkerTimeControl.resendWeeklyHourEmail(ctx, workerId, args.timed, myOptions); - - return response; + return Self.clockIn(workerId, args.timed, args.direction, myOptions); }; }; diff --git a/modules/worker/back/methods/worker-time-control/clockIn.js b/modules/worker/back/methods/worker-time-control/clockIn.js new file mode 100644 index 0000000000..44e0c547ae --- /dev/null +++ b/modules/worker/back/methods/worker-time-control/clockIn.js @@ -0,0 +1,45 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethod('clockIn', { + description: 'Check if the employee can clock in', + accessType: 'WRITE', + accepts: [ + { + arg: 'workerFk', + type: 'number', + required: true, + }, + { + arg: 'timed', + type: 'date' + }, + { + arg: 'direction', + type: 'string' + }, + + ], + http: { + path: `/clockIn`, + verb: 'POST' + }, + returns: { + type: 'Object', + root: true + } + }); + + Self.clockIn = async(workerFk, timed, direction, options) => { + const myOptions = {}; + if (typeof options == 'object') + Object.assign(myOptions, options); + + const query = 'CALL vn.workerTimeControl_clockIn(?, ?, ?)'; + const [[response]] = await Self.rawSql(query, [workerFk, timed, direction], myOptions); + if (response && response.error) + throw new UserError(response.error); + + return response; + }; +}; diff --git a/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js b/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js index 8f95415964..3e1d731bb8 100644 --- a/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js +++ b/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js @@ -3,7 +3,7 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethodCtx('deleteTimeEntry', { description: 'Deletes a manual time entry for a worker if the user role is above the worker', - accessType: 'READ', + accessType: 'WRITE', accepts: [{ arg: 'id', type: 'number', diff --git a/modules/worker/back/methods/worker-time-control/getClockIn.js b/modules/worker/back/methods/worker-time-control/getClockIn.js new file mode 100644 index 0000000000..458cadafb7 --- /dev/null +++ b/modules/worker/back/methods/worker-time-control/getClockIn.js @@ -0,0 +1,32 @@ +module.exports = Self => { + Self.remoteMethod('getClockIn', { + description: 'Shows the clockings for each day, in columns per day', + accessType: 'READ', + accepts: [ + { + arg: 'workerFk', + type: 'number', + required: true, + }, + + ], + http: { + path: `/getClockIn`, + verb: 'GET' + }, + returns: { + type: ['Object'], + root: true + }, + }); + + Self.getClockIn = async(workerFk, options) => { + const myOptions = {}; + if (typeof options == 'object') + Object.assign(myOptions, options); + + const query = `CALL vn.workerTimeControl_getClockIn(?, ?)`; + const [result] = await Self.rawSql(query, [workerFk, Date.vnNew()], myOptions); + return result; + }; +}; diff --git a/modules/worker/back/methods/worker-time-control/login.js b/modules/worker/back/methods/worker-time-control/login.js new file mode 100644 index 0000000000..9aa4bd1452 --- /dev/null +++ b/modules/worker/back/methods/worker-time-control/login.js @@ -0,0 +1,35 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('login', { + description: 'Consult the user\'s information and the buttons that must be activated after logging in', + accessType: 'READ', + accepts: [ + { + arg: 'pin', + type: 'string', + required: true + }, + ], + returns: { + type: 'Object', + root: true + }, + http: { + path: `/login`, + verb: 'POST' + } + }); + + Self.login = async(ctx, pin, options) => { + const myOptions = {}; + const $t = ctx.req.__; + if (typeof options == 'object') + Object.assign(myOptions, options); + + const query = `CALL vn.workerTimeControl_login(?)`; + const [[user]] = await Self.rawSql(query, [pin], myOptions); + if (!user) throw new UserError($t('Incorrect pin')); + return user; + }; +}; diff --git a/modules/worker/back/methods/worker-time-control/resendWeeklyHourEmail.js b/modules/worker/back/methods/worker-time-control/resendWeeklyHourEmail.js index 2452a29f9c..8964584551 100644 --- a/modules/worker/back/methods/worker-time-control/resendWeeklyHourEmail.js +++ b/modules/worker/back/methods/worker-time-control/resendWeeklyHourEmail.js @@ -1,6 +1,6 @@ module.exports = Self => { Self.remoteMethodCtx('resendWeeklyHourEmail', { - description: 'Adds a new hour registry', + description: 'Send the records for the week of the date provided', accessType: 'WRITE', accepts: [{ arg: 'id', diff --git a/modules/worker/back/methods/worker-time-control/specs/clockIn.spec.js b/modules/worker/back/methods/worker-time-control/specs/clockIn.spec.js new file mode 100644 index 0000000000..9cd3ed1c06 --- /dev/null +++ b/modules/worker/back/methods/worker-time-control/specs/clockIn.spec.js @@ -0,0 +1,581 @@ +const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); + +describe('workerTimeControl clockIn()', () => { + const workerId = 9; + const salesBossId = 19; + const hankPymId = 1107; + const jessicaJonesId = 1110; + const HHRRId = 37; + const teamBossId = 13; + const monday = 1; + const tuesday = 2; + const thursday = 4; + const friday = 5; + const sunday = 7; + const inTime = '2001-01-01T00:00:00.000Z'; + const activeCtx = { + accessToken: {userId: 50}, + }; + const ctx = {req: activeCtx}; + + beforeAll(() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + }); + + it('should correctly clock in', async() => { + const tx = await models.WorkerTimeControl.beginTransaction({}); + + try { + const options = {transaction: tx}; + await models.WorkerTimeControl.clockIn(workerId, inTime, 'in', options); + const isClockIn = await models.WorkerTimeControl.findOne({ + where: { + userFk: workerId + } + }, options); + + expect(isClockIn).toBeDefined(); + expect(isClockIn.direction).toBe('in'); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + describe('as Role errors', () => { + it('should add if the current user is team boss and the target user is himself', async() => { + activeCtx.accessToken.userId = teamBossId; + const workerId = teamBossId; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + try { + const options = {transaction: tx}; + + const todayAtOne = Date.vnNew(); + todayAtOne.setHours(1, 0, 0, 0); + + ctx.args = {timed: todayAtOne, direction: 'in'}; + const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + expect(createdTimeEntry.id).toBeDefined(); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should delete the created time entry for the team boss as himself', async() => { + activeCtx.accessToken.userId = teamBossId; + const workerId = teamBossId; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + try { + const options = {transaction: tx}; + + const todayAtOne = Date.vnNew(); + todayAtOne.setHours(1, 0, 0, 0); + + ctx.args = {timed: todayAtOne, direction: 'in'}; + const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + expect(createdTimeEntry.id).toBeDefined(); + + await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options); + + const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options); + + expect(deletedTimeEntry).toBeNull(); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should edit the created time entry for the team boss as HHRR', async() => { + activeCtx.accessToken.userId = HHRRId; + const workerId = teamBossId; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + try { + const options = {transaction: tx}; + + const todayAtOne = Date.vnNew(); + todayAtOne.setHours(1, 0, 0, 0); + + ctx.args = {timed: todayAtOne, direction: 'in'}; + const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + expect(createdTimeEntry.id).toBeDefined(); + + ctx.args = {direction: 'out'}; + const updatedTimeEntry = await models.WorkerTimeControl.updateTimeEntry( + ctx, createdTimeEntry.id, options + ); + + expect(updatedTimeEntry.direction).toEqual('out'); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + }); + + describe('as saleBoss editor', () => { + let workerId; + beforeEach(() => { + activeCtx.accessToken.userId = salesBossId; + workerId = hankPymId; + }); + + it('should fail to add a time entry if the target user has an absence that day', async() => { + const date = Date.vnNew(); + date.setHours(8, 0, 0); + date.setDate(date.getDate() - 16); + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + try { + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`No está permitido trabajar`); + }); + + it('should fail to add a time entry for a worker without an existing contract', async() => { + const date = Date.vnNew(); + date.setFullYear(date.getFullYear() - 2); + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + try { + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`No hay un contrato en vigor`); + }); + + it('should fail to add a time entry for a worker without an existing contract and exceeding time', async() => { + let date = Date.vnNew(); + date.setDate(date.getDate() - 2); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + date.setHours(0, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date.setHours(20, 0, 1); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Superado el tiempo máximo entre entrada y salida`); + }); + + describe('direction errors', () => { + let date = Date.vnNew(); + date.setDate(date.getDate() - 1); + let error; + it('should throw an error when trying "in" direction twice', async() => { + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date.setHours(10, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Dirección incorrecta`); + }); + + it('should throw an error when trying "in" direction after insert "in" and "middle"', async() => { + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + date.setHours(9, 0, 0); + ctx.args = {timed: date, direction: 'middle'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date.setHours(10, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Dirección incorrecta`); + }); + + it('Should throw an error when trying "out" before closing a "middle" couple', async() => { + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(9, 0, 0); + ctx.args = {timed: date, direction: 'middle'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date.setHours(10, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Dirección incorrecta`); + }); + + it('should throw an error when trying "middle" after "out"', async() => { + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(9, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date.setHours(10, 0, 0); + ctx.args = {timed: date, direction: 'middle'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Dirección incorrecta`); + }); + + it('should throw an error when trying "out" direction twice', async() => { + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(9, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date.setHours(10, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Dirección incorrecta`); + }); + }); + + describe('12h rest', () => { + activeCtx.accessToken.userId = salesBossId; + const workerId = hankPymId; + it('should throw an error when the 12h rest is not fulfilled yet', async() => { + let date = Date.vnNew(); + date.setDate(date.getDate() - 2); + date = weekDay(date, monday); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(16, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date = weekDay(date, tuesday); + date.setHours(4, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Descanso diario`); + }); + + it('should not fail as the 12h rest is fulfilled', async() => { + let date = Date.vnNew(); + date.setDate(date.getDate() - 2); + date = weekDay(date, monday); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(16, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date = weekDay(date, tuesday); + date.setHours(4, 1, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error).not.toBeDefined; + }); + }); + + describe('for 3500kg drivers with enforced 9h rest', () => { + activeCtx.accessToken.userId = salesBossId; + const workerId = jessicaJonesId; + it('should throw an error when the 9h enforced rest is not fulfilled', async() => { + let date = Date.vnNew(); + date.setDate(date.getDate() - 2); + date = weekDay(date, monday); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(16, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date = weekDay(date, tuesday); + date.setHours(1, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Descanso diario`); + }); + + it('should not fail when the 9h enforced rest is fulfilled', async() => { + let date = Date.vnNew(); + date.setDate(date.getDate() - 2); + date = weekDay(date, monday); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + date.setHours(8, 0, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + date.setHours(16, 0, 0); + ctx.args = {timed: date, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + try { + date = weekDay(date, tuesday); + date.setHours(1, 1, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error).not.toBeDefined; + }); + }); + + describe('for 72h weekly rest', () => { + + it('should throw an error when work 11 consecutive days', async() => { + let date = Date.vnNew(); + date.setMonth(date.getMonth() - 1); + date.setDate(1); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + await populateWeek(date, monday, sunday, ctx, workerId, options); + date = nextWeek(date); + await populateWeek(date, monday, thursday, ctx, workerId, options); + try { + date = weekDay(date, friday); + date.setHours(10, 0, 1); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Descanso semanal`); + }); + + it('should throw an error when the 72h weekly rest is not fulfilled', async() => { + + let date = Date.vnNew(); + date.setMonth(date.getMonth() - 1); + date.setDate(1); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + await populateWeek(date, monday, sunday, ctx, workerId, options); + date = nextWeek(date); + await populateWeek(date, monday, thursday, ctx, workerId, options); + + try { + date = weekDay(date, sunday); + date.setHours(17, 59, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error.message).toBe(`Descanso semanal`); + }); + + it('should throw an error when the 72h weekly rest is fulfilled', async() => { + + let date = Date.vnNew(); + date.setMonth(date.getMonth() - 1); + date.setDate(1); + let error; + + const tx = await models.WorkerTimeControl.beginTransaction({}); + const options = {transaction: tx}; + + await populateWeek(date, monday, sunday, ctx, workerId, options); + date = nextWeek(date); + await populateWeek(date, monday, thursday, ctx, workerId, options); + + try { + date = weekDay(date, sunday); + date.setHours(18, 00, 0); + ctx.args = {timed: date, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error).not.toBeDefined; + }); + }); + }); +}); + +function weekDay(date, dayToSet) { + const currentDay = date.getDay(); + const distance = dayToSet - currentDay; + + date.setDate(date.getDate() + distance); + return date; +} + +function nextWeek(date) { + const sunday = 7; + const currentDay = date.getDay(); + let newDate = date; + if (currentDay != 0) + newDate = weekDay(date, sunday); + + newDate.setDate(newDate.getDate() + 1); + return newDate; +} + +async function populateWeek(date, dayStart, dayEnd, ctx, workerId, options) { + const dateStart = new Date(weekDay(date, dayStart)); + const dateEnd = new Date(dateStart); + dateEnd.setDate(dateStart.getDate() + dayEnd); + + for (let i = dayStart; i <= dayEnd; i++) { + dateStart.setHours(10, 0, 0); + ctx.args = {timed: dateStart, direction: 'in'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + dateStart.setHours(18, 0, 0); + ctx.args = {timed: dateStart, direction: 'out'}; + await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + dateStart.setDate(dateStart.getDate() + 1); + } +} diff --git a/modules/worker/back/methods/worker-time-control/specs/getClockIn.spec.js b/modules/worker/back/methods/worker-time-control/specs/getClockIn.spec.js new file mode 100644 index 0000000000..d75ffac704 --- /dev/null +++ b/modules/worker/back/methods/worker-time-control/specs/getClockIn.spec.js @@ -0,0 +1,16 @@ +const models = require('vn-loopback/server/server').models; + +describe('workerTimeControl getClockIn()', () => { + it('should correctly get the timetable of a worker', async() => { + const response = await models.WorkerTimeControl.getClockIn(1106, {}); + + expect(response.length).toEqual(4); + const [inHrs, middleOutHrs, middleInHrs, outHrs] = response; + + expect(inHrs['0daysAgo']).toEqual('07:00'); + expect(middleOutHrs['0daysAgo']).toEqual('10:00'); + expect(middleInHrs['0daysAgo']).toEqual('10:20'); + expect(outHrs['0daysAgo']).toEqual('14:50'); + }); +}); + diff --git a/modules/worker/back/methods/worker-time-control/specs/login.spec.js b/modules/worker/back/methods/worker-time-control/specs/login.spec.js new file mode 100644 index 0000000000..d9f2dbb393 --- /dev/null +++ b/modules/worker/back/methods/worker-time-control/specs/login.spec.js @@ -0,0 +1,34 @@ +const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); +const UserError = require('vn-loopback/util/user-error'); + +describe('workerTimeControl login()', () => { + let ctx; + beforeAll(async() => { + ctx = { + accessToken: {userId: 9}, + req: { + headers: {origin: 'http://localhost'}, + __: key => key + } + }; + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: ctx + }); + }); + + it('should correctly login', async() => { + const response = await models.WorkerTimeControl.login(ctx, 9); + + expect(response.name).toBe('developer'); + }); + + it('should throw UserError if pin is not provided', async() => { + try { + await models.WorkerTimeControl.login(ctx); + } catch (error) { + expect(error).toBeInstanceOf(UserError); + expect(error.message).toBe('Incorrect pin'); + } + }); +}); diff --git a/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js b/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js index 42ec6290a2..92c01792f5 100644 --- a/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js +++ b/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js @@ -3,18 +3,10 @@ const models = require('vn-loopback/server/server').models; const LoopBackContext = require('loopback-context'); describe('workerTimeControl add/delete timeEntry()', () => { - const HHRRId = 37; - const teamBossId = 13; const employeeId = 1; - const salesPersonId = 1106; const salesBossId = 19; const hankPymId = 1107; - const jessicaJonesId = 1110; const monday = 1; - const tuesday = 2; - const thursday = 4; - const friday = 5; - const sunday = 7; const activeCtx = { accessToken: {userId: 50}, }; @@ -61,560 +53,11 @@ describe('workerTimeControl add/delete timeEntry()', () => { expect(error.statusCode).toBe(400); expect(error.message).toBe(`You don't have enough privileges`); }); - - it('should add if the current user is team boss and the target user is himself', async() => { - activeCtx.accessToken.userId = teamBossId; - const workerId = teamBossId; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - try { - const options = {transaction: tx}; - - const todayAtOne = Date.vnNew(); - todayAtOne.setHours(1, 0, 0, 0); - - ctx.args = {timed: todayAtOne, direction: 'in'}; - const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - expect(createdTimeEntry.id).toBeDefined(); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); - - it('should try but fail to delete his own time entry', async() => { - activeCtx.accessToken.userId = salesBossId; - const workerId = salesBossId; - - let error; - const tx = await models.WorkerTimeControl.beginTransaction({}); - try { - const options = {transaction: tx}; - - const todayAtOne = Date.vnNew(); - todayAtOne.setHours(1, 0, 0, 0); - - ctx.args = {timed: todayAtOne, direction: 'in'}; - const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - activeCtx.accessToken.userId = salesPersonId; - await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options); - - await tx.rollback(); - } catch (e) { - error = e; - await tx.rollback(); - } - - expect(error).toBeDefined(); - expect(error.statusCode).toBe(400); - expect(error.message).toBe(`You don't have enough privileges`); - }); - - it('should delete the created time entry for the team boss as himself', async() => { - activeCtx.accessToken.userId = teamBossId; - const workerId = teamBossId; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - try { - const options = {transaction: tx}; - - const todayAtOne = Date.vnNew(); - todayAtOne.setHours(1, 0, 0, 0); - - ctx.args = {timed: todayAtOne, direction: 'in'}; - const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - expect(createdTimeEntry.id).toBeDefined(); - - await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options); - - const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options); - - expect(deletedTimeEntry).toBeNull(); - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); - - it('should delete the created time entry for the team boss as HHRR', async() => { - activeCtx.accessToken.userId = HHRRId; - const workerId = teamBossId; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - try { - const options = {transaction: tx}; - - const todayAtOne = Date.vnNew(); - todayAtOne.setHours(1, 0, 0, 0); - - ctx.args = {timed: todayAtOne, direction: 'in'}; - const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - expect(createdTimeEntry.id).toBeDefined(); - - await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options); - - const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options); - - expect(deletedTimeEntry).toBeNull(); - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); - - it('should edit the created time entry for the team boss as HHRR', async() => { - activeCtx.accessToken.userId = HHRRId; - const workerId = teamBossId; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - try { - const options = {transaction: tx}; - - const todayAtOne = Date.vnNew(); - todayAtOne.setHours(1, 0, 0, 0); - - ctx.args = {timed: todayAtOne, direction: 'in'}; - const [createdTimeEntry] = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - expect(createdTimeEntry.id).toBeDefined(); - - ctx.args = {direction: 'out'}; - const updatedTimeEntry = await models.WorkerTimeControl.updateTimeEntry(ctx, createdTimeEntry.id, options); - - expect(updatedTimeEntry.direction).toEqual('out'); - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); }); describe('WorkerTimeControl_clockIn calls', () => { - let workerId; - beforeEach(() => { - activeCtx.accessToken.userId = salesBossId; - workerId = hankPymId; - }); - it('should fail to add a time entry if the target user has an absence that day', async() => { - const date = Date.vnNew(); - date.setHours(8, 0, 0); - date.setDate(date.getDate() - 16); - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - try { - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); + beforeEach(() => activeCtx.accessToken.userId = salesBossId); - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`No está permitido trabajar`); - }); - - it('should fail to add a time entry for a worker without an existing contract', async() => { - const date = Date.vnNew(); - date.setFullYear(date.getFullYear() - 2); - - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - try { - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`No hay un contrato en vigor`); - }); - - it('should fail to add a time entry for a worker without an existing contract', async() => { - let date = Date.vnNew(); - date.setDate(date.getDate() - 2); - let error; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - date.setHours(0, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - try { - date.setHours(20,0, 1); - ctx.args = {timed: date, direction: 'out'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`Superado el tiempo máximo entre entrada y salida`); - }); - - describe('direction errors', () => { - let date = Date.vnNew(); - date.setDate(date.getDate() - 1); - let error; - it('should throw an error when trying "in" direction twice', async() => { - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - date.setHours(8, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - try { - date.setHours(10, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`Dirección incorrecta`); - }); - - it('should throw an error when trying "in" direction after insert "in" and "middle"', async() => { - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - date.setHours(8, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - date.setHours(9, 0, 0); - ctx.args = {timed: date, direction: 'middle'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - try { - date.setHours(10, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`Dirección incorrecta`); - }); - - it('Should throw an error when trying "out" before closing a "middle" couple', async() => { - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - date.setHours(8, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - date.setHours(9, 0, 0); - ctx.args = {timed: date, direction: 'middle'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - try { - date.setHours(10, 0, 0); - ctx.args = {timed: date, direction: 'out'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`Dirección incorrecta`); - }); - - it('should throw an error when trying "middle" after "out"', async() => { - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - date.setHours(8, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - date.setHours(9, 0, 0); - ctx.args = {timed: date, direction: 'out'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - try { - date.setHours(10, 0, 0); - ctx.args = {timed: date, direction: 'middle'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`Dirección incorrecta`); - }); - - it('should throw an error when trying "out" direction twice', async() => { - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - date.setHours(8, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - date.setHours(9, 0, 0); - ctx.args = {timed: date, direction: 'out'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - try { - date.setHours(10, 0, 0); - ctx.args = {timed: date, direction: 'out'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`Dirección incorrecta`); - }); - }); - - describe('12h rest', () => { - activeCtx.accessToken.userId = salesBossId; - const workerId = hankPymId; - it('should throw an error when the 12h rest is not fulfilled yet', async() => { - - let date = Date.vnNew(); - date.setDate(date.getDate() - 2); - date = weekDay(date, monday); - let error; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - date.setHours(8, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - date.setHours(16, 0, 0); - ctx.args = {timed: date, direction: 'out'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - try { - date = weekDay(date, tuesday); - date.setHours(4, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`Descanso diario`); - }); - - it('should not fail as the 12h rest is fulfilled', async() => { - let date = Date.vnNew(); - date.setDate(date.getDate() - 2); - date = weekDay(date, monday); - let error; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - date.setHours(8, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - date.setHours(16, 0, 0); - ctx.args = {timed: date, direction: 'out'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - try { - date = weekDay(date, tuesday); - date.setHours(4, 1, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error).not.toBeDefined; - }); - }); - - describe('for 3500kg drivers with enforced 9h rest', () => { - activeCtx.accessToken.userId = salesBossId; - const workerId = jessicaJonesId; - it('should throw an error when the 9h enforced rest is not fulfilled', async() => { - - let date = Date.vnNew(); - date.setDate(date.getDate() - 2); - date = weekDay(date, monday); - let error; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - date.setHours(8, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - date.setHours(16, 0, 0); - ctx.args = {timed: date, direction: 'out'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - try { - date = weekDay(date, tuesday); - date.setHours(1, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`Descanso diario`); - }); - - it('should not fail when the 9h enforced rest is fulfilled', async() => { - - let date = Date.vnNew(); - date.setDate(date.getDate() - 2); - date = weekDay(date, monday); - let error; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - date.setHours(8, 0, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - date.setHours(16, 0, 0); - ctx.args = {timed: date, direction: 'out'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - try { - date = weekDay(date, tuesday); - date.setHours(1, 1, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error).not.toBeDefined; - }); - }); - - describe('for 72h weekly rest', () => { - - it('should throw an error when work 11 consecutive days', async() => { - let date = Date.vnNew(); - date.setMonth(date.getMonth() - 1); - date.setDate(1); - let error; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - await populateWeek(date, monday, sunday, ctx, workerId, options); - date = nextWeek(date); - await populateWeek(date, monday, thursday, ctx, workerId, options); - try { - date = weekDay(date, friday); - date.setHours(10, 0, 1); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`Descanso semanal`); - }); - - it('should throw an error when the 72h weekly rest is not fulfilled', async() => { - - let date = Date.vnNew(); - date.setMonth(date.getMonth() - 1); - date.setDate(1); - let error; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - await populateWeek(date, monday, sunday, ctx, workerId, options); - date = nextWeek(date); - await populateWeek(date, monday, thursday, ctx, workerId, options); - - try { - date = weekDay(date, sunday); - date.setHours(17, 59, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error.message).toBe(`Descanso semanal`); - }); - - it('should throw an error when the 72h weekly rest is fulfilled', async() => { - - let date = Date.vnNew(); - date.setMonth(date.getMonth() - 1); - date.setDate(1); - let error; - - const tx = await models.WorkerTimeControl.beginTransaction({}); - const options = {transaction: tx}; - - await populateWeek(date, monday, sunday, ctx, workerId, options); - date = nextWeek(date); - await populateWeek(date, monday, thursday, ctx, workerId, options); - - try { - date = weekDay(date, sunday); - date.setHours(18, 00, 0); - ctx.args = {timed: date, direction: 'in'}; - await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options); - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error).not.toBeDefined; - }); - }); - describe('WorkerTimeControl_calculate calls', () => { let dated = Date.vnNew(); dated.setDate(dated.getDate() - 7); @@ -836,25 +279,6 @@ function weekDay(date, dayToSet) { return date; } -function nextWeek(date) { - const sunday = 7; - const currentDay = date.getDay(); - let newDate = date; - if (currentDay != 0) - newDate = weekDay(date, sunday); - - newDate.setDate(newDate.getDate() + 1); - return newDate; -} - -function lastWeek(date) { - const monday = 1; - newDate = weekDay(date, monday); - - newDate.setDate(newDate.getDate() - 1); - return newDate; -} - async function populateWeek(date, dayStart, dayEnd, ctx, workerId, options) { const dateStart = new Date(weekDay(date, dayStart)); const dateEnd = new Date(dateStart); diff --git a/modules/worker/back/methods/worker-time-control/updateTimeEntry.js b/modules/worker/back/methods/worker-time-control/updateTimeEntry.js index 8231be7bbf..e9d35f8803 100644 --- a/modules/worker/back/methods/worker-time-control/updateTimeEntry.js +++ b/modules/worker/back/methods/worker-time-control/updateTimeEntry.js @@ -3,7 +3,7 @@ const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethodCtx('updateTimeEntry', { description: 'Updates a time entry for a worker if the user role is above the worker', - accessType: 'READ', + accessType: 'WRITE', accepts: [{ arg: 'id', type: 'number', diff --git a/modules/worker/back/methods/worker/specs/activeWithInheritedRole.spec.js b/modules/worker/back/methods/worker/specs/activeWithInheritedRole.spec.js index da54f6adbe..580e073513 100644 --- a/modules/worker/back/methods/worker/specs/activeWithInheritedRole.spec.js +++ b/modules/worker/back/methods/worker/specs/activeWithInheritedRole.spec.js @@ -3,7 +3,7 @@ const app = require('vn-loopback/server/server'); describe('Worker activeWithInheritedRole', () => { let allRolesCount; beforeAll(async() => { - allRolesCount = await app.models.Role.count(); + allRolesCount = await app.models.VnRole.count(); }); it('should return the workers with an inherited role of salesPerson', async() => { diff --git a/modules/worker/back/models/device-production-user.json b/modules/worker/back/models/device-production-user.json index 3eeaae137f..35a90fb50d 100644 --- a/modules/worker/back/models/device-production-user.json +++ b/modules/worker/back/models/device-production-user.json @@ -1,6 +1,9 @@ { "name": "DeviceProductionUser", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "log": { "model": "DeviceProductionLog", "relation": "deviceProduction" diff --git a/modules/worker/back/models/device-production.json b/modules/worker/back/models/device-production.json index 35787cccc2..f6e5105adc 100644 --- a/modules/worker/back/models/device-production.json +++ b/modules/worker/back/models/device-production.json @@ -1,6 +1,9 @@ { "name": "DeviceProduction", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "log": { "model": "DeviceProductionLog" }, diff --git a/modules/worker/back/models/worker-dms.json b/modules/worker/back/models/worker-dms.json index e9a9f17730..a5c0f30b2d 100644 --- a/modules/worker/back/models/worker-dms.json +++ b/modules/worker/back/models/worker-dms.json @@ -1,6 +1,9 @@ { "name": "WorkerDms", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "workerDocument" diff --git a/modules/worker/back/models/worker-time-control.js b/modules/worker/back/models/worker-time-control.js index d5da680cf8..1457c7a462 100644 --- a/modules/worker/back/models/worker-time-control.js +++ b/modules/worker/back/models/worker-time-control.js @@ -10,6 +10,9 @@ module.exports = Self => { require('../methods/worker-time-control/weeklyHourRecordEmail')(Self); require('../methods/worker-time-control/getMailStates')(Self); require('../methods/worker-time-control/resendWeeklyHourEmail')(Self); + require('../methods/worker-time-control/login')(Self); + require('../methods/worker-time-control/getClockIn')(Self); + require('../methods/worker-time-control/clockIn')(Self); Self.rewriteDbError(function(err) { if (err.code === 'ER_DUP_ENTRY') diff --git a/modules/worker/back/models/worker.js b/modules/worker/back/models/worker.js index 985d83e9f7..b475bf26e2 100644 --- a/modules/worker/back/models/worker.js +++ b/modules/worker/back/models/worker.js @@ -1,4 +1,5 @@ module.exports = Self => { + const validateTin = require('vn-loopback/util/validateTin'); require('../methods/worker/filter')(Self); require('../methods/worker/mySubordinates')(Self); require('../methods/worker/isSubordinate')(Self); @@ -23,4 +24,21 @@ module.exports = Self => { Self.validatesUniquenessOf('locker', { message: 'This locker has already been assigned' }); + + Self.validateAsync('fi', tinIsValid, { + message: 'Invalid TIN' + }); + + async function tinIsValid(err, done) { + const filter = { + fields: ['code'], + where: {id: this.countryFk} + }; + const country = await Self.app.models.Country.findOne(filter); + const code = country ? country.code.toLowerCase() : null; + + if (!this.fi || !validateTin(this.fi, code)) + err(); + done(); + } }; diff --git a/modules/worker/back/models/worker.json b/modules/worker/back/models/worker.json index 1a777fffeb..ed430f133f 100644 --- a/modules/worker/back/models/worker.json +++ b/modules/worker/back/models/worker.json @@ -1,7 +1,10 @@ { "name": "Worker", "description": "Company employees", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "worker" diff --git a/modules/zone/back/methods/zone/deleteZone.js b/modules/zone/back/methods/zone/deleteZone.js index 38e724cd3c..a75302703d 100644 --- a/modules/zone/back/methods/zone/deleteZone.js +++ b/modules/zone/back/methods/zone/deleteZone.js @@ -61,7 +61,7 @@ module.exports = Self => { for (ticket of ticketList) { if (ticket.ticketState().alertLevel == 0) { - promises.push(models.TicketTracking.create({ + promises.push(models.Ticket.state(ctx, { ticketFk: ticket.id, stateFk: fixingState.id, userFk: worker.id diff --git a/modules/zone/back/models/zone-event.json b/modules/zone/back/models/zone-event.json index e477dad6aa..366bdec9d9 100644 --- a/modules/zone/back/models/zone-event.json +++ b/modules/zone/back/models/zone-event.json @@ -1,6 +1,9 @@ { "name": "ZoneEvent", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "zoneEvent" diff --git a/modules/zone/back/models/zone-exclusion.json b/modules/zone/back/models/zone-exclusion.json index 00c9145cda..6e91a0a013 100644 --- a/modules/zone/back/models/zone-exclusion.json +++ b/modules/zone/back/models/zone-exclusion.json @@ -1,6 +1,9 @@ { "name": "ZoneExclusion", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "zoneExclusion" diff --git a/modules/zone/back/models/zone-included.json b/modules/zone/back/models/zone-included.json index deba73f34e..a34e51091d 100644 --- a/modules/zone/back/models/zone-included.json +++ b/modules/zone/back/models/zone-included.json @@ -1,6 +1,9 @@ { "name": "ZoneIncluded", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "zoneIncluded" diff --git a/modules/zone/back/models/zone-warehouse.json b/modules/zone/back/models/zone-warehouse.json index b222e95e72..c2cc989f0a 100644 --- a/modules/zone/back/models/zone-warehouse.json +++ b/modules/zone/back/models/zone-warehouse.json @@ -1,6 +1,9 @@ { "name": "ZoneWarehouse", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "zoneWarehouse" diff --git a/modules/zone/back/models/zone.json b/modules/zone/back/models/zone.json index c86da3d3e9..cf73710530 100644 --- a/modules/zone/back/models/zone.json +++ b/modules/zone/back/models/zone.json @@ -1,6 +1,9 @@ { "name": "Zone", - "base": "Loggable", + "base": "VnModel", + "mixins": { + "Loggable": true + }, "options": { "mysql": { "table": "zone" diff --git a/package-lock.json b/package-lock.json index 012fb50e75..4a8aa4fb88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "salix-back", - "version": "24.02.01", + "version": "24.06.01", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "salix-back", - "version": "24.02.01", + "version": "24.06.01", "license": "GPL-3.0", "dependencies": { "axios": "^1.2.2", diff --git a/package.json b/package.json index ab3d99e19f..ba8006e8a2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-back", - "version": "24.02.01", + "version": "24.06.01", "author": "Verdnatura Levante SL", "description": "Salix backend", "license": "GPL-3.0", diff --git a/print/templates/email/letter-debtor-nd/letter-debtor-nd.html b/print/templates/email/letter-debtor-nd/letter-debtor-nd.html index 44a34a9b47..01bab79390 100644 --- a/print/templates/email/letter-debtor-nd/letter-debtor-nd.html +++ b/print/templates/email/letter-debtor-nd/letter-debtor-nd.html @@ -45,4 +45,4 @@ - \ No newline at end of file + diff --git a/print/templates/email/letter-debtor-nd/letter-debtor-nd.js b/print/templates/email/letter-debtor-nd/letter-debtor-nd.js index 54ed9fed65..41b6f1af86 100755 --- a/print/templates/email/letter-debtor-nd/letter-debtor-nd.js +++ b/print/templates/email/letter-debtor-nd/letter-debtor-nd.js @@ -1,14 +1,33 @@ const Component = require(`vn-print/core/component`); const emailBody = new Component('email-body'); const attachment = new Component('attachment'); +const db = require('../../../core/database'); module.exports = { name: 'letter-debtor-nd', async serverPrefetch() { - this.debtor = await this.fetchDebtor(this.id, this.companyId); - - if (!this.debtor) - throw new Error('Something went wrong'); + this.debtor = await db.findOne(` + SELECT sa.id, + sa.iban, + be.name bankName, + sa.countryFk, + c.countryFk + FROM supplierAccount sa + JOIN bankEntity be ON sa.bankEntityFk = be.id + LEFT JOIN company co ON co.supplierAccountFk = sa.id + JOIN client c ON c.countryFk = sa.countryFk + WHERE c.id = ?; + `, [this.id]); + if (!this.debtor) { + this.debtor = await db.findOne(` + SELECT sa.iban, + be.name bankName + FROM supplierAccount sa + JOIN bankEntity be ON sa.bankEntityFk = be.id + JOIN company co ON co.supplierAccountFk = sa.id + WHERE co.id = ?; + `, [this.companyId]); + } }, data() { return { diff --git a/print/templates/email/letter-debtor-nd/locale/en.yml b/print/templates/email/letter-debtor-nd/locale/en.yml new file mode 100644 index 0000000000..9fb3343396 --- /dev/null +++ b/print/templates/email/letter-debtor-nd/locale/en.yml @@ -0,0 +1,21 @@ +subject: Reminder of Outstanding Balance Notice +title: Reminder Notice +sections: + introduction: + title: Dear Customer + description: We are writing to you once again to inform you that your debt with our company remains unpaid, as you can verify in the attached statement. + terms: Since the agreed payment deadlines have significantly passed, there should be no further delay in settling the outstanding amount. + payMethod: + description: To do so, you have the following payment options + options: + - Online payment through our website. + - Deposit or transfer to the account number provided at the bottom of this letter, indicating your customer number. + legalAction: + description: If this payment reminder is not heeded, we will be compelled to initiate the necessary legal actions, which may include + options: + - Inclusion in negative credit and financial solvency records. + - Legal proceedings. + - Debt assignment to a debt collection agency. +contactPhone: For inquiries, you can reach us at 96 324 21 00. +conclusion: We look forward to hearing from you.
Thank you for your attention. +transferAccount: Bank Transfer Details diff --git a/print/templates/email/letter-debtor-nd/locale/fr.yml b/print/templates/email/letter-debtor-nd/locale/fr.yml new file mode 100644 index 0000000000..3ea3501d7c --- /dev/null +++ b/print/templates/email/letter-debtor-nd/locale/fr.yml @@ -0,0 +1,26 @@ +subject: Réitération de l'avis de solde débiteur +title: Avis réitéré +sections: + introduction: + title: Cher client + description: Nous vous écrivons à nouveau pour vous informer qu'il est toujours en attente + votre dette envers notre société, comme vous pouvez le voir dans le relevé ci-joint. + terms: Étant donné que les délais de paiement convenus sont largement dépassés, il n'est pas approprié + retard plus important dans le règlement du montant dû. + payMethod: + description: Pour cela, vous disposez des modes de paiement suivants + options: + - Paiement en ligne depuis notre site internet. + - Revenu ou virement sur le numéro de compte que nous détaillons en bas de ce courrier, + indiquant le numéro de client. + legalAction: + description: Si cette obligation de paiement n'est pas remplie, nous serons contraints de + d'engager les actions judiciaires qui se déroulent, parmi lesquelles + options: + - Inclusion dans les dossiers négatifs sur la solvabilité financière et le crédit. + - Réclamation judiciaire. + - Cession de créance à une société de gestion de recouvrement. +contactPhone: Pour toute demande, vous pouvez nous contacter au 96 + 324 21 00. +conclusion: En attente de vos nouvelles.
Merci pour ton attention. +transferAccount: Données pour virement bancaire \ No newline at end of file diff --git a/print/templates/email/letter-debtor-nd/locale/pt.yml b/print/templates/email/letter-debtor-nd/locale/pt.yml new file mode 100644 index 0000000000..f4acbd1d32 --- /dev/null +++ b/print/templates/email/letter-debtor-nd/locale/pt.yml @@ -0,0 +1,26 @@ +subject: Reiteração de aviso de saldo devedor +title: Aviso reiterado +sections: + introduction: + title: Estimado cliente + description: Estamos escrevendo para você novamente para informar que ainda está pendente + sua dívida para com nossa empresa, conforme demonstrativo anexo. + terms: Dado que os prazos de pagamento acordados são largamente excedidos, não é adequado + maior atraso na liquidação do valor devido. + payMethod: + description: Para isso você tem as seguintes formas de pagamento + options: + - Pagamento online em nosso site. + - Renda ou transferência para o número da conta que detalhamos no final desta carta, + indicando o número do cliente. + legalAction: + description: Se esta obrigação de pagamento não for cumprida, seremos obrigados a + para iniciar as ações legais que procedem, entre as quais estão + options: + - Inclusão em processos negativos de solvência financeira e de crédito. + - Reivindicação judicial. + - Cessão de dívida a uma empresa de gestão de cobranças. +contactPhone: Para consultas, você pode entrar em contato conosco em 96 + 324 21 00. +conclusion: Aguardando suas notícias.
Agradecimentos para sua atenção. +transferAccount: Dados para transferência bancária \ No newline at end of file diff --git a/print/templates/email/letter-debtor-nd/sql/client.sql b/print/templates/email/letter-debtor-nd/sql/client.sql index d5da5d0d58..013a3b7d6c 100644 --- a/print/templates/email/letter-debtor-nd/sql/client.sql +++ b/print/templates/email/letter-debtor-nd/sql/client.sql @@ -1,10 +1,9 @@ -SELECT - c.dueDay, - c.iban, - sa.iban, - be.name AS bankName -FROM client c - JOIN company AS cny - JOIN supplierAccount AS sa ON sa.id = cny.supplierAccountFk - JOIN bankEntity be ON be.id = sa.bankEntityFk -WHERE c.id = ? AND cny.id = ? \ No newline at end of file +SELECT c.dueDay, + sa.iban, + be.name bankName + FROM client c + JOIN supplierAccount sa ON sa.id = cny.supplierAccountFk + JOIN bankEntity be ON be.id = sa.bankEntityFk + JOIN company cny + WHERE c.id = ? + AND cny.id = ? diff --git a/print/templates/email/letter-debtor-st/letter-debtor-st.html b/print/templates/email/letter-debtor-st/letter-debtor-st.html index 36f300c986..6679567c08 100644 --- a/print/templates/email/letter-debtor-st/letter-debtor-st.html +++ b/print/templates/email/letter-debtor-st/letter-debtor-st.html @@ -28,4 +28,4 @@ - \ No newline at end of file + diff --git a/print/templates/email/letter-debtor-st/letter-debtor-st.js b/print/templates/email/letter-debtor-st/letter-debtor-st.js index e0a690f816..b168f969f0 100755 --- a/print/templates/email/letter-debtor-st/letter-debtor-st.js +++ b/print/templates/email/letter-debtor-st/letter-debtor-st.js @@ -1,14 +1,33 @@ const Component = require(`vn-print/core/component`); const emailBody = new Component('email-body'); const attachment = new Component('attachment'); +const db = require('../../../core/database'); module.exports = { name: 'letter-debtor-st', async serverPrefetch() { - this.debtor = await this.fetchDebtor(this.id, this.companyId); - - if (!this.debtor) - throw new Error('Something went wrong'); + this.debtor = await db.findOne(` + SELECT sa.id, + sa.iban, + be.name bankName, + sa.countryFk, + c.countryFk + FROM supplierAccount sa + JOIN bankEntity be ON sa.bankEntityFk = be.id + LEFT JOIN company co ON co.supplierAccountFk = sa.id + JOIN client c ON c.countryFk = sa.countryFk + WHERE c.id = ?; + `, [this.id]); + if (!this.debtor) { + this.debtor = await db.findOne(` + SELECT sa.iban, + be.name bankName + FROM supplierAccount sa + JOIN bankEntity be ON sa.bankEntityFk = be.id + JOIN company co ON co.supplierAccountFk = sa.id + WHERE co.id = ?; + `, [this.companyId]); + } }, data() { return { diff --git a/print/templates/email/letter-debtor-st/locale/en.yml b/print/templates/email/letter-debtor-st/locale/en.yml new file mode 100644 index 0000000000..42b3bb5043 --- /dev/null +++ b/print/templates/email/letter-debtor-st/locale/en.yml @@ -0,0 +1,11 @@ +subject: Initial Notice for Outstanding Balance +title: Initial Notice for Outstanding Balance +sections: + introduction: + title: Dear Customer + description: Through this letter, we would like to inform you that, according to our accounting records, your account has an outstanding balance that needs to be settled. +checkExtract: We kindly request you to verify that the attached statement corresponds to the information you have. Our administration department will be happy to clarify any questions you may have and provide any documents you may request. +checkValidData: If, upon reviewing the provided information, everything appears to be accurate, we kindly ask you to proceed with rectifying your situation. +payMethod: If you prefer not to visit our offices in person, you can make the payment through a bank transfer to the account listed at the bottom of this communication, indicating your customer number. Alternatively, you can make the payment online through our website. +conclusion: We sincerely appreciate your kind cooperation. +transferAccount: Bank Transfer Details diff --git a/print/templates/email/letter-debtor-st/sql/client.sql b/print/templates/email/letter-debtor-st/sql/client.sql index d5da5d0d58..013a3b7d6c 100644 --- a/print/templates/email/letter-debtor-st/sql/client.sql +++ b/print/templates/email/letter-debtor-st/sql/client.sql @@ -1,10 +1,9 @@ -SELECT - c.dueDay, - c.iban, - sa.iban, - be.name AS bankName -FROM client c - JOIN company AS cny - JOIN supplierAccount AS sa ON sa.id = cny.supplierAccountFk - JOIN bankEntity be ON be.id = sa.bankEntityFk -WHERE c.id = ? AND cny.id = ? \ No newline at end of file +SELECT c.dueDay, + sa.iban, + be.name bankName + FROM client c + JOIN supplierAccount sa ON sa.id = cny.supplierAccountFk + JOIN bankEntity be ON be.id = sa.bankEntityFk + JOIN company cny + WHERE c.id = ? + AND cny.id = ? diff --git a/print/templates/reports/collection-label/collection-label.html b/print/templates/reports/collection-label/collection-label.html index a699d4ac5c..a0bfcad0ee 100644 --- a/print/templates/reports/collection-label/collection-label.html +++ b/print/templates/reports/collection-label/collection-label.html @@ -15,7 +15,7 @@ {{labelData.workerCode || '---'}} - {{labelCount || labelData.labelCount || 0}} + {{labelData.labelCount || 0}} {{labelData.code == 'V' ? (labelData.size || 0) + 'cm' : (labelData.volume || 0) + 'm³'}} diff --git a/print/templates/reports/collection-label/collection-label.js b/print/templates/reports/collection-label/collection-label.js index d45ecb2bce..cd6ff719a1 100644 --- a/print/templates/reports/collection-label/collection-label.js +++ b/print/templates/reports/collection-label/collection-label.js @@ -18,9 +18,9 @@ module.exports = { } }, async serverPrefetch() { + await this.rawSql('SET @hasPrevia := 0'); let ticketIds; const res = await this.rawSqlFromDef('tickets', [this.id]); - if (res.length) { ticketIds = []; for (const row of res) diff --git a/print/templates/reports/collection-label/sql/labelsData.sql b/print/templates/reports/collection-label/sql/labelsData.sql index fef692272c..7a12a56d33 100644 --- a/print/templates/reports/collection-label/sql/labelsData.sql +++ b/print/templates/reports/collection-label/sql/labelsData.sql @@ -7,14 +7,16 @@ SELECT c.itemPackingTypeFk code, cc.code color, t.clientFk, CAST(SUM(sv.volume) AS DECIMAL(5, 2)) volume, - MAX(i.`size`) `size`, + MAX( + IF(sgd.id, IFNULL(pc.itemPreviousDefaultSize, i.`size`), i.`size`) + ) `size`, w.code workerCode, TIME_FORMAT(t.shipped, '%H:%i') shippedHour, TIME_FORMAT(zo.`hour`, '%H:%i') zoneHour, DATE_FORMAT(t.shipped, '%d/%m/%y') shipped, tt.labelCount, t.nickName, - COUNT(*) lineCount, + SUM(IF(sgd.id, IF(@hasPrevia, 0, @hasPrevia := 1), 1)) lineCount, rm.routeFk FROM vn.ticket t JOIN vn.ticketCollection tc ON tc.ticketFk = t.id @@ -23,7 +25,7 @@ SELECT c.itemPackingTypeFk code, AND cc.wagon = tc.wagon AND cc.trainFk = c.trainFk JOIN vn.sale s ON s.ticketFk = t.id - LEFT JOIN vn.saleVolume sv ON sv.saleFk = s.id + LEFT JOIN vn.saleVolume sv ON sv.saleFk = s.id JOIN vn.item i ON i.id = s.itemFk JOIN vn.itemType it ON it.id = i.typeFk JOIN vn.itemCategory ic ON ic.id = it.categoryFk @@ -32,7 +34,9 @@ SELECT c.itemPackingTypeFk code, LEFT JOIN vn.ticketTrolley tt ON tt.ticket = t.id LEFT JOIN vn.`zone` zo ON t.zoneFk = zo.id LEFT JOIN vn.routesMonitor rm ON rm.routeFk = t.routeFk - LEFT JOIN vn.expeditionTruck et ON et.id = rm.expeditionTruckFk + LEFT JOIN vn.expeditionTruck et ON et.id = rm.expeditionTruckFk + LEFT JOIN vn.saleGroupDetail sgd ON sgd.saleFk = s.id + JOIN vn.productionConfig pc WHERE t.id IN (?) GROUP BY t.id - ORDER BY cc.`code`; \ No newline at end of file + ORDER BY cc.`code` \ No newline at end of file diff --git a/print/templates/reports/invoice-incoterms/invoice-incoterms.js b/print/templates/reports/invoice-incoterms/invoice-incoterms.js index 2c183b6ccf..9cc2600af1 100755 --- a/print/templates/reports/invoice-incoterms/invoice-incoterms.js +++ b/print/templates/reports/invoice-incoterms/invoice-incoterms.js @@ -7,7 +7,7 @@ module.exports = { this.invoice = await this.findOneFromDef('invoice', [this.reference]); this.checkMainEntity(this.invoice); this.client = await this.findOneFromDef('client', [this.reference]); - this.incoterms = await this.findOneFromDef('incoterms', [this.reference, this.reference, this.reference]); + this.incoterms = await this.findOneFromDef('incoterms', [this.reference, this.reference, this.reference, this.reference]); }, props: { reference: { diff --git a/print/templates/reports/invoice-incoterms/sql/client.sql b/print/templates/reports/invoice-incoterms/sql/client.sql index 3e66c15c95..f3734bcd93 100644 --- a/print/templates/reports/invoice-incoterms/sql/client.sql +++ b/print/templates/reports/invoice-incoterms/sql/client.sql @@ -1,13 +1,12 @@ -SELECT - c.id, - c.socialName, - c.street AS postalAddress, - IF (ios.taxAreaFk IS NOT NULL, CONCAT(cty.code, c.fi), c.fi) fi, - CONCAT(c.postcode, ' - ', c.city) postcodeCity -FROM vn.invoiceOut io - JOIN vn.client c ON c.id = io.clientFk - JOIN vn.country cty ON cty.id = c.countryFk - LEFT JOIN vn.invoiceOutSerial ios ON ios.code = io.serial - AND ios.taxAreaFk = 'CEE' - LEFT JOIN ticket t ON t.refFk = io.ref -WHERE t.refFk = ? \ No newline at end of file +SELECT c.id, + c.socialName, + c.street postalAddress, + IF (ios.taxAreaFk IS NOT NULL, CONCAT(cty.code, c.fi), c.fi) fi, + CONCAT(c.postcode, ' - ', c.city) postcodeCity + FROM vn.invoiceOut io + JOIN vn.client c ON c.id = io.clientFk + JOIN vn.country cty ON cty.id = c.countryFk + LEFT JOIN vn.invoiceOutSerial ios ON ios.code = io.serial + AND ios.taxAreaFk = 'CEE' + LEFT JOIN ticket t ON t.refFk = io.ref + WHERE t.refFk = ? \ No newline at end of file diff --git a/print/templates/reports/invoice-incoterms/sql/incoterms.sql b/print/templates/reports/invoice-incoterms/sql/incoterms.sql index 0c4af803d8..b36f500841 100644 --- a/print/templates/reports/invoice-incoterms/sql/incoterms.sql +++ b/print/templates/reports/invoice-incoterms/sql/incoterms.sql @@ -1,38 +1,14 @@ -SELECT io.issued, - c.socialName, - c.street postalAddress, - c.fi, - io.clientFk, - c.postcode, - c.city, - io.companyFk, - io.ref, - tc.code, - s.concept, - s.quantity, - s.price, - s.discount, - s.ticketFk, - t.shipped, - t.refFk, - a.nickname, - s.itemFk, - s.id saleFk, - pm.name AS pmname, - sa.iban, - c.phone, - MAX(t.packages) packages, +SELECT pack.packages, a.incotermsFk, - ic.name incotermsName , + ic.name incotermsName, t.weight, - t.observations, ca.fiscalName customsAgentName, ca.street customsAgentStreet, ca.nif customsAgentNif, ca.phone customsAgentPhone, ca.email customsAgentEmail, - CAST(sub2.volume AS DECIMAL (10,2)) volume, - sub3.intrastat + CAST(vol.volume AS DECIMAL (10,2)) volume, + intr.intrastat FROM vn.invoiceOut io JOIN vn.supplier su ON su.id = io.companyFk JOIN vn.client c ON c.id = io.clientFk @@ -42,26 +18,35 @@ SELECT io.issued, LEFT JOIN vn.incoterms ic ON ic.code = a.incotermsFk LEFT JOIN vn.customsAgent ca ON ca.id = a.customsAgentFk JOIN vn.sale s ON s.ticketFk = t.id - JOIN (SELECT SUM(volume) volume - FROM vn.invoiceOut io - JOIN vn.ticket t ON t.refFk = io.ref - JOIN vn.saleVolume sv ON sv.ticketFk = t.id - WHERE t.refFk = ? - ) sub2 ON TRUE - JOIN vn.itemTaxCountry itc ON itc.countryFk = su.countryFk AND itc.itemFk = s.itemFk + JOIN ( + SELECT SUM(volume) volume, COUNT(*) packages + FROM vn.invoiceOut io + JOIN vn.ticket t ON t.refFk = io.ref + JOIN vn.saleVolume sv ON sv.ticketFk = t.id + WHERE t.refFk = ? + ) vol + JOIN vn.itemTaxCountry itc ON itc.countryFk = su.countryFk + AND itc.itemFk = s.itemFk JOIN vn.taxClass tc ON tc.id = itc.taxClassFk - JOIN vn.invoiceOutSerial ios ON ios.code = io.serial AND ios.taxAreaFk = 'WORLD' + JOIN vn.invoiceOutSerial ios ON ios.code = io.serial + AND ios.taxAreaFk = 'WORLD' JOIN vn.country cty ON cty.id = c.countryFk JOIN vn.payMethod pm ON pm.id = c .payMethodFk JOIN vn.company co ON co.id=io.companyFk JOIN vn.supplierAccount sa ON sa.id=co.supplierAccountFk - LEFT JOIN (SELECT GROUP_CONCAT(DISTINCT ir.description ORDER BY ir.description SEPARATOR '. ' ) as intrastat - FROM vn.ticket t - JOIN vn.invoiceOut io ON io.ref = t.refFk - JOIN vn.sale s ON t.id = s.ticketFk - JOIN vn.item i ON i.id = s.itemFk - JOIN vn.intrastat ir ON ir.id = i.intrastatFk - WHERE t.refFk = ? - )sub3 ON TRUE - WHERE t.refFk = ? - + JOIN ( + SELECT GROUP_CONCAT(DISTINCT ir.description ORDER BY ir.description SEPARATOR '. ' ) intrastat + FROM vn.ticket t + JOIN vn.invoiceOut io ON io.ref = t.refFk + JOIN vn.sale s ON t.id = s.ticketFk + JOIN vn.item i ON i.id = s.itemFk + JOIN vn.intrastat ir ON ir.id = i.intrastatFk + WHERE t.refFk = ? + ) intr + JOIN ( + SELECT SUM(packages) packages + FROM ticket + WHERE refFk = ? + ) pack + WHERE t.refFk = ? + LIMIT 1 diff --git a/print/templates/reports/invoice-incoterms/sql/invoice.sql b/print/templates/reports/invoice-incoterms/sql/invoice.sql index 571ea2af39..02074f5c42 100644 --- a/print/templates/reports/invoice-incoterms/sql/invoice.sql +++ b/print/templates/reports/invoice-incoterms/sql/invoice.sql @@ -1,18 +1,17 @@ -SELECT - io.id, - io.issued, - io.clientFk, - io.companyFk, - io.ref, - pm.code AS payMethodCode, - cny.code companyCode, - sa.iban, - ios.footNotes -FROM invoiceOut io - JOIN client c ON c.id = io.clientFk - JOIN payMethod pm ON pm.id = c.payMethodFk - JOIN company cny ON cny.id = io.companyFk - JOIN supplierAccount sa ON sa.id = cny.supplierAccountFk - LEFT JOIN invoiceOutSerial ios ON ios.code = io.serial - LEFT JOIN ticket t ON t.refFk = io.ref -WHERE t.refFk = ? \ No newline at end of file +SELECT io.id, + io.issued, + io.clientFk, + io.companyFk, + io.ref, + pm.code payMethodCode, + cny.code companyCode, + sa.iban, + ios.footNotes + FROM invoiceOut io + JOIN client c ON c.id = io.clientFk + JOIN payMethod pm ON pm.id = c.payMethodFk + JOIN company cny ON cny.id = io.companyFk + JOIN supplierAccount sa ON sa.id = cny.supplierAccountFk + LEFT JOIN invoiceOutSerial ios ON ios.code = io.serial + LEFT JOIN ticket t ON t.refFk = io.ref + WHERE t.refFk = ? \ No newline at end of file diff --git a/print/templates/reports/sepa-core/locale/es.yml b/print/templates/reports/sepa-core/locale/es.yml index 5f3f08fc30..2737dc3132 100644 --- a/print/templates/reports/sepa-core/locale/es.yml +++ b/print/templates/reports/sepa-core/locale/es.yml @@ -45,4 +45,4 @@ instructions: title: Instrucciones accountFields: Rellenar los campos relativos a la cuenta bancaria signDocument: Firmar y sellar el documento. Para que tenga validez, en el sello debe aparecer el CIF/NIF. De no ser así, deberá acompañarse la solicitud de un certificado de titularidad de la cuenta. - thanks: ¡Gracias por su colaboración! \ No newline at end of file + thanks: ¡Gracias por su colaboración! diff --git a/print/templates/reports/sepa-core/locale/fr.yml b/print/templates/reports/sepa-core/locale/fr.yml index 354c061145..355b880a1c 100644 --- a/print/templates/reports/sepa-core/locale/fr.yml +++ b/print/templates/reports/sepa-core/locale/fr.yml @@ -1,11 +1,11 @@ reportName: direct-debit title: Direct Debit -description: En signant ce formulaire de mandat, vous autorisez VERDNATURA LEVANTE SL - à envoyer des instructions à votre banque pour débiter votre compte, et (B) votre banque - à débiter votre compte conformément aux instructions de VERDNATURA LEVANTE SL. - Vous bénéficiez d’un droit au remboursement par votre banque selon les conditions décrites - dans la convention que vous avez passée avec elle. Toute demande de remboursement doit être - présentée dans les 8 semaines suivant la date de débit de votre compte. +description: En signant ce formulaire de mandat, vous autorisez VERDNATURA LEVANTE SL + à envoyer des instructions à votre banque pour débiter votre compte, et (B) votre banque + à débiter votre compte conformément aux instructions de VERDNATURA LEVANTE SL. + Vous bénéficiez d’un droit au remboursement par votre banque selon les conditions décrites + dans la convention que vous avez passée avec elle. Toute demande de remboursement doit être + présentée dans les 8 semaines suivant la date de débit de votre compte. Votre banque peut vous renseigner au sujet de vos droits relatifs à ce mandat. documentCopy: Veuillez dater, signer et retourner ce document à votre banque. mandatoryFields: TOUS LES CHAMPS DOIVENT ÊTRE REINSEGNÉS IMPÉRATIVEMENT. @@ -42,4 +42,4 @@ instructions: title: instructions accountFields: Remplissez les champs relatifs au compte bancaire signDocument: Signez et scellez le document. Pour être valide, le CIF / NIF doit apparaître sur le cachet. Sinon, la demande de certificat de propriété du compte doit être jointe. - thanks: Merci de votre collaboration! \ No newline at end of file + thanks: Merci de votre collaboration! diff --git a/print/templates/reports/sepa-core/sepa-core.html b/print/templates/reports/sepa-core/sepa-core.html index a8c270ad50..363ebdfe52 100644 --- a/print/templates/reports/sepa-core/sepa-core.html +++ b/print/templates/reports/sepa-core/sepa-core.html @@ -27,8 +27,8 @@ {{$t('supplier.identifier')}} -
ES89000B97367486
-
B97367486-000
+
{{supplier.iban}}
+
{{supplier.nif}}
diff --git a/print/templates/reports/sepa-core/sepa-core.js b/print/templates/reports/sepa-core/sepa-core.js index 0e19d2a6af..96c512a9df 100755 --- a/print/templates/reports/sepa-core/sepa-core.js +++ b/print/templates/reports/sepa-core/sepa-core.js @@ -1,4 +1,5 @@ const vnReport = require('../../../core/mixins/vn-report.js'); +const db = require('../../../core/database'); module.exports = { name: 'sepa-core', @@ -18,5 +19,16 @@ module.exports = { type: Number, required: true } + }, + methods: { + getSupplierCif() { + return db.findOne(` + SELECT sa.iban, s.nif + FROM supplierAccount sa + JOIN company co ON co.supplierAccountFk = sa.id + JOIN supplier s ON sa.supplierFk = s.id + WHERE co.id = ?`) [this.companyId]; + } } + }; diff --git a/print/templates/reports/sepa-core/sql/supplier.sql b/print/templates/reports/sepa-core/sql/supplier.sql index 55b0e49cae..80635ecf59 100644 --- a/print/templates/reports/sepa-core/sql/supplier.sql +++ b/print/templates/reports/sepa-core/sql/supplier.sql @@ -1,17 +1,27 @@ SELECT - m.code mandateCode, - s.name, - s.street, - sc.country, - s.postCode, - s.city, - sp.name province -FROM client c - LEFT JOIN mandate m ON m.clientFk = c.id - AND m.companyFk = ? AND m.finished IS NULL + m.code mandateCode, + s.name, + s.street, + sc.country, + s.postCode, + s.city, + sp.name province, + s.nif, + sa.iban, + sa.supplierFk, + be.name bankName +FROM + client c + LEFT JOIN mandate m ON m.clientFk = c.id AND m.companyFk = ? AND m.finished IS NULL LEFT JOIN supplier s ON s.id = m.companyFk LEFT JOIN country sc ON sc.id = s.countryFk LEFT JOIN province sp ON sp.id = s.provinceFk LEFT JOIN province p ON p.id = c.provinceFk -WHERE (m.companyFk = ? OR m.companyFk IS NULL) AND c.id = ? -ORDER BY m.created DESC LIMIT 1 \ No newline at end of file + LEFT JOIN supplierAccount sa ON sa.supplierFk = s.id + LEFT JOIN bankEntity be ON sa.bankEntityFk = be.id +WHERE + (m.companyFk = ? OR m.companyFk IS NULL) + AND (c.id = ? OR (c.id IS NULL AND c.countryFk = sa.countryFk)) +ORDER BY + m.created DESC +LIMIT 1;