diff --git a/back/methods/chat/sendCheckingPresence.js b/back/methods/chat/sendCheckingPresence.js index 274ec3a5b..85b66e94b 100644 --- a/back/methods/chat/sendCheckingPresence.js +++ b/back/methods/chat/sendCheckingPresence.js @@ -26,15 +26,14 @@ module.exports = Self => { Self.sendCheckingPresence = async(ctx, recipientId, message) => { if (!recipientId) return false; - const models = Self.app.models; + const userId = ctx.req.accessToken.userId; const sender = await models.VnUser.findById(userId, {fields: ['id']}); const recipient = await models.VnUser.findById(recipientId, null); // Prevent sending messages to yourself if (recipientId == userId) return false; - if (!recipient) throw new Error(`Could not send message "${message}" to worker id ${recipientId} from user ${userId}`); diff --git a/back/methods/collection/getTickets.js b/back/methods/collection/getTickets.js index 445fc070d..f04822697 100644 --- a/back/methods/collection/getTickets.js +++ b/back/methods/collection/getTickets.js @@ -26,7 +26,7 @@ module.exports = Self => { Self.getTickets = async(ctx, id, print, options) => { const userId = ctx.req.accessToken.userId; - const origin = ctx.req.headers.origin; + const url = await Self.app.models.Url.getUrl(); const $t = ctx.req.__; const myOptions = {}; @@ -36,7 +36,6 @@ module.exports = Self => { myOptions.userId = userId; const promises = []; - const [tickets] = await Self.rawSql(`CALL vn.collection_getTickets(?)`, [id], myOptions); const sales = await Self.rawSql(` SELECT s.ticketFk, @@ -86,24 +85,19 @@ module.exports = Self => { if (tickets && tickets.length) { for (const ticket of tickets) { const ticketId = ticket.ticketFk; - - // SEND ROCKET if (ticket.observaciones != '') { for (observation of ticket.observaciones.split(' ')) { if (['#', '@'].includes(observation.charAt(0))) { promises.push(Self.app.models.Chat.send(ctx, observation, $t('The ticket is in preparation', { ticketId: ticketId, - ticketUrl: `${origin}/#!/ticket/${ticketId}/summary`, + ticketUrl: `${url}ticket/${ticketId}/summary`, salesPersonId: ticket.salesPersonFk }))); } } } - - // SET COLLECTION if (sales && sales.length) { - // GET BARCODES const barcodes = await Self.rawSql(` SELECT s.id saleFk, b.code, c.id FROM vn.sale s @@ -114,13 +108,10 @@ module.exports = Self => { WHERE s.ticketFk = ? AND tr.landed >= util.VN_CURDATE() - INTERVAL 1 YEAR`, [ticketId], myOptions); - - // BINDINGS ticket.sales = []; for (const sale of sales) { if (sale.ticketFk === ticketId) { sale.Barcodes = []; - if (barcodes && barcodes.length) { for (const barcode of barcodes) { if (barcode.saleFk === sale.saleFk) { @@ -131,7 +122,6 @@ module.exports = Self => { } } } - ticket.sales.push(sale); } } @@ -140,7 +130,6 @@ module.exports = Self => { } } await Promise.all(promises); - return collection; }; }; diff --git a/back/methods/collection/spec/setSaleQuantity.spec.js b/back/methods/collection/spec/setSaleQuantity.spec.js index fdc1bce1a..b563f5b19 100644 --- a/back/methods/collection/spec/setSaleQuantity.spec.js +++ b/back/methods/collection/spec/setSaleQuantity.spec.js @@ -18,6 +18,14 @@ describe('setSaleQuantity()', () => { it('should change quantity sale', async() => { const tx = await models.Ticket.beginTransaction({}); + spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => { + if (sqlStatement.includes('catalog_calcFromItem')) { + sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY + SELECT 100 as available;`; + params = null; + } + return models.Ticket.rawSql(sqlStatement, params, options); + }); try { const options = {transaction: tx}; diff --git a/back/methods/url/getByUser.js b/back/methods/url/getByUser.js new file mode 100644 index 000000000..dd4805182 --- /dev/null +++ b/back/methods/url/getByUser.js @@ -0,0 +1,40 @@ +module.exports = function(Self) { + Self.remoteMethod('getByUser', { + description: 'returns the starred modules for the current user', + accessType: 'READ', + accepts: [{ + arg: 'userId', + type: 'number', + description: 'The user id', + required: true, + http: {source: 'path'} + }], + returns: { + type: 'object', + root: true + }, + http: { + path: `/:userId/get-by-user`, + verb: 'GET' + } + }); + + Self.getByUser = async userId => { + const models = Self.app.models; + const appNames = ['hedera']; + const filter = { + fields: ['appName', 'url'], + where: { + appName: {inq: appNames}, + environment: process.env.NODE_ENV ?? 'development', + } + }; + + const isWorker = await models.Account.findById(userId, {fields: ['id']}); + if (!isWorker) + return models.Url.find(filter); + + appNames.push('salix'); + return models.Url.find(filter); + }; +}; diff --git a/back/methods/url/getUrl.js b/back/methods/url/getUrl.js new file mode 100644 index 000000000..f30719b9f --- /dev/null +++ b/back/methods/url/getUrl.js @@ -0,0 +1,30 @@ +module.exports = Self => { + Self.remoteMethod('getUrl', { + description: 'Returns the colling app name', + accessType: 'READ', + accepts: [ + { + arg: 'app', + type: 'string', + required: false + } + ], + returns: { + type: 'object', + root: true + }, + http: { + path: `/getUrl`, + verb: 'get' + } + }); + Self.getUrl = async(appName = 'salix') => { + const {url} = await Self.app.models.Url.findOne({ + where: { + appName, + enviroment: process.env.NODE_ENV || 'development' + } + }); + return url; + }; +}; diff --git a/back/methods/url/specs/getByUser.spec.js b/back/methods/url/specs/getByUser.spec.js new file mode 100644 index 000000000..f6af6ec00 --- /dev/null +++ b/back/methods/url/specs/getByUser.spec.js @@ -0,0 +1,19 @@ +const {models} = require('vn-loopback/server/server'); + +describe('getByUser()', () => { + const worker = 1; + const notWorker = 2; + it(`should return only hedera url if not is worker`, async() => { + const urls = await models.Url.getByUser(notWorker); + + expect(urls.length).toEqual(1); + expect(urls[0].appName).toEqual('hedera'); + }); + + it(`should return more than hedera url`, async() => { + const urls = await models.Url.getByUser(worker); + + expect(urls.length).toBeGreaterThan(1); + expect(urls.find(url => url.appName == 'salix').appName).toEqual('salix'); + }); +}); diff --git a/back/methods/vn-user/update-user.js b/back/methods/vn-user/update-user.js new file mode 100644 index 000000000..ddaae8548 --- /dev/null +++ b/back/methods/vn-user/update-user.js @@ -0,0 +1,39 @@ +module.exports = Self => { + Self.remoteMethodCtx('updateUser', { + description: 'Update user data', + accepts: [ + { + arg: 'id', + type: 'integer', + description: 'The user id', + required: true, + http: {source: 'path'} + }, { + arg: 'name', + type: 'string', + description: 'The user name', + }, { + arg: 'nickname', + type: 'string', + description: 'The user nickname', + }, { + arg: 'email', + type: 'string', + description: 'The user email' + }, { + arg: 'lang', + type: 'string', + description: 'The user lang' + } + ], + http: { + path: `/:id/update-user`, + verb: 'PATCH' + } + }); + + Self.updateUser = async(ctx, id, name, nickname, email, lang) => { + await Self.userSecurity(ctx, id); + await Self.upsertWithWhere({id}, {name, nickname, email, lang}); + }; +}; diff --git a/back/models/chat.js b/back/models/chat.js index a18edbd3f..882db747e 100644 --- a/back/models/chat.js +++ b/back/models/chat.js @@ -7,17 +7,14 @@ module.exports = Self => { Self.observe('before save', async function(ctx) { if (!ctx.isNewInstance) return; - let {message} = ctx.instance; if (!message) return; const parts = message.match(/(?<=\[)[a-zA-Z0-9_\-+!@#$%^&*()={};':"\\|,.<>/?\s]*(?=])/g); if (!parts) return; - const replacedParts = parts.map(part => { return part.replace(/[!$%^&*()={};':"\\,.<>/?]/g, ''); }); - for (const [index, part] of parts.entries()) message = message.replace(part, replacedParts[index]); diff --git a/back/models/specs/vn-user.spec.js b/back/models/specs/vn-user.spec.js index 3700b919a..8689a7854 100644 --- a/back/models/specs/vn-user.spec.js +++ b/back/models/specs/vn-user.spec.js @@ -1,4 +1,5 @@ const models = require('vn-loopback/server/server').models; +const ForbiddenError = require('vn-loopback/util/forbiddenError'); describe('loopback model VnUser', () => { it('should return true if the user has the given role', async() => { @@ -12,4 +13,42 @@ describe('loopback model VnUser', () => { expect(result).toBeFalsy(); }); + + describe('userSecurity', () => { + const itManagementId = 115; + const hrId = 37; + const employeeId = 1; + + it('should check if you are the same user', async() => { + const ctx = {options: {accessToken: {userId: employeeId}}}; + await models.VnUser.userSecurity(ctx, employeeId); + }); + + it('should check for higher privileges', async() => { + const ctx = {options: {accessToken: {userId: itManagementId}}}; + await models.VnUser.userSecurity(ctx, employeeId); + }); + + it('should check if you have medium privileges and the user email is not verified', async() => { + const ctx = {options: {accessToken: {userId: hrId}}}; + await models.VnUser.userSecurity(ctx, employeeId); + }); + + it('should throw an error if you have medium privileges and the users email is verified', async() => { + const tx = await models.VnUser.beginTransaction({}); + const ctx = {options: {accessToken: {userId: hrId}}}; + try { + const options = {transaction: tx}; + const userToUpdate = await models.VnUser.findById(1, null, options); + userToUpdate.updateAttribute('emailVerified', 1, options); + + await models.VnUser.userSecurity(ctx, employeeId, options); + await tx.rollback(); + } catch (error) { + await tx.rollback(); + + expect(error).toEqual(new ForbiddenError()); + } + }); + }); }); diff --git a/back/models/url.js b/back/models/url.js new file mode 100644 index 000000000..b603e9eb5 --- /dev/null +++ b/back/models/url.js @@ -0,0 +1,4 @@ +module.exports = Self => { + require('../methods/url/getByUser')(Self); + require('../methods/url/getUrl')(Self); +}; diff --git a/back/models/vn-user.js b/back/models/vn-user.js index cf210b61b..de5bf7b63 100644 --- a/back/models/vn-user.js +++ b/back/models/vn-user.js @@ -1,6 +1,7 @@ const vnModel = require('vn-loopback/common/models/vn-model'); -const LoopBackContext = require('loopback-context'); const {Email} = require('vn-print'); +const ForbiddenError = require('vn-loopback/util/forbiddenError'); +const LoopBackContext = require('loopback-context'); module.exports = function(Self) { vnModel(Self); @@ -12,6 +13,7 @@ module.exports = function(Self) { require('../methods/vn-user/privileges')(Self); require('../methods/vn-user/validate-auth')(Self); require('../methods/vn-user/renew-token')(Self); + require('../methods/vn-user/update-user')(Self); Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create'); @@ -90,11 +92,7 @@ module.exports = function(Self) { }; Self.on('resetPasswordRequest', async function(info) { - const loopBackContext = LoopBackContext.getCurrentContext(); - const httpCtx = {req: loopBackContext.active}; - const httpRequest = httpCtx.req.http.req; - const headers = httpRequest.headers; - const origin = headers.origin; + const url = await Self.app.models.Url.getUrl(); const defaultHash = '/reset-password?access_token=$token$'; const recoverHashes = { @@ -110,7 +108,7 @@ module.exports = function(Self) { const params = { recipient: info.email, lang: user.lang, - url: origin + '/#!' + recoverHash + url: url.slice(0, -1) + recoverHash }; const options = Object.assign({}, info.options); @@ -178,45 +176,75 @@ module.exports = function(Self) { Self.sharedClass._methods.find(method => method.name == 'changePassword').ctor.settings.acls .filter(acl => acl.property != 'changePassword'); - // FIXME: https://redmine.verdnatura.es/issues/5761 - // Self.afterRemote('prototype.patchAttributes', async(ctx, instance) => { - // if (!ctx.args || !ctx.args.data.email) return; + Self.userSecurity = async(ctx, userId, options) => { + const models = Self.app.models; + const accessToken = ctx?.options?.accessToken || LoopBackContext.getCurrentContext().active.accessToken; + const ctxToken = {req: {accessToken}}; - // const loopBackContext = LoopBackContext.getCurrentContext(); - // const httpCtx = {req: loopBackContext.active}; - // const httpRequest = httpCtx.req.http.req; - // const headers = httpRequest.headers; - // const origin = headers.origin; - // const url = origin.split(':'); + if (userId === accessToken.userId) return; - // class Mailer { - // async send(verifyOptions, cb) { - // const params = { - // url: verifyOptions.verifyHref, - // recipient: verifyOptions.to, - // lang: ctx.req.getLocale() - // }; + const myOptions = {}; + if (typeof options == 'object') + Object.assign(myOptions, options); - // const email = new Email('email-verify', params); - // email.send(); + const hasHigherPrivileges = await models.ACL.checkAccessAcl(ctxToken, 'VnUser', 'higherPrivileges', myOptions); + if (hasHigherPrivileges) return; - // cb(null, verifyOptions.to); - // } - // } + const hasMediumPrivileges = await models.ACL.checkAccessAcl(ctxToken, 'VnUser', 'mediumPrivileges', myOptions); + const user = await models.VnUser.findById(userId, {fields: ['id', 'emailVerified']}, myOptions); + if (!user.emailVerified && hasMediumPrivileges) return; - // const options = { - // type: 'email', - // to: instance.email, - // from: {}, - // redirect: `${origin}/#!/account/${instance.id}/basic-data?emailConfirmed`, - // template: false, - // mailer: new Mailer, - // host: url[1].split('/')[2], - // port: url[2], - // protocol: url[0], - // user: Self - // }; + throw new ForbiddenError(); + }; - // await instance.verify(options); - // }); + Self.observe('after save', async ctx => { + const instance = ctx?.instance; + const newEmail = instance?.email; + const oldEmail = ctx?.hookState?.oldInstance?.email; + if (!ctx.isNewInstance && (!newEmail || !oldEmail || newEmail == oldEmail)) return; + + const loopBackContext = LoopBackContext.getCurrentContext(); + const httpCtx = {req: loopBackContext.active}; + const httpRequest = httpCtx.req.http.req; + const headers = httpRequest.headers; + const origin = headers.origin; + const url = origin.split(':'); + + const env = process.env.NODE_ENV; + const liliumUrl = await Self.app.models.Url.findOne({ + where: {and: [ + {appName: 'lilium'}, + {environment: env} + ]} + }); + + class Mailer { + async send(verifyOptions, cb) { + const params = { + url: verifyOptions.verifyHref, + recipient: verifyOptions.to + }; + + const email = new Email('email-verify', params); + email.send(); + + cb(null, verifyOptions.to); + } + } + + const options = { + type: 'email', + to: newEmail, + from: {}, + redirect: `${liliumUrl.url}verifyEmail?userId=${instance.id}`, + template: false, + mailer: new Mailer, + host: url[1].split('/')[2], + port: url[2], + protocol: url[0], + user: Self + }; + + await instance.verify(options, ctx.options); + }); }; diff --git a/back/models/vn-user.json b/back/models/vn-user.json index f5eb3ae0f..0f6daff5a 100644 --- a/back/models/vn-user.json +++ b/back/models/vn-user.json @@ -13,15 +13,12 @@ "type": "number", "id": true }, - "name": { + "name": { "type": "string", "required": true }, "username": { - "type": "string", - "mysql": { - "columnName": "name" - } + "type": "string" }, "roleFk": { "type": "number", @@ -38,6 +35,12 @@ "active": { "type": "boolean" }, + "email": { + "type": "string" + }, + "emailVerified": { + "type": "boolean" + }, "created": { "type": "date" }, @@ -137,7 +140,8 @@ "image", "hasGrant", "realm", - "email" + "email", + "emailVerified" ] } } diff --git a/db/changes/234201/00-account_acl.sql b/db/changes/234201/00-account_acl.sql new file mode 100644 index 000000000..8dfe1d1ec --- /dev/null +++ b/db/changes/234201/00-account_acl.sql @@ -0,0 +1,7 @@ +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) + VALUES + ('VnUser', 'higherPrivileges', '*', 'ALLOW', 'ROLE', 'itManagement'), + ('VnUser', 'mediumPrivileges', '*', 'ALLOW', 'ROLE', 'hr'), + ('VnUser', 'updateUser', '*', 'ALLOW', 'ROLE', 'employee'); + +ALTER TABLE `account`.`user` ADD `username` varchar(30) AS (name) VIRTUAL; diff --git a/db/changes/234201/00-aclSetPassword.sql b/db/changes/234201/00-aclSetPassword.sql new file mode 100644 index 000000000..44b3e9de0 --- /dev/null +++ b/db/changes/234201/00-aclSetPassword.sql @@ -0,0 +1,4 @@ +INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalType,principalId) + VALUES ('Worker','setPassword','*','ALLOW','ROLE','employee'); + + diff --git a/db/changes/234201/00-aclUrlHedera.sql b/db/changes/234201/00-aclUrlHedera.sql new file mode 100644 index 000000000..79d9fb4c8 --- /dev/null +++ b/db/changes/234201/00-aclUrlHedera.sql @@ -0,0 +1,7 @@ +INSERT INTO `salix`.`url` (`appName`, `environment`, `url`) + VALUES + ('hedera', 'test', 'https://test-shop.verdnatura.es/'), + ('hedera', 'production', 'https://shop.verdnatura.es/'); + +INSERT INTO `salix`.`ACL` ( model, property, accessType, permission, principalType, principalId) + VALUES('Url', 'getByUser', 'READ', 'ALLOW', 'ROLE', '$everyone'); diff --git a/db/changes/234201/00-packagingFk.sql b/db/changes/234201/00-packagingFk.sql new file mode 100644 index 000000000..9a775ada9 --- /dev/null +++ b/db/changes/234201/00-packagingFk.sql @@ -0,0 +1,5 @@ +ALTER TABLE `vn`.`buy` CHANGE `packageFk` `packagingFk` varchar(10) +CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT '--' NULL; + +ALTER TABLE `vn`.`buy` +ADD COLUMN `packageFk` varchar(10) AS (`packagingFk`) VIRTUAL; \ No newline at end of file diff --git a/db/changes/234201/00-packagingFkviews.sql b/db/changes/234201/00-packagingFkviews.sql new file mode 100644 index 000000000..f355325f3 --- /dev/null +++ b/db/changes/234201/00-packagingFkviews.sql @@ -0,0 +1,146 @@ +CREATE OR REPLACE DEFINER=`root`@`localhost` + SQL SECURITY DEFINER + VIEW `vn2008`.`Compres` +AS SELECT `c`.`id` AS `Id_Compra`, + `c`.`entryFk` AS `Id_Entrada`, + `c`.`itemFk` AS `Id_Article`, + `c`.`buyingValue` AS `Costefijo`, + `c`.`quantity` AS `Cantidad`, + `c`.`packagingFk` AS `Id_Cubo`, + `c`.`stickers` AS `Etiquetas`, + `c`.`freightValue` AS `Portefijo`, + `c`.`packageValue` AS `Embalajefijo`, + `c`.`comissionValue` AS `Comisionfija`, + `c`.`packing` AS `Packing`, + `c`.`grouping` AS `grouping`, + `c`.`groupingMode` AS `caja`, + `c`.`location` AS `Nicho`, + `c`.`price1` AS `Tarifa1`, + `c`.`price2` AS `Tarifa2`, + `c`.`price3` AS `Tarifa3`, + `c`.`minPrice` AS `PVP`, + `c`.`printedStickers` AS `Vida`, + `c`.`isChecked` AS `punteo`, + `c`.`ektFk` AS `buy_edi_id`, + `c`.`created` AS `odbc_date`, + `c`.`isIgnored` AS `Novincular`, + `c`.`isPickedOff` AS `isPickedOff`, + `c`.`workerFk` AS `Id_Trabajador`, + `c`.`weight` AS `weight`, + `c`.`dispatched` AS `dispatched`, + `c`.`containerFk` AS `container_id`, + `c`.`itemOriginalFk` AS `itemOriginalFk` +FROM `vn`.`buy` `c`; + +CREATE OR REPLACE DEFINER=`root`@`localhost` + SQL SECURITY DEFINER + VIEW `vn2008`.`buySource` +AS SELECT `b`.`entryFk` AS `Id_Entrada`, + `b`.`isPickedOff` AS `isPickedOff`, + NULL AS `tarifa0`, + `e`.`kop` AS `kop`, + `b`.`id` AS `Id_Compra`, + `i`.`typeFk` AS `tipo_id`, + `b`.`itemFk` AS `Id_Article`, + `i`.`size` AS `Medida`, + `i`.`stems` AS `Tallos`, + `b`.`stickers` AS `Etiquetas`, + `b`.`packagingFk` AS `Id_Cubo`, + `b`.`buyingValue` AS `Costefijo`, + `b`.`packing` AS `Packing`, + `b`.`grouping` AS `Grouping`, + `b`.`quantity` AS `Cantidad`, + `b`.`price2` AS `Tarifa2`, + `b`.`price3` AS `Tarifa3`, + `b`.`isChecked` AS `Punteo`, + `b`.`groupingMode` AS `Caja`, + `i`.`isToPrint` AS `Imprimir`, + `i`.`name` AS `Article`, + `vn`.`ink`.`picture` AS `Tinta`, + `i`.`originFk` AS `id_origen`, + `i`.`minPrice` AS `PVP`, + NULL AS `Id_Accion`, + `s`.`company_name` AS `pro`, + `i`.`hasMinPrice` AS `Min`, + `b`.`isIgnored` AS `Novincular`, + `b`.`freightValue` AS `Portefijo`, + round(`b`.`buyingValue` * `b`.`quantity`, 2) AS `Importe`, + `b`.`printedStickers` AS `Vida`, + `i`.`comment` AS `reference`, + `b`.`workerFk` AS `Id_Trabajador`, + `e`.`s1` AS `S1`, + `e`.`s2` AS `S2`, + `e`.`s3` AS `S3`, + `e`.`s4` AS `S4`, + `e`.`s5` AS `S5`, + `e`.`s6` AS `S6`, + 0 AS `price_fixed`, + `i`.`producerFk` AS `producer_id`, + `i`.`subName` AS `tag1`, + `i`.`value5` AS `tag2`, + `i`.`value6` AS `tag3`, + `i`.`value7` AS `tag4`, + `i`.`value8` AS `tag5`, + `i`.`value9` AS `tag6`, + `s`.`company_name` AS `company_name`, + `b`.`weight` AS `weightPacking`, + `i`.`packingOut` AS `packingOut`, + `b`.`itemOriginalFk` AS `itemOriginalFk`, + `io`.`longName` AS `itemOriginalName`, + `it`.`gramsMax` AS `gramsMax` +FROM ( + ( + ( + ( + ( + ( + `vn`.`item` `i` + JOIN `vn`.`itemType` `it` ON(`it`.`id` = `i`.`typeFk`) + ) + LEFT JOIN `vn`.`ink` ON(`vn`.`ink`.`id` = `i`.`inkFk`) + ) + LEFT JOIN `vn`.`buy` `b` ON(`b`.`itemFk` = `i`.`id`) + ) + LEFT JOIN `vn`.`item` `io` ON(`io`.`id` = `b`.`itemOriginalFk`) + ) + LEFT JOIN `edi`.`ekt` `e` ON(`e`.`id` = `b`.`ektFk`) + ) + LEFT JOIN `edi`.`supplier` `s` ON(`e`.`pro` = `s`.`supplier_id`) + ); + +CREATE OR REPLACE DEFINER=`root`@`localhost` + SQL SECURITY DEFINER + VIEW `vn`.`awbVolume` +AS SELECT `d`.`awbFk` AS `awbFk`, + `b`.`stickers` * `i`.`density` * IF( + `p`.`volume` > 0, + `p`.`volume`, + `p`.`width` * `p`.`depth` * IF(`p`.`height` = 0, `i`.`size` + 10, `p`.`height`) + ) / (`vc`.`aerealVolumetricDensity` * 1000) AS `volume`, + `b`.`id` AS `buyFk` +FROM ( + ( + ( + ( + ( + ( + ( + ( + `vn`.`buy` `b` + JOIN `vn`.`item` `i` ON(`b`.`itemFk` = `i`.`id`) + ) + JOIN `vn`.`itemType` `it` ON(`i`.`typeFk` = `it`.`id`) + ) + JOIN `vn`.`packaging` `p` ON(`p`.`id` = `b`.`packagingFk`) + ) + JOIN `vn`.`entry` `e` ON(`b`.`entryFk` = `e`.`id`) + ) + JOIN `vn`.`travel` `t` ON(`t`.`id` = `e`.`travelFk`) + ) + JOIN `vn`.`duaEntry` `de` ON(`de`.`entryFk` = `e`.`id`) + ) + JOIN `vn`.`dua` `d` ON(`d`.`id` = `de`.`duaFk`) + ) + JOIN `vn`.`volumeConfig` `vc` + ) +WHERE `t`.`shipped` > makedate(year(`util`.`VN_CURDATE`()) - 1, 1); \ No newline at end of file diff --git a/db/changes/234201/00-zoneIncluded.sql b/db/changes/234201/00-zoneIncluded.sql new file mode 100644 index 000000000..12d4058cf --- /dev/null +++ b/db/changes/234201/00-zoneIncluded.sql @@ -0,0 +1,26 @@ +ALTER TABLE `vn`.`zoneIncluded` + ADD COLUMN `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT FIRST, + DROP PRIMARY KEY, + DROP FOREIGN KEY `zoneFk2`, + DROP FOREIGN KEY `zoneGeoFk2`, + DROP KEY `geoFk_idx`, + ADD PRIMARY KEY (`id`), + ADD CONSTRAINT `zoneIncluded_FK_1` FOREIGN KEY (zoneFk) REFERENCES `vn`.`zone`(id) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `zoneIncluded_FK_2` FOREIGN KEY (geoFk) REFERENCES `vn`.`zoneGeo`(id) ON DELETE CASCADE ON UPDATE CASCADE, + ADD CONSTRAINT `unique_zone_geo` UNIQUE (`zoneFk`, `geoFk`); + +DROP TRIGGER IF EXISTS `vn`.`zoneIncluded_afterDelete`; +USE `vn`; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`zoneIncluded_afterDelete` + AFTER DELETE ON `zoneIncluded` + FOR EACH ROW +BEGIN + INSERT INTO zoneLog + SET `action` = 'delete', + `changedModel` = 'zoneIncluded', + `changedModelId` = OLD.zoneFk, + `userFk` = account.myUser_getId(); +END$$ +DELIMITER ; diff --git a/db/changes/234201/02-packagingFktrigger.sql b/db/changes/234201/02-packagingFktrigger.sql new file mode 100644 index 000000000..4edcd3f3e --- /dev/null +++ b/db/changes/234201/02-packagingFktrigger.sql @@ -0,0 +1,57 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`buy_afterUpdate` + AFTER UPDATE ON `buy` + FOR EACH ROW +trig: BEGIN + DECLARE vLanded DATE; + DECLARE vBuyerFk INT; + DECLARE vIsBuyerToBeEmailed BOOL; + DECLARE vItemName VARCHAR(50); + + IF @isModeInventory OR @isTriggerDisabled THEN + LEAVE trig; + END IF; + + IF !(NEW.id <=> OLD.id) + OR !(NEW.entryFk <=> OLD.entryFk) + OR !(NEW.itemFk <=> OLD.itemFk) + OR !(NEW.quantity <=> OLD.quantity) + OR !(NEW.created <=> OLD.created) THEN + CALL stock.log_add('buy', NEW.id, OLD.id); + END IF; + + CALL buy_afterUpsert(NEW.id); + + SELECT w.isBuyerToBeEmailed, t.landed + INTO vIsBuyerToBeEmailed, vLanded + FROM entry e + JOIN travel t ON t.id = e.travelFk + JOIN warehouse w ON w.id = t.warehouseInFk + WHERE e.id = NEW.entryFk; + + SELECT it.workerFk, i.longName + INTO vBuyerFk, vItemName + FROM itemCategory k + JOIN itemType it ON it.categoryFk = k.id + JOIN item i ON i.typeFk = it.id + WHERE i.id = OLD.itemFk; + + IF vIsBuyerToBeEmailed AND + vBuyerFk != account.myUser_getId() AND + vLanded = util.VN_CURDATE() THEN + IF !(NEW.itemFk <=> OLD.itemFk) OR + !(NEW.quantity <=> OLD.quantity) OR + !(NEW.packing <=> OLD.packing) OR + !(NEW.grouping <=> OLD.grouping) OR + !(NEW.packagingFk <=> OLD.packagingFk) OR + !(NEW.weight <=> OLD.weight) THEN + CALL vn.mail_insert( + CONCAT(account.user_getNameFromId(vBuyerFk),'@verdnatura.es'), + CONCAT(account.myUser_getName(),'@verdnatura.es'), + CONCAT('E ', NEW.entryFk ,' Se ha modificado item ', NEW.itemFk, ' ', vItemName), + 'Este email se ha generado automáticamente' + ); + END IF; + END IF; +END$$ +DELIMITER ; \ No newline at end of file diff --git a/db/changes/234201/03-packagingFkProc.sql b/db/changes/234201/03-packagingFkProc.sql new file mode 100644 index 000000000..4876c270e --- /dev/null +++ b/db/changes/234201/03-packagingFkProc.sql @@ -0,0 +1,1381 @@ +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`travelVolume`(vTravelFk INT) +BEGIN + + SELECT w1.name AS ORI, + w2.name AS DES, + tr.shipped shipment, + tr.landed landing, + a.name Agencia, + s.name Proveedor, + e.id Id_Entrada, + e.invoiceNumber Referencia, + CAST(ROUND(SUM(GREATEST(b.stickers ,b.quantity /b.packing ) * + vn.item_getVolume(b.itemFk ,b.packagingFk)) / vc.trolleyM3 / 1000000 ,1) AS DECIMAL(10,2)) AS CC, + CAST(ROUND(SUM(GREATEST(b.stickers ,b.quantity /b.packing ) * + vn.item_getVolume(b.itemFk ,b.packagingFk)) / vc.palletM3 / 1000000,1) AS DECIMAL(10,2)) AS espais + FROM vn.buy b + JOIN vn.entry e ON e.id = b.entryFk + JOIN vn.supplier s ON s.id = e.supplierFk + JOIN vn.travel tr ON tr.id = e.travelFk + JOIN vn.agencyMode a ON a.id = tr.agencyModeFk + JOIN vn.warehouse w1 ON w1.id = tr.warehouseInFk + JOIN vn.warehouse w2 ON w2.id = tr.warehouseOutFk + JOIN vn.volumeConfig vc + JOIN vn.item i ON i.id = b.itemFk + JOIN vn.itemType it ON it.id = i.typeFk + WHERE tr.id = vTravelFk; + +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`travelVolume_get`(vFromDated DATE, vToDated DATE, vWarehouseFk INT) +BEGIN + SELECT tr.landed Fecha, + a.name Agencia, + count(DISTINCT e.id) numEntradas, + FLOOR(sum(item_getVolume(b.itemFk, b.packagingFk) * b.stickers / 1000000 )) AS m3 + FROM vn.travel tr + JOIN vn.agencyMode a ON a.id = tr.agencyModeFk + JOIN vn.entry e ON e.travelFk = tr.id + JOIN vn.buy b ON b.entryFk = e.id + WHERE tr.landed BETWEEN vFromDated AND vToDated + AND e.isRaid = FALSE + AND tr.warehouseInFk = vWarehouseFk + GROUP BY tr.landed , a.name ; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`travel_getEntriesMissingPackage`(vSelf INT) +BEGIN + DECLARE vpackageOrPackingNull INT; + DECLARE vTravelFk INT; + + SELECT travelfk INTO vTravelFk + FROM entry + WHERE id = vSelf; + + SELECT e.id entryFk + FROM travel t + JOIN entry e ON e.travelFk = t.id + JOIN buy b ON b.entryFk = e.id + WHERE t.id = vTravelFk + AND (b.packing IS NULL OR b.packagingFk IS NULL); +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticketBoxesView`(IN vTicketFk INT) +BEGIN + + SELECT s.id, + s.itemFk, + s.concept, + floor(s.quantity / b.packing) as Cajas, + b.packing, + s.isPicked, + i.size + FROM ticket t + JOIN sale s ON s.ticketFk = t.id + JOIN item i ON i.id = s.itemFk + JOIN cache.last_buy lb on lb.warehouse_id = t.warehouseFk AND lb.item_id = s.itemFk + JOIN buy b on b.id = lb.buy_id + JOIN packaging p on p.id = b.packagingFk + WHERE s.quantity >= b.packing + AND t.id = vTicketFk + AND p.isBox + GROUP BY s.itemFk; + + +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`stockBuyedByWorker`( + vDated DATE, + vWorker INT +) +BEGIN +/** + * Inserta el volumen de compra de un comprador + * en stockBuyed de acuerdo con la fecha. + * + * @param vDated Fecha de compra + * @param vWorker Id de trabajador + */ + CREATE OR REPLACE TEMPORARY TABLE tStockBuyed + (INDEX (userFk)) + ENGINE = MEMORY + SELECT requested, reserved, userFk + FROM stockBuyed + WHERE dated = vDated + AND userFk = vWorker; + + DELETE FROM stockBuyed + WHERE dated = vDated + AND userFk = vWorker; + + CALL stockTraslation(vDated); + + INSERT INTO stockBuyed(userFk, buyed, `dated`, reserved, requested, description) + SELECT it.workerFk, + SUM((ti.quantity / b.packing) * buy_getVolume(b.id)) / vc.palletM3 / 1000000, + vDated, + sb.reserved, + sb.requested, + u.name + FROM itemType it + JOIN item i ON i.typeFk = it.id + LEFT JOIN tmp.item ti ON ti.itemFk = i.id + JOIN itemCategory ic ON ic.id = it.categoryFk + JOIN warehouse wh ON wh.code = 'VNH' + JOIN tmp.buyUltimate bu ON bu.itemFk = i.id + AND bu.warehouseFk = wh.id + JOIN buy b ON b.id = bu.buyFk + JOIN volumeConfig vc + JOIN account.`user` u ON u.id = it.workerFk + LEFT JOIN tStockBuyed sb ON sb.userFk = it.workerFk + WHERE ic.display + AND it.workerFk = vWorker; + + SELECT b.entryFk Id_Entrada, + i.id Id_Article, + i.name Article, + ti.quantity Cantidad, + (ac.conversionCoefficient * (ti.quantity / b.packing) * buy_getVolume(b.id)) + / (vc.trolleyM3 * 1000000) buyed, + b.packagingFk id_cubo, + b.packing + FROM tmp.item ti + JOIN item i ON i.id = ti.itemFk + JOIN itemType it ON i.typeFk = it.id + JOIN itemCategory ic ON ic.id = it.categoryFk + JOIN worker w ON w.id = it.workerFk + JOIN auctionConfig ac + JOIN tmp.buyUltimate bu ON bu.itemFk = i.id + AND bu.warehouseFk = ac.warehouseFk + JOIN buy b ON b.id = bu.buyFk + JOIN volumeConfig vc + WHERE ic.display + AND w.id = vWorker; + + DROP TEMPORARY TABLE tmp.buyUltimate, + tmp.item, + tStockBuyed; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`itemShelvingMakeFromDate`(IN `vShelvingFk` VARCHAR(8), IN `vBarcode` VARCHAR(22), IN `vQuantity` INT, IN `vPackagingFk` VARCHAR(10), IN `vGrouping` INT, IN `vPacking` INT, IN `vWarehouseFk` INT, `vCreated` VARCHAR(22)) +BEGIN + + DECLARE vItemFk INT; + + SELECT vn.barcodeToItem(vBarcode) INTO vItemFk; + + SELECT itemFk INTO vItemFk + FROM vn.buy b + WHERE b.id = vItemFk; + + IF (SELECT COUNT(*) FROM vn.shelving WHERE code = vShelvingFk COLLATE utf8_unicode_ci) = 0 THEN + + INSERT IGNORE INTO vn.parking(`code`) VALUES(vShelvingFk); + INSERT INTO vn.shelving(`code`, parkingFk) + SELECT vShelvingFk, id + FROM vn.parking + WHERE `code` = vShelvingFk COLLATE utf8_unicode_ci; + + END IF; + + IF (SELECT COUNT(*) FROM vn.itemShelving + WHERE shelvingFk COLLATE utf8_unicode_ci = vShelvingFk + AND itemFk = vItemFk + AND packing = vPacking) = 1 THEN + + UPDATE vn.itemShelving + SET visible = visible+vQuantity, + created = vCreated + WHERE shelvingFk COLLATE utf8_unicode_ci = vShelvingFk + AND itemFk = vItemFk + AND packing = vPacking; + + ELSE + CALL cache.last_buy_refresh(FALSE); + INSERT INTO itemShelving( itemFk, + shelvingFk, + visible, + created, + `grouping`, + packing, + packagingFk) + SELECT vItemFk, + vShelvingFk, + vQuantity, + vCreated, + IF(vGrouping = 0, IFNULL(b.packing, vPacking), vGrouping) `grouping`, + IF(vPacking = 0, b.packing, vPacking) packing, + IF(vPackagingFk = '', b.packagingFk, vPackagingFk) packaging + FROM vn.item i + LEFT JOIN cache.last_buy lb ON i.id = lb.item_id AND lb.warehouse_id = vWarehouseFk + LEFT JOIN vn.buy b ON b.id = lb.buy_id + WHERE i.id = vItemFk; + END IF; + +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`itemShelving_add`(IN vShelvingFk VARCHAR(8), IN vBarcode VARCHAR(22), IN vQuantity INT, IN vPackagingFk VARCHAR(10), IN vGrouping INT, IN vPacking INT, IN vWarehouseFk INT) +BEGIN + + +/** + * Añade registro o lo actualiza si ya existe. + * + * @param vShelvingFk matrícula del carro + * @param vBarcode el id del registro + * @param vQuantity indica la cantidad del producto + * @param vPackagingFk el packaging del producto en itemShelving, NULL para coger el de la ultima compra + * @param vGrouping el grouping del producto en itemShelving, NULL para coger el de la ultima compra + * @param vPacking el packing del producto, NULL para coger el de la ultima compra + * @param vWarehouseFk indica el sector + * + **/ + + DECLARE vItemFk INT; + + SELECT barcodeToItem(vBarcode) INTO vItemFk; + + IF (SELECT COUNT(*) FROM shelving WHERE code = vShelvingFk COLLATE utf8_unicode_ci) = 0 THEN + + INSERT IGNORE INTO parking(code) VALUES(vShelvingFk); + INSERT INTO shelving(code, parkingFk) + SELECT vShelvingFk, id + FROM parking + WHERE `code` = vShelvingFk COLLATE utf8_unicode_ci; + + END IF; + + IF (SELECT COUNT(*) FROM itemShelving + WHERE shelvingFk COLLATE utf8_unicode_ci = vShelvingFk + AND itemFk = vItemFk + AND packing = vPacking) = 1 THEN + + UPDATE itemShelving + SET visible = visible+vQuantity + WHERE shelvingFk COLLATE utf8_unicode_ci = vShelvingFk AND itemFk = vItemFk AND packing = vPacking; + + ELSE + CALL cache.last_buy_refresh(FALSE); + INSERT INTO itemShelving( itemFk, + shelvingFk, + visible, + grouping, + packing, + packagingFk) + + SELECT vItemFk, + vShelvingFk, + vQuantity, + IFNULL(vGrouping, b.grouping), + IFNULL(vPacking, b.packing), + IFNULL(vPackagingFk, b.packagingFk) + FROM item i + LEFT JOIN cache.last_buy lb ON i.id = lb.item_id AND lb.warehouse_id = vWarehouseFk + LEFT JOIN buy b ON b.id = lb.buy_id + WHERE i.id = vItemFk; + END IF; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`itemFreight_Show`(vItemFk INT, vWarehouseFk INT) +BEGIN + + SELECT cm3 Volumen_Entrada, + cm3delivery Volumen_Salida, + p.volume Volumen_del_embalaje, + p.width Ancho_del_embalaje, + p.`depth` Largo_del_embalaje, + b.packagingFk , + IFNULL(p.height, i.`size`) + 10 Altura, + b.packing Packing_Entrada, + i.packingOut Packing_Salida, + i.id itemFk, + b.id buyFk, + b.entryFk, + w.name warehouseFk + FROM vn.itemCost ic + JOIN vn.item i ON i.id = ic.itemFk + LEFT JOIN cache.last_buy lb ON lb.item_id = ic.itemFk AND lb.warehouse_id = ic.warehouseFk + LEFT JOIN vn.buy b ON b.id = lb.buy_id + LEFT JOIN vn.packaging p ON p.id = b.packagingFk + LEFT JOIN vn.warehouse w ON w.id = ic.warehouseFk + WHERE ic.itemFk = vItemFk + AND ic.warehouseFk = vWarehouseFk; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`inventoryMake`(vDate DATE, vWh INT) +proc: BEGIN +/** +* Recalcula los inventarios de todos los almacenes, si vWh = 0 +* +* @param vDate Fecha de los nuevos inventarios +* @param vWh almacen al cual hacer el inventario +*/ + + DECLARE vDone BOOL; + DECLARE vEntryFk INT; + DECLARE vTravelFk INT; + DECLARE vDateLastInventory DATE; + DECLARE vDateYesterday DATETIME DEFAULT vDate - INTERVAL 1 SECOND; + DECLARE vWarehouseOutFkInventory INT; + DECLARE vInventorySupplierFk INT; + DECLARE vAgencyModeFkInventory INT; + + DECLARE cWarehouses CURSOR FOR + SELECT id + FROM warehouse + WHERE isInventory + AND vWh IN (0,id); + + DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE; + + OPEN cWarehouses; + SET @isModeInventory := TRUE; + l: LOOP + + SET vDone = FALSE; + FETCH cWarehouses INTO vWh; + + IF vDone THEN + LEAVE l; + END IF; + + SELECT w.id INTO vWarehouseOutFkInventory + FROM warehouse w + WHERE w.code = 'inv'; + + SELECT inventorySupplierFk INTO vInventorySupplierFk + FROM entryConfig; + + SELECT am.id INTO vAgencyModeFkInventory + FROM agencyMode am + where code = 'inv'; + + SELECT MAX(landed) INTO vDateLastInventory + FROM travel tr + JOIN entry e ON e.travelFk = tr.id + JOIN buy b ON b.entryFk = e.id + WHERE warehouseOutFk = vWarehouseOutFkInventory + AND landed < vDate + AND e.supplierFk = vInventorySupplierFk + AND warehouseInFk = vWh + AND NOT isRaid; + + IF vDateLastInventory IS NULL THEN + SELECT inventoried INTO vDateLastInventory FROM config; + END IF; + + -- Generamos travel, si no existe. + SET vTravelFK = 0; + + SELECT id INTO vTravelFk + FROM travel + WHERE warehouseOutFk = vWarehouseOutFkInventory + AND warehouseInFk = vWh + AND landed = vDate + AND agencyModeFk = vAgencyModeFkInventory + AND ref = 'inventario' + LIMIT 1; + + IF NOT vTravelFK THEN + + INSERT INTO travel SET + warehouseOutFk = vWarehouseOutFkInventory, + warehouseInFk = vWh, + shipped = vDate, + landed = vDate, + agencyModeFk = vAgencyModeFkInventory, + ref = 'inventario', + isDelivered = TRUE, + isReceived = TRUE; + + SELECT LAST_INSERT_ID() INTO vTravelFk; + + END IF; + + -- Generamos entrada si no existe, o la vaciamos. + SET vEntryFk = 0; + + SELECT id INTO vEntryFk + FROM entry + WHERE supplierFk = vInventorySupplierFk + AND travelFk = vTravelFk; + + IF NOT vEntryFk THEN + + INSERT INTO entry SET + supplierFk = vInventorySupplierFk, + isConfirmed = TRUE, + isOrdered = TRUE, + travelFk = vTravelFk; + + SELECT LAST_INSERT_ID() INTO vEntryFk; + + ELSE + + DELETE FROM buy WHERE entryFk = vEntryFk; + + END IF; + + -- Preparamos tabla auxilar + CREATE OR REPLACE TEMPORARY TABLE tmp.inventory ( + itemFk INT(11) NOT NULL PRIMARY KEY, + quantity int(11) DEFAULT '0', + buyingValue decimal(10,3) DEFAULT '0.000', + freightValue decimal(10,3) DEFAULT '0.000', + packing int(11) DEFAULT '0', + `grouping` smallint(5) unsigned NOT NULL DEFAULT '1', + groupingMode tinyint(4) NOT NULL DEFAULT 0 , + comissionValue decimal(10,3) DEFAULT '0.000', + packageValue decimal(10,3) DEFAULT '0.000', + packageFk varchar(10) COLLATE utf8_unicode_ci DEFAULT '--', + price1 decimal(10,2) DEFAULT '0.00', + price2 decimal(10,2) DEFAULT '0.00', + price3 decimal(10,2) DEFAULT '0.00', + minPrice decimal(10,2) DEFAULT '0.00', + producer varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL, + INDEX (itemFK)) ENGINE = MEMORY; + + -- Compras + INSERT INTO tmp.inventory(itemFk,quantity) + SELECT b.itemFk, SUM(b.quantity) + FROM buy b + JOIN entry e ON e.id = b.entryFk + JOIN travel tr ON tr.id = e.travelFk + WHERE tr.warehouseInFk = vWh + AND tr.landed BETWEEN vDateLastInventory + AND vDateYesterday + AND NOT isRaid + GROUP BY b.itemFk; + SELECT vDateLastInventory , vDateYesterday; + + -- Traslados + INSERT INTO tmp.inventory(itemFk, quantity) + SELECT itemFk, quantityOut + FROM ( + SELECT b.itemFk,- SUM(b.quantity) quantityOut + FROM buy b + JOIN entry e ON e.id = b.entryFk + JOIN travel tr ON tr.id = e.travelFk + WHERE tr.warehouseOutFk = vWh + AND tr.shipped BETWEEN vDateLastInventory + AND vDateYesterday + AND NOT isRaid + GROUP BY b.itemFk + ) sub + ON DUPLICATE KEY UPDATE quantity = IFNULL(quantity, 0) + sub.quantityOut; + + -- Ventas + INSERT INTO tmp.inventory(itemFk,quantity) + SELECT itemFk, saleOut + FROM ( + SELECT s.itemFk, - SUM(s.quantity) saleOut + FROM sale s + JOIN ticket t ON t.id = s.ticketFk + WHERE t.warehouseFk = vWh + AND t.shipped BETWEEN vDateLastInventory AND vDateYesterday + GROUP BY s.itemFk + ) sub + ON DUPLICATE KEY UPDATE quantity = IFNULL(quantity,0) + sub.saleOut; + + -- Actualiza valores de la ultima compra + UPDATE tmp.inventory inv + JOIN cache.last_buy lb ON lb.item_id = inv.itemFk AND lb.warehouse_id = vWh + JOIN buy b ON b.id = lb.buy_id + JOIN item i ON i.id = b.itemFk + LEFT JOIN producer p ON p.id = i.producerFk + SET inv.buyingValue = b.buyingValue, + inv.freightValue = b.freightValue, + inv.packing = b.packing, + inv.`grouping`= b.`grouping`, + inv.groupingMode = b.groupingMode, + inv.comissionValue = b.comissionValue, + inv.packageValue = b.packageValue, + inv.packageFk = b.packagingFk, + inv.price1 = b.price1, + inv.price2 = b.price2, + inv.price3 = b.price3, + inv.minPrice = b.minPrice, + inv.producer = p.name; + + INSERT INTO buy( itemFk, + quantity, + buyingValue, + freightValue, + packing, + `grouping`, + groupingMode, + comissionValue, + packageValue, + packagingFk, + price1, + price2, + price3, + minPrice, + entryFk) + SELECT itemFk, + GREATEST(quantity, 0), + buyingValue, + freightValue, + packing, + `grouping`, + groupingMode, + comissionValue, + packageValue, + packagingFk, + price1, + price2, + price3, + minPrice, + vEntryFk + FROM tmp.inventory; + + SELECT vWh, COUNT(*), util.VN_NOW() FROM tmp.inventory; + + -- Actualizamos el campo lastUsed de item + UPDATE item i + JOIN tmp.inventory i2 ON i2.itemFk = i.id + SET i.lastUsed = NOW() + WHERE i2.quantity; + + -- DROP TEMPORARY TABLE tmp.inventory; + + END LOOP; + + CLOSE cWarehouses; + + UPDATE config SET inventoried = vDate; + SET @isModeInventory := FALSE; + + DROP TEMPORARY TABLE IF EXISTS tmp.entryToDelete; + CREATE TEMPORARY TABLE tmp.entryToDelete + (INDEX(entryId) USING BTREE) ENGINE = MEMORY + SELECT e.id as entryId, + t.id as travelId + FROM travel t + JOIN `entry` e ON e.travelFk = t.id + WHERE e.supplierFk = vInventorySupplierFk + AND t.shipped <= util.VN_CURDATE() - INTERVAL 12 DAY + AND (DAY(t.shipped) <> 1 OR shipped < util.VN_CURDATE() - INTERVAL 12 DAY); + + DELETE e + FROM `entry` e + JOIN tmp.entryToDelete tmp ON tmp.entryId = e.id; + + DELETE IGNORE t + FROM travel t + JOIN tmp.entryToDelete tmp ON tmp.travelId = t.id; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`inventory_repair`() +BEGIN + + DROP TEMPORARY TABLE IF EXISTS tmp.lastEntry; + CREATE TEMPORARY TABLE tmp.lastEntry + (PRIMARY KEY (buyFk)) + SELECT + i.id AS itemFk, + w.id AS warehouseFk, + w.name AS warehouse, + tr.landed, + b.id AS buyFk, + b.entryFk, + b.isIgnored, + b.price2, + b.price3, + b.stickers, + b.packing, + b.grouping, + b.groupingMode, + b.weight, + i.stems, + b.quantity, + b.buyingValue, + b.packagingFk , + s.id AS supplierFk, + s.name AS supplier + FROM itemType it + RIGHT JOIN (entry e + LEFT JOIN supplier s ON s.id = e.supplierFk + RIGHT JOIN buy b ON b.entryFk = e.id + LEFT JOIN item i ON i.id = b.itemFk + LEFT JOIN ink ON ink.id = i.inkFk + LEFT JOIN travel tr ON tr.id = e.travelFk + LEFT JOIN warehouse w ON w.id = tr.warehouseInFk + LEFT JOIN origin o ON o.id = i.originFk + ) ON it.id = i.typeFk + LEFT JOIN edi.ekt ek ON b.ektFk = ek.id + WHERE (b.packagingFk = "--" OR b.price2 = 0 OR b.packing = 0 OR b.buyingValue = 0) AND tr.landed > util.firstDayOfMonth(TIMESTAMPADD(MONTH,-1,util.VN_CURDATE())) AND s.name = 'INVENTARIO'; + + DROP TEMPORARY TABLE IF EXISTS tmp.lastEntryOk; + CREATE TEMPORARY TABLE tmp.lastEntryOk + (PRIMARY KEY (buyFk)) + SELECT + i.id AS itemFk, + w.id AS warehouseFk, + w.name AS warehouse, + tr.landed, + b.id AS buyFk, + b.entryFk, + b.isIgnored, + b.price2, + b.price3, + b.stickers, + b.packing, + b.grouping, + b.groupingMode, + b.weight, + i.stems, + b.quantity, + b.buyingValue, + b.packagingFk, + s.id AS supplierFk, + s.name AS supplier + FROM itemType it + RIGHT JOIN (entry e + LEFT JOIN supplier s ON s.id = e.supplierFk + RIGHT JOIN buy b ON b.entryFk = e.id + LEFT JOIN item i ON i.id = b.itemFk + LEFT JOIN ink ON ink.id = i.inkFk + LEFT JOIN travel tr ON tr.id = e.travelFk + LEFT JOIN warehouse w ON w.id = tr.warehouseInFk + LEFT JOIN origin o ON o.id = i.originFk + ) ON it.id = i.typeFk + LEFT JOIN edi.ekt ek ON b.ektFk = ek.id + WHERE b.packagingFk != "--" AND b.price2 != 0 AND b.packing != 0 AND b.buyingValue > 0 AND tr.landed > util.firstDayOfMonth(TIMESTAMPADD(MONTH,-2,util.VN_CURDATE())) + ORDER BY tr.landed DESC; + + DROP TEMPORARY TABLE IF EXISTS tmp.lastEntryOkGroup; + CREATE TEMPORARY TABLE tmp.lastEntryOkGroup + (INDEX (warehouseFk,itemFk)) + SELECT * + FROM tmp.lastEntryOk tmp + GROUP BY tmp.itemFk,tmp.warehouseFk; + + UPDATE buy b + JOIN tmp.lastEntry lt ON lt.buyFk = b.id + JOIN tmp.lastEntryOkGroup eo ON eo.itemFk = lt.itemFk AND eo.warehouseFk = lt.warehouseFk + SET b.packagingFk = eo.packagingFk WHERE b.packagingFk = "--"; + + UPDATE buy b + JOIN tmp.lastEntry lt ON lt.buyFk = b.id + JOIN tmp.lastEntryOkGroup eo ON eo.itemFk = lt.itemFk AND eo.warehouseFk = lt.warehouseFk + SET b.price2 = eo.price2 WHERE b.price2 = 0 ; + + UPDATE buy b + JOIN tmp.lastEntry lt ON lt.buyFk = b.id + JOIN tmp.lastEntryOkGroup eo ON eo.itemFk = lt.itemFk AND eo.warehouseFk = lt.warehouseFk + SET b.packing = eo.packing WHERE b.packing = 0; + + UPDATE buy b + JOIN tmp.lastEntry lt ON lt.buyFk = b.id + JOIN tmp.lastEntryOkGroup eo ON eo.itemFk = lt.itemFk AND eo.warehouseFk = lt.warehouseFk + SET b.buyingValue = eo.buyingValue WHERE b.buyingValue = 0; + + DROP TEMPORARY TABLE tmp.lastEntry; + DROP TEMPORARY TABLE tmp.lastEntryOk; + DROP TEMPORARY TABLE tmp.lastEntryOkGroup; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`buy_afterUpsert`(vSelf INT) +BEGIN +/** + * Triggered actions when a buy is updated or inserted. + * + * @param vSelf The buy reference + */ + DECLARE vEntryFk INT; + DECLARE vItemFk INT; + DECLARE vPackingOut DECIMAL(10,2); + DECLARE vWarehouse INT; + DECLARE vStandardFlowerBox INT; + DECLARE vWarehouseOut INT; + DECLARE vIsMerchandise BOOL; + DECLARE vIsFeedStock BOOL; + DECLARE vWeight DECIMAL(10,2); + DECLARE vPacking INT; + + SELECT b.entryFk, + b.itemFk, + i.packingOut, + ic.merchandise, + vc.standardFlowerBox, + b.weight, + b.packing + INTO + vEntryFk, + vItemFk, + vPackingOut, + vIsMerchandise, + vStandardFlowerBox, + vWeight, + vPacking + FROM buy b + LEFT JOIN item i ON i.id = b.itemFk + LEFT JOIN itemType it ON it.id = i.typeFk + LEFT JOIN itemCategory ic ON ic.id = it.categoryFk + LEFT JOIN packaging p ON p.id = b.packagingFk AND NOT p.isBox + JOIN volumeConfig vc ON TRUE + WHERE b.id = vSelf; + + SELECT t.warehouseInFk, t.warehouseOutFk + INTO vWarehouse, vWarehouseOut + FROM entry e + JOIN travel t ON t.id = e.travelFk + WHERE e.id = vEntryFk; + + IF vIsMerchandise THEN + + REPLACE itemCost SET + itemFk = vItemFk, + warehouseFk = vWarehouse, + cm3 = buy_getUnitVolume(vSelf), + cm3Delivery = IFNULL((vStandardFlowerBox * 1000) / vPackingOut, buy_getUnitVolume(vSelf)); + + IF vWeight AND vPacking THEN + UPDATE itemCost SET + grams = vWeight * 1000 / vPacking + WHERE itemFk = vItemFk + AND warehouseFk = vWarehouse; + END IF; + END IF; + + SELECT isFeedStock INTO vIsFeedStock + FROM warehouse WHERE id = vWarehouseOut; + + IF vIsFeedStock THEN + INSERT IGNORE INTO producer(`name`) + SELECT es.company_name + FROM buy b + JOIN edi.ekt be ON be.id = b.ektFk + JOIN edi.supplier es ON es.supplier_id = be.pro + WHERE b.id = vSelf; + + END IF; + +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` FUNCTION `vn`.`buy_getUnitVolume`(vSelf INT) + RETURNS int(11) + DETERMINISTIC +BEGIN +/** + * Calculates the unit volume occupied by a buy. + * + * @param vSelf The buy id + * @return The unit volume in cubic centimeters + */ + DECLARE vItem INT; + DECLARE vPackaging VARCHAR(10); + DECLARE vPacking INT; + + SELECT itemFk, packagingFk, packing + INTO vItem, vPackaging, vPacking + FROM buy + WHERE id = vSelf; + + RETURN IFNULL(ROUND(item_getVolume(vItem, vPackaging) / vPacking), 0); +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`buy_recalcPrices`() +BEGIN +/** + * Recalcula los precios para las compras insertadas en tmp.buyRecalc + * + * @param tmp.buyRecalc (id) + */ + DECLARE vLanded DATE; + DECLARE vWarehouseFk INT; + DECLARE vHasNotPrice BOOL; + DECLARE vBuyingValue DECIMAL(10,3); + DECLARE vPackagingFk VARCHAR(10); + DECLARE vIsWarehouseFloramondo BOOL; + + SELECT t.landed, t.warehouseInFk, (w.`name` = 'Floramondo') + INTO vLanded, vWarehouseFk, vIsWarehouseFloramondo + FROM tmp.buyRecalc br + JOIN buy b ON b.id = br.id + JOIN entry e ON e.id = b.entryFk + JOIN travel t ON t.id = e.travelFk + JOIN warehouse w ON w.id = t.warehouseInFk + LIMIT 1; + + CALL rate_getPrices(vLanded, vWarehouseFk); + + UPDATE buy b + JOIN tmp.buyRecalc br ON br.id = b.id AND (@buyId := b.id) + LEFT JOIN packaging p ON p.id = b.packagingFk + JOIN item i ON i.id = b.itemFk + JOIN entry e ON e.id = b.entryFk + JOIN itemType it ON it.id = i.typeFk + JOIN travel tr ON tr.id = e.travelFk + JOIN agencyMode am ON am.id = tr.agencyModeFk + JOIN tmp.rate r + JOIN volumeConfig vc + SET b.freightValue = @PF:= IFNULL(((am.m3 * @m3:= item_getVolume(b.itemFk, b.packagingFk) / 1000000) + / b.packing) * IF(am.hasWeightVolumetric, GREATEST(b.weight / @m3 / vc.aerealVolumetricDensity, 1), 1), 0), + b.comissionValue = @CF:= ROUND(IFNULL(e.commission * b.buyingValue / 100, 0), 3), + b.packageValue = @EF:= IF(vIsWarehouseFloramondo, 0, IFNULL(ROUND(IF(p.isPackageReturnable, p.returnCost / b.packing , p.`value` / b.packing), 3),0)), + b.price3 = @t3:= IF(r.rate3 = 0, b.buyingValue,ROUND((b.buyingValue + @CF + @EF + @PF) / ((100 - r.rate3 - it.promo ) /100) ,2)), -- He añadido que el coste sea igual a tarifa3 si t3 = 0 + b.price2 = @t2:= round(@t3 * (1 + ((r.rate2 - r.rate3)/100)),2), + b.price2 = @t2:= IF(@t2 <= @t3,@t3 , @t2); + + SELECT (b.buyingValue = b.price2), b.buyingValue, b.packagingFk + INTO vHasNotPrice, vBuyingValue, vPackagingFk + FROM vn.buy b + WHERE b.id = @buyId AND b.buyingValue <> 0.01; + + DROP TEMPORARY TABLE tmp.rate; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `edi`.`ekt_load`(IN `vSelf` INT) +proc:BEGIN + + DECLARE vRef INT; + DECLARE vBuy INT; + DECLARE vItem INT; + DECLARE vQty INT; + DECLARE vPackage INT; + DECLARE vPutOrderFk INT; + DECLARE vIsLot BOOLEAN; + DECLARE vForceToPacking INT DEFAULT 2; + DECLARE vEntryFk INT; + DECLARE vHasToChangePackagingFk BOOLEAN; + DECLARE vIsFloramondoDirect BOOLEAN; + DECLARE vTicketFk INT; + DECLARE vHasItemGroup BOOL; + DECLARE vDescription VARCHAR(255); + DECLARE vSaleFk INT; + + -- Carga los datos necesarios del EKT + + SELECT e.ref, qty, package, putOrderFk MOD 1000000, i2.id , NOT ISNULL(eea.addressFk), NOT ISNULL(igto.group_code), + CONCAT(e.`ref`, ' ', e.item, ' ', e.sub, ' EktFk:', e.id) + INTO vRef, vQty, vPackage, vPutOrderFk, vItem, vIsFloramondoDirect, vHasItemGroup, vDescription + FROM edi.ekt e + LEFT JOIN edi.item i ON e.ref = i.id + LEFT JOIN edi.putOrder po ON po.id = e.putOrderFk + LEFT JOIN vn.item i2 ON i2.supplyResponseFk = po.supplyResponseID + LEFT JOIN vn.ektEntryAssign eea ON eea.sub = e.sub + LEFT JOIN edi.item_groupToOffer igto ON igto.group_code = i.group_id + WHERE e.id = vSelf + LIMIT 1; + + IF NOT vHasItemGroup THEN + + CALL vn.mail_insert('logistica@verdnatura.es', 'nocontestar@verdnatura.es', 'Nuevo grupo en Floramondo', vDescription); + + CALL vn.mail_insert('pako@verdnatura.es', 'nocontestar@verdnatura.es', CONCAT('Nuevo grupo en Floramondo: ', vDescription), vDescription); + + LEAVE proc; + + END IF; + + -- Asigna la entrada + SELECT vn.ekt_getEntry(vSelf) INTO vEntryFk; + + -- Inserta el cubo si no existe + + IF vPackage = 800 THEN + + SET vHasToChangePackagingFk = TRUE; + + IF vItem THEN + + SELECT vn.item_getPackage(vItem) INTO vPackage ; + + ELSE + + SET vPackage = 8000 + vQty; + + INSERT IGNORE INTO vn.packaging(id, width, `depth`) + SELECT vPackage, vc.ccLength / vQty, vc.ccWidth + FROM vn.volumeConfig vc; + + END IF; + + ELSE + + INSERT IGNORE INTO vn2008.Cubos (Id_Cubo, X, Y, Z) + SELECT bucket_id, ROUND(x_size/10), ROUND(y_size/10), ROUND(z_size/10) + FROM bucket WHERE bucket_id = vPackage; + + IF ROW_COUNT() > 0 + THEN + INSERT INTO vn2008.mail SET + `subject` = 'Cubo añadido', + `text` = CONCAT('Se ha añadido el cubo: ', vPackage), + `to` = 'ekt@verdnatura.es'; + END IF; + END IF; + + -- Si es una compra de Logiflora obtiene el articulo + IF vPutOrderFk THEN + + SELECT i.id INTO vItem + FROM edi.putOrder po + JOIN vn.item i ON i.supplyResponseFk = po.supplyResponseID + WHERE po.id = vPutOrderFk + LIMIT 1; + + END IF; + + INSERT IGNORE INTO item_track SET + item_id = vRef; + + IF IFNULL(vItem,0) = 0 THEN + + -- Intenta obtener el artículo en base a los atributos holandeses + + SELECT b.id, IFNULL(b.itemOriginalFk ,b.itemFk) INTO vBuy, vItem + FROM edi.ekt e + JOIN edi.item_track t ON t.item_id = e.ref + LEFT JOIN edi.ekt l ON l.ref = e.ref + LEFT JOIN vn.buy b ON b.ektFk = l.id + LEFT JOIN vn.item i ON i.id = b.itemFk + JOIN vn2008.config cfg + WHERE e.id = vSelf + AND l.id != vSelf + AND b.itemFk != cfg.generic_item + AND IF(t.s1, l.s1 = e.s1, TRUE) + AND IF(t.s2, l.s2 = e.s2, TRUE) + AND IF(t.s3, l.s3 = e.s3, TRUE) + AND IF(t.s4, l.s4 = e.s4, TRUE) + AND IF(t.s5, l.s5 = e.s5, TRUE) + AND IF(t.s6, l.s6 = e.s6, TRUE) + AND IF(t.pac, l.pac = e.pac, TRUE) + AND IF(t.cat, l.cat = e.cat, TRUE) + AND IF(t.ori, l.ori = e.ori, TRUE) + AND IF(t.pro, l.pro = e.pro, TRUE) + AND IF(t.package, l.package = e.package, TRUE) + AND IF(t.item, l.item = e.item, TRUE) + AND i.isFloramondo = vIsFloramondoDirect + ORDER BY l.now DESC, b.id ASC + LIMIT 1; + + END IF; + + -- Si no encuentra el articulo lo crea en el caso de las compras directas en Floramondo + IF ISNULL(vItem) AND vIsFloramondoDirect THEN + + CALL edi.item_getNewByEkt(vSelf, vItem); + + END IF; + + INSERT INTO vn.buy + ( + entryFk + ,ektFk + ,buyingValue + ,itemFk + ,stickers + ,packing + ,`grouping` + ,quantity + ,groupingMode + ,packagingFk + ,weight + ) + SELECT + vEntryFk + ,vSelf + ,(@t := IF(i.stems, i.stems, 1)) * e.pri / IFNULL(i.stemMultiplier, 1) buyingValue + ,IFNULL(vItem, cfg.generic_item) itemFk + ,e.qty stickers + ,@pac := IFNULL(i.stemMultiplier, 1) * e.pac / @t packing + ,IFNULL(b.`grouping`, e.pac) + ,@pac * e.qty + ,vForceToPacking + ,IF(vHasToChangePackagingFk OR ISNULL(b.packagingFk), vPackage, b.packagingFk) + ,(IFNULL(i.weightByPiece,0) * @pac)/1000 + FROM edi.ekt e + LEFT JOIN vn.buy b ON b.id = vBuy + LEFT JOIN vn.item i ON i.id = b.itemFk + LEFT JOIN vn.supplier s ON e.pro = s.id + JOIN vn2008.config cfg + WHERE e.id = vSelf + LIMIT 1; + + DROP TEMPORARY TABLE IF EXISTS tmp.buyRecalc; + + CREATE TEMPORARY TABLE tmp.buyRecalc + SELECT buy.id + FROM vn.buy + WHERE ektFk = vSelf; + + CALL vn.buy_recalcPrices(); + + -- Si es una compra de Logiflora hay que informar la tabla vn.saleBuy + IF vPutOrderFk THEN + + REPLACE vn.saleBuy(saleFk, buyFk, workerFk) + SELECT po.saleFk, b.id, account.myUser_getId() + FROM edi.putOrder po + JOIN vn.buy b ON b.ektFk = vSelf + WHERE po.id = vPutOrderFk; + + END IF; + -- Si es una compra directa en Floramondo hay que añadirlo al ticket + + IF vIsFloramondoDirect THEN + + SELECT t.id INTO vTicketFk + FROM vn.ticket t + JOIN vn.ektEntryAssign eea + ON eea.addressFk = t.addressFk + AND t.warehouseFk = eea.warehouseInFk + JOIN edi.ekt e + ON e.sub = eea.sub + AND e.id = vSelf + WHERE e.fec = t.shipped + LIMIT 1; + + IF ISNULL(vTicketFk) THEN + + INSERT INTO vn.ticket ( + clientFk, + shipped, + addressFk, + agencyModeFk, + nickname, + warehouseFk, + companyFk, + landed, + zoneFk, + zonePrice, + zoneBonus + ) + SELECT + a.clientFk, + e.fec, + a.id, + a.agencyModeFk, + a.nickname, + eea.warehouseInFk, + c.id, + e.fec, + z.id, + z.price, + z.bonus + FROM edi.ekt e + JOIN vn.ektEntryAssign eea ON eea.sub = e.sub + JOIN vn.address a ON a.id = eea.addressFk + JOIN vn.company c ON c.code = 'VNL' + JOIN vn.`zone` z ON z.code = 'FLORAMONDO' + WHERE e.id = vSelf + LIMIT 1; + + SET vTicketFk = LAST_INSERT_ID(); + + INSERT INTO vn.ticketLog + SET originFk = vTicketFk, + userFk = account.myUser_getId(), + `action` = 'insert', + description = CONCAT('EktLoad ha creado el ticket:', ' ', vTicketFk); + + END IF; + + INSERT INTO vn.sale (itemFk, ticketFk, concept, quantity, price) + SELECT vItem, vTicketFk, e.item, e.qty * e.pac, e.pri * ( 1 + fhc.floramondoMargin ) + FROM edi.ekt e + JOIN edi.floraHollandConfig fhc + WHERE e.id = vSelf; + + SELECT LAST_INSERT_ID() INTO vSaleFk; + + REPLACE vn.saleBuy(saleFk, buyFk, workerFk) + SELECT vSaleFk, b.id, account.myUser_getId() + FROM vn.buy b + WHERE b.ektFk = vSelf; + + INSERT INTO vn.saleComponent(saleFk, componentFk, value) + SELECT vSaleFk, c.id, e.pri + FROM edi.ekt e + JOIN vn.component c ON c.code = 'purchaseValue' + WHERE e.id = vSelf; + + INSERT INTO vn.saleComponent(saleFk, componentFk, value) + SELECT vSaleFk, c.id, e.pri * fhc.floramondoMargin + FROM edi.ekt e + JOIN edi.floraHollandConfig fhc + JOIN vn.component c ON c.code = 'margin' + WHERE e.id = vSelf; + END IF; + DROP TEMPORARY TABLE tmp.buyRecalc; +END$$ +DELIMITER ; + +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 vn2008.date_inv(); + 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.ticket = 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 `vn`.`buy_getVolume`() +BEGIN +/** + * Cálculo de volumen en líneas de compra + * @table tmp.buy(buyFk) + */ + SELECT t.name Temp, + CAST(ROUND(SUM(GREATEST(b.stickers ,b.quantity /b.packing ) * + item_getVolume(b.itemFk, b.packagingFk)) / vc.trolleyM3 / 1000000 ,1) AS DECIMAL(10,2)) carros , + CAST(ROUND(SUM(GREATEST(b.stickers ,b.quantity /b.packing ) * + item_getVolume(b.itemFk, b.packagingFk)) / vc.palletM3 / 1000000,1) AS DECIMAL(10,2)) espais + FROM buy b + JOIN tmp.buy tb ON tb.buyFk = b.id + JOIN volumeConfig vc + JOIN item i ON i.id = b.itemFk + JOIN itemType it ON it.id = i.typeFk + LEFT JOIN temperature t ON t.code = it.temperatureFk + GROUP BY Temp; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`entry_checkPackaging`(vEntryFk INT) +BEGIN +/** + * Comprueba que los campos package y packaging no sean nulos + * + * @param vEntryFk Id de entrada + */ + DECLARE vpackageOrPackingNull INT; + + SELECT count(*) INTO vpackageOrPackingNull + FROM buy b + WHERE b.entryFk = vEntryFk + AND (b.packing IS NULL OR b.packagingFk IS NULL); + + IF vpackageOrPackingNull THEN + CALL util.throw("packageOrPackingNull"); + END IF; +END$$ +DELIMITER ; + +DELIMITER $$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`fustControl`(vFromDated DATE, vToDated DATE) +BEGIN + + DECLARE vSijsnerClientFk INT DEFAULT 19752; + + DECLARE vDateStart DATETIME; + DECLARE vDateEnd DATETIME; + + SET vDateStart = vFromDated; + SET vDateEnd = util.Dayend(vToDated); + + SELECT p.id FustCode, + CAST(sent.stucks AS DECIMAL(10,0)) FH, + CAST(tp.stucks AS DECIMAL(10,0)) Tickets, + CAST(-sj.stucks AS DECIMAL(10,0)) Sijsner, + CAST(IFNULL(sent.stucks,0) - IFNULL(tp.stucks,0) + IFNULL(sj.stucks,0) AS DECIMAL(10,0)) saldo + FROM vn.packaging p + LEFT JOIN ( + SELECT FustCode, sum(fustQuantity) stucks + FROM ( + SELECT IFNULL(pe.equivalentFk ,b.packagingFk) FustCode, s.quantity / b.packing AS fustQuantity + FROM vn.sale s + JOIN vn.ticket t ON t.id = s.ticketFk + JOIN vn.warehouse w ON w.id = t.warehouseFk + JOIN vn.warehouseAlias wa ON wa.id = w.aliasFk + JOIN cache.last_buy lb ON lb.item_id = s.itemFk AND lb.warehouse_id = t.warehouseFk + JOIN vn.buy b ON b.id = lb.buy_id + JOIN vn.packaging p ON p.id = b.packagingFk + LEFT JOIN vn.packageEquivalent pe ON pe.packagingFk = p.id + JOIN vn.address a ON a.id = t.addressFk + JOIN vn.province p2 ON p2.id = a.provinceFk + JOIN vn.country c ON c.id = p2.countryFk + WHERE t.shipped BETWEEN vDateStart AND vDateEnd + AND wa.name = 'VNH' + AND p.isPackageReturnable + AND c.country = 'FRANCIA') sub + GROUP BY FustCode) sent ON sent.FustCode = p.id + LEFT JOIN ( + SELECT FustCode, sum(quantity) stucks + FROM ( + SELECT IFNULL(pe.equivalentFk ,tp.packagingFk) FustCode, tp.quantity + FROM vn.ticketPackaging tp + JOIN vn.ticket t ON t.id = tp.ticketFk + JOIN vn.warehouse w ON w.id = t.warehouseFk + JOIN vn.warehouseAlias wa ON wa.id = w.aliasFk + JOIN vn.packaging p ON p.id = tp.packagingFk + LEFT JOIN vn.packageEquivalent pe ON pe.packagingFk = p.id + JOIN vn.address a ON a.id = t.addressFk + JOIN vn.province p2 ON p2.id = a.provinceFk + JOIN vn.country c ON c.id = p2.countryFk + WHERE t.shipped BETWEEN vDateStart AND vDateEnd + AND wa.name = 'VNH' + AND p.isPackageReturnable + AND c.country = 'FRANCIA' + AND t.clientFk != vSijsnerClientFk + AND tp.quantity > 0) sub + GROUP BY FustCode) tp ON tp.FustCode = p.id + LEFT JOIN ( + SELECT FustCode, sum(quantity) stucks + FROM ( + SELECT IFNULL(pe.equivalentFk ,tp.packagingFk) FustCode, tp.quantity + FROM vn.ticketPackaging tp + JOIN vn.ticket t ON t.id = tp.ticketFk + JOIN vn.warehouse w ON w.id = t.warehouseFk + JOIN vn.warehouseAlias wa ON wa.id = w.aliasFk + JOIN vn.packaging p ON p.id = tp.packagingFk + LEFT JOIN vn.packageEquivalent pe ON pe.packagingFk = p.id + WHERE t.shipped BETWEEN TIMESTAMPADD(DAY, 1, vDateStart ) AND TIMESTAMPADD(DAY, 1, vDateEnd ) + AND wa.name = 'VNH' + AND p.isPackageReturnable + AND t.clientFk = vSijsnerClientFk) sub + GROUP BY FustCode) sj ON sj.FustCode = p.id + WHERE sent.stucks + OR tp.stucks + OR sj.stucks; + +END$$ +DELIMITER ; + diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index b6c89bd40..a062168c9 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -1454,7 +1454,7 @@ INSERT INTO `bs`.`waste`(`buyer`, `year`, `week`, `family`, `itemFk`, `itemTypeF ('HankPym', YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK), 1), 'Miscellaneous Accessories', 6, 1, '186', '0', '0.0'), ('HankPym', YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 WEEK), 1), 'Adhesives', 7, 1, '277', '0', '0.0'); -INSERT INTO `vn`.`buy`(`id`,`entryFk`,`itemFk`,`buyingValue`,`quantity`,`packageFk`,`stickers`,`freightValue`,`packageValue`,`comissionValue`,`packing`,`grouping`,`groupingMode`,`location`,`price1`,`price2`,`price3`, `printedStickers`,`isChecked`,`isIgnored`,`weight`, `created`) +INSERT INTO `vn`.`buy`(`id`,`entryFk`,`itemFk`,`buyingValue`,`quantity`,`packagingFk`,`stickers`,`freightValue`,`packageValue`,`comissionValue`,`packing`,`grouping`,`groupingMode`,`location`,`price1`,`price2`,`price3`, `printedStickers`,`isChecked`,`isIgnored`,`weight`, `created`) VALUES (1, 1, 1, 50, 5000, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0, 1, 0, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -2 MONTH)), (2, 2, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0, 1, 0, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)), @@ -2867,6 +2867,7 @@ INSERT INTO `vn`.`profileType` (`id`, `name`) INSERT INTO `salix`.`url` (`appName`, `environment`, `url`) VALUES ('lilium', 'development', 'http://localhost:9000/#/'), + ('hedera', 'development', 'http://localhost:9090/'), ('salix', 'development', 'http://localhost:5000/#!/'); INSERT INTO `vn`.`report` (`id`, `name`, `paperSizeFk`, `method`) diff --git a/db/dump/structure.sql b/db/dump/structure.sql index 08df0541c..b242821fc 100644 --- a/db/dump/structure.sql +++ b/db/dump/structure.sql @@ -30434,6 +30434,7 @@ CREATE TABLE `item` ( `editorFk` int(10) unsigned DEFAULT NULL, `recycledPlastic` int(11) DEFAULT NULL, `nonRecycledPlastic` int(11) DEFAULT NULL, + `minQuantity` int(10) unsigned DEFAULT NULL COMMENT 'Cantidad mínima para una línea de venta', PRIMARY KEY (`id`), UNIQUE KEY `item_supplyResponseFk_idx` (`supplyResponseFk`), KEY `Color` (`inkFk`), diff --git a/db/tests/vn/ticketCalculateClon.spec.js b/db/tests/vn/ticketCalculateClon.spec.js index 9116d805f..665d52ed0 100644 --- a/db/tests/vn/ticketCalculateClon.spec.js +++ b/db/tests/vn/ticketCalculateClon.spec.js @@ -53,7 +53,8 @@ describe('ticket ticketCalculateClon()', () => { expect(result[orderIndex][0].ticketFk).toBeGreaterThan(newestTicketIdInFixtures); }); - it('should add the ticket to the order containing the original ticket and generate landed value if it was null', async() => { + it('should add the ticket to the order containing the original ' + + 'ticket and generate landed value if it was null', async() => { let stmts = []; let stmt; diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 6f78d7c4e..5e071911e 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -1192,7 +1192,7 @@ export default { secondBuyPacking: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.packing"]', secondBuyWeight: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.weight"]', secondBuyStickers: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.stickers"]', - secondBuyPackage: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-autocomplete[ng-model="buy.packageFk"]', + secondBuyPackage: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-autocomplete[ng-model="buy.packagingFk"]', secondBuyQuantity: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.quantity"]', secondBuyItem: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-autocomplete[ng-model="buy.itemFk"]', importButton: 'vn-entry-buy-index vn-icon[icon="publish"]', 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 6264073f6..23983a9c8 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 @@ -134,14 +134,6 @@ describe('Ticket Edit sale path', () => { await page.accessToSection('ticket.card.sale'); }); - it('should try to add a higher quantity value and then receive an error', async() => { - await page.waitToClick(selectors.ticketSales.firstSaleQuantityCell); - await page.type(selectors.ticketSales.firstSaleQuantity, '11\u000d'); - const message = await page.waitForSnackbar(); - - expect(message.text).toContain('The new quantity should be smaller than the old one'); - }); - it('should remove 1 from the first sale quantity', async() => { await page.waitToClick(selectors.ticketSales.firstSaleQuantityCell); await page.waitForSelector(selectors.ticketSales.firstSaleQuantity); diff --git a/e2e/paths/13-supplier/03_fiscal_data.spec.js b/e2e/paths/13-supplier/03_fiscal_data.spec.js index 891b769c9..96977e708 100644 --- a/e2e/paths/13-supplier/03_fiscal_data.spec.js +++ b/e2e/paths/13-supplier/03_fiscal_data.spec.js @@ -1,20 +1,5 @@ import getBrowser from '../../helpers/puppeteer'; -const $ = { - saveButton: 'vn-supplier-fiscal-data button[type="submit"]', -}; -const $inputs = { - province: 'vn-supplier-fiscal-data [name="province"]', - country: 'vn-supplier-fiscal-data [name="country"]', - postcode: 'vn-supplier-fiscal-data [name="postcode"]', - city: 'vn-supplier-fiscal-data [name="city"]', - socialName: 'vn-supplier-fiscal-data [name="socialName"]', - taxNumber: 'vn-supplier-fiscal-data [name="taxNumber"]', - account: 'vn-supplier-fiscal-data [name="account"]', - sageWithholding: 'vn-supplier-fiscal-data [ng-model="$ctrl.supplier.sageWithholdingFk"]', - sageTaxType: 'vn-supplier-fiscal-data [ng-model="$ctrl.supplier.sageTaxTypeFk"]' -}; - describe('Supplier fiscal data path', () => { let browser; let page; @@ -30,7 +15,7 @@ describe('Supplier fiscal data path', () => { await browser.close(); }); - it('should attempt to edit the fiscal data and check data is saved', async() => { + it('should attempt to edit the fiscal data and check data iss saved', async() => { await page.accessToSection('supplier.card.fiscalData'); const form = 'vn-supplier-fiscal-data form'; @@ -40,16 +25,16 @@ describe('Supplier fiscal data path', () => { postcode: null, city: 'Valencia', socialName: 'Farmer King SL', - taxNumber: 'Wrong tax number', + taxNumber: '12345678Z', account: '0123456789', sageWithholding: 'retencion estimacion objetiva', sageTaxType: 'operaciones no sujetas' }; - const errorMessage = await page.sendForm(form, values); - const message = await page.sendForm(form, { - taxNumber: '12345678Z' + const errorMessage = await page.sendForm(form, { + taxNumber: 'Wrong tax number' }); + const message = await page.sendForm(form, values); await page.reloadSection('supplier.card.fiscalData'); const formValues = await page.fetchForm(form, Object.keys(values)); diff --git a/loopback/common/models/application.json b/loopback/common/models/application.json index bc72df315..f79001585 100644 --- a/loopback/common/models/application.json +++ b/loopback/common/models/application.json @@ -13,6 +13,6 @@ "principalType": "ROLE", "principalId": "$everyone", "permission": "ALLOW" - } + } ] } diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 645a874e8..8dfed66f6 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -14,7 +14,7 @@ "The default consignee can not be unchecked": "The default consignee can not be unchecked", "Enter an integer different to zero": "Enter an integer different to zero", "Package cannot be blank": "Package cannot be blank", - "The new quantity should be smaller than the old one": "The new quantity should be smaller than the old one", + "The price of the item changed": "The price of the item changed", "The sales of this ticket can't be modified": "The sales of this ticket can't be modified", "Cannot check Equalization Tax in this NIF/CIF": "Cannot check Equalization Tax in this NIF/CIF", "You can't create an order for a frozen client": "You can't create an order for a frozen client", @@ -189,5 +189,6 @@ "The sales do not exists": "The sales do not exists", "Ticket without Route": "Ticket without route", "Booking completed": "Booking complete", - "The ticket is in preparation": "The ticket [{{ticketId}}]({{{ticketUrl}}}) of the sales person {{salesPersonId}} is in preparation" + "The ticket is in preparation": "The ticket [{{ticketId}}]({{{ticketUrl}}}) of the sales person {{salesPersonId}} is in preparation", + "You can only add negative amounts in refund tickets": "You can only add negative amounts in refund tickets" } diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 6e478c000..4b297144f 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -35,7 +35,7 @@ "The grade must be an integer greater than or equal to zero": "El grade debe ser un entero mayor o igual a cero", "Sample type cannot be blank": "El tipo de plantilla no puede quedar en blanco", "Description cannot be blank": "Se debe rellenar el campo de texto", - "The new quantity should be smaller than the old one": "La nueva cantidad debe de ser menor que la anterior", + "The price of the item changed": "El precio del artículo cambió", "The value should not be greater than 100%": "El valor no debe de ser mayor de 100%", "The value should be a number": "El valor debe ser un numero", "This order is not editable": "Esta orden no se puede modificar", @@ -320,5 +320,7 @@ "The response is not a PDF": "La respuesta no es un PDF", "Ticket without Route": "Ticket sin ruta", "Booking completed": "Reserva completada", - "The ticket is in preparation": "El ticket [{{ticketId}}]({{{ticketUrl}}}) del comercial {{salesPersonId}} está en preparación" + "The ticket is in preparation": "El ticket [{{ticketId}}]({{{ticketUrl}}}) del comercial {{salesPersonId}} está en preparación", + "The amount cannot be less than the minimum": "La cantidad no puede ser menor que la cantidad mímina", + "quantityLessThanMin": "La cantidad no puede ser menor que la cantidad mímina" } diff --git a/modules/account/back/models/mail-forward.json b/modules/account/back/models/mail-forward.json index edef1bf08..874810b7a 100644 --- a/modules/account/back/models/mail-forward.json +++ b/modules/account/back/models/mail-forward.json @@ -21,5 +21,16 @@ "model": "VnUser", "foreignKey": "account" } - } + }, + "acls": [{ + "accessType": "READ", + "principalType": "ROLE", + "principalId": "$owner", + "permission": "ALLOW" + }, { + "accessType": "WRITE", + "principalType": "ROLE", + "principalId": "$owner", + "permission": "ALLOW" + }] } diff --git a/modules/account/front/basic-data/index.html b/modules/account/front/basic-data/index.html index 6f757753e..9fd3506fe 100644 --- a/modules/account/front/basic-data/index.html +++ b/modules/account/front/basic-data/index.html @@ -1,9 +1,9 @@ + + form="form" + save="patch">
diff --git a/modules/account/front/basic-data/index.js b/modules/account/front/basic-data/index.js index 77d3eab26..f6b266bbc 100644 --- a/modules/account/front/basic-data/index.js +++ b/modules/account/front/basic-data/index.js @@ -18,5 +18,8 @@ ngModule.component('vnUserBasicData', { controller: Controller, require: { card: '^vnUserCard' + }, + bindings: { + user: '<' } }); diff --git a/modules/account/front/routes.json b/modules/account/front/routes.json index fd33e7122..d7845090b 100644 --- a/modules/account/front/routes.json +++ b/modules/account/front/routes.json @@ -78,7 +78,9 @@ "state": "account.card.basicData", "component": "vn-user-basic-data", "description": "Basic data", - "acl": ["itManagement"] + "params": { + "user": "$ctrl.user" + } }, { "url" : "/log", diff --git a/modules/claim/back/methods/claim/claimPickupEmail.js b/modules/claim/back/methods/claim/claimPickupEmail.js index 1fd8eb99e..596102a24 100644 --- a/modules/claim/back/methods/claim/claimPickupEmail.js +++ b/modules/claim/back/methods/claim/claimPickupEmail.js @@ -43,9 +43,8 @@ module.exports = Self => { Self.claimPickupEmail = async ctx => { const models = Self.app.models; - const userId = ctx.req.accessToken.userId; const $t = ctx.req.__; // $translate - const origin = ctx.req.headers.origin; + const url = await Self.app.models.Url.getUrl(); const args = Object.assign({}, ctx.args); const params = { @@ -70,9 +69,8 @@ module.exports = Self => { const message = $t('Claim pickup order sent', { claimId: args.id, clientName: claim.client().name, - claimUrl: `${origin}/#!/claim/${args.id}/summary`, + claimUrl: `${url}claim/${args.id}/summary`, }); - const salesPersonId = claim.client().salesPersonFk; if (salesPersonId) await models.Chat.sendCheckingPresence(ctx, salesPersonId, message); diff --git a/modules/claim/back/methods/claim/createFromSales.js b/modules/claim/back/methods/claim/createFromSales.js index 07bdb30aa..30093e43d 100644 --- a/modules/claim/back/methods/claim/createFromSales.js +++ b/modules/claim/back/methods/claim/createFromSales.js @@ -94,13 +94,13 @@ module.exports = Self => { const salesPerson = ticket.client().salesPersonUser(); if (salesPerson) { - const origin = ctx.req.headers.origin; + const url = await Self.app.models.Url.getUrl(); const message = $t('Created claim', { claimId: newClaim.id, ticketId: ticketId, - ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`, - claimUrl: `${origin}/#!/claim/${newClaim.id}/summary`, + ticketUrl: `${url}ticket/${ticketId}/sale`, + claimUrl: `${url}claim/${newClaim.id}/summary`, changes: changesMade }); await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message); diff --git a/modules/claim/back/methods/claim/regularizeClaim.js b/modules/claim/back/methods/claim/regularizeClaim.js index 672c94947..a51b114ef 100644 --- a/modules/claim/back/methods/claim/regularizeClaim.js +++ b/modules/claim/back/methods/claim/regularizeClaim.js @@ -56,15 +56,15 @@ module.exports = Self => { const salesPerson = sale.ticket().client().salesPersonUser(); if (salesPerson) { const nickname = address && address.nickname || destination.description; - const origin = ctx.req.headers.origin; + const url = await Self.app.models.Url.getUrl(); const message = $t('Sent units from ticket', { quantity: sale.quantity, concept: sale.concept, itemId: sale.itemFk, ticketId: sale.ticketFk, nickname: nickname, - ticketUrl: `${origin}/#!/ticket/${sale.ticketFk}/sale`, - itemUrl: `${origin}/#!/item/${sale.itemFk}/summary` + ticketUrl: `${url}ticket/${sale.ticketFk}/sale`, + itemUrl: `${url}item/${sale.itemFk}/summary` }); await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message); } diff --git a/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js b/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js index 276843c32..95c356374 100644 --- a/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js +++ b/modules/claim/back/methods/claim/specs/regularizeClaim.spec.js @@ -64,7 +64,7 @@ describe('claim regularizeClaim()', () => { claimEnds = await importTicket(ticketId, claimId, userId, options); - for (claimEnd of claimEnds) + for (const claimEnd of claimEnds) await claimEnd.updateAttributes({claimDestinationFk: trashDestination}, options); let claimBefore = await models.Claim.findById(claimId, null, options); diff --git a/modules/claim/back/methods/claim/specs/updateClaim.spec.js b/modules/claim/back/methods/claim/specs/updateClaim.spec.js index d367fb89f..85ada869a 100644 --- a/modules/claim/back/methods/claim/specs/updateClaim.spec.js +++ b/modules/claim/back/methods/claim/specs/updateClaim.spec.js @@ -2,7 +2,9 @@ const app = require('vn-loopback/server/server'); const LoopBackContext = require('loopback-context'); describe('Update Claim', () => { + let url; beforeAll(async() => { + url = await app.models.Url.getUrl(); const activeCtx = { accessToken: {userId: 9}, http: { @@ -29,7 +31,6 @@ describe('Update Claim', () => { it(`should throw an error as the user doesn't have rights`, async() => { const tx = await app.models.Claim.beginTransaction({}); - let error; try { @@ -77,7 +78,7 @@ describe('Update Claim', () => { const ctx = { req: { accessToken: {userId: claimManagerId}, - headers: {origin: 'http://localhost'} + headers: {origin: url} }, args: { observation: 'valid observation', @@ -118,7 +119,7 @@ describe('Update Claim', () => { const ctx = { req: { accessToken: {userId: claimManagerId}, - headers: {origin: 'http://localhost'} + headers: {origin: url} }, args: { observation: 'valid observation', diff --git a/modules/claim/back/methods/claim/updateClaim.js b/modules/claim/back/methods/claim/updateClaim.js index 5486b8eff..d99528413 100644 --- a/modules/claim/back/methods/claim/updateClaim.js +++ b/modules/claim/back/methods/claim/updateClaim.js @@ -91,16 +91,16 @@ module.exports = Self => { // When hasToPickUp has been changed if (salesPerson && changedHasToPickUp && updatedClaim.hasToPickUp) - notifyPickUp(ctx, salesPerson.id, claim); + await notifyPickUp(ctx, salesPerson.id, claim); // 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') - notifyStateChange(ctx, salesPerson.id, claim, newState.code); + await notifyStateChange(ctx, salesPerson.id, claim, newState.code); if (newState.code == 'canceled') - notifyStateChange(ctx, claim.workerFk, claim, newState.code); + await notifyStateChange(ctx, claim.workerFk, claim, newState.code); } } @@ -115,26 +115,26 @@ module.exports = Self => { async function notifyStateChange(ctx, workerId, claim, state) { const models = Self.app.models; - const origin = ctx.req.headers.origin; + const url = await models.Url.getUrl(); const $t = ctx.req.__; // $translate const message = $t(`Claim state has changed to ${state}`, { claimId: claim.id, clientName: claim.client().name, - claimUrl: `${origin}/#!/claim/${claim.id}/summary` + claimUrl: `${url}claim/${claim.id}/summary` }); await models.Chat.sendCheckingPresence(ctx, workerId, message); } async function notifyPickUp(ctx, workerId, claim) { - const origin = ctx.req.headers.origin; const models = Self.app.models; + const url = await models.Url.getUrl(); const $t = ctx.req.__; // $translate const message = $t('Claim will be picked', { claimId: claim.id, clientName: claim.client().name, - claimUrl: `${origin}/#!/claim/${claim.id}/summary` + claimUrl: `${url}claim/${claim.id}/summary` }); await models.Chat.sendCheckingPresence(ctx, workerId, message); } diff --git a/modules/claim/front/locale/es.yml b/modules/claim/front/locale/es.yml index f6dac2b83..83ccf1e7b 100644 --- a/modules/claim/front/locale/es.yml +++ b/modules/claim/front/locale/es.yml @@ -17,6 +17,7 @@ Search claim by id or client name: Buscar reclamaciones por identificador o nomb Claim deleted!: Reclamación eliminada! claim: reclamación Photos: Fotos +Development: Trazabilidad Go to the claim: Ir a la reclamación Sale tracking: Líneas preparadas Ticket tracking: Estados del ticket diff --git a/modules/client/back/models/client.js b/modules/client/back/models/client.js index 8e720484f..e16e884cc 100644 --- a/modules/client/back/models/client.js +++ b/modules/client/back/models/client.js @@ -250,7 +250,12 @@ module.exports = Self => { const loopBackContext = LoopBackContext.getCurrentContext(); const accessToken = {req: loopBackContext.active.accessToken}; - const editVerifiedDataWithoutTaxDataChecked = models.ACL.checkAccessAcl(accessToken, 'Client', 'editVerifiedDataWithoutTaxDataCheck', 'WRITE'); + const editVerifiedDataWithoutTaxDataChecked = models.ACL.checkAccessAcl( + accessToken, + 'Client', + 'editVerifiedDataWithoutTaxDataCheck', + 'WRITE' + ); const hasChanges = orgData && changes; const isTaxDataChecked = hasChanges && (changes.isTaxDataChecked || orgData.isTaxDataChecked); @@ -263,7 +268,9 @@ module.exports = Self => { const sageTransactionTypeChanged = hasChanges && orgData.sageTransactionTypeFk != sageTransactionType; const cantEditVerifiedData = isTaxDataCheckedChanged && !editVerifiedDataWithoutTaxDataChecked; - const cantChangeSageData = (sageTaxTypeChanged || sageTransactionTypeChanged) && !editVerifiedDataWithoutTaxDataChecked; + const cantChangeSageData = (sageTaxTypeChanged || + sageTransactionTypeChanged + ) && !editVerifiedDataWithoutTaxDataChecked; if (cantEditVerifiedData || cantChangeSageData) throw new UserError(`You don't have enough privileges`); @@ -346,8 +353,7 @@ module.exports = Self => { const httpCtx = {req: loopBackContext.active}; const httpRequest = httpCtx.req.http.req; const $t = httpRequest.__; - const headers = httpRequest.headers; - const origin = headers.origin; + const url = await Self.app.models.Url.getUrl(); const salesPersonId = instance.salesPersonFk; @@ -366,7 +372,7 @@ module.exports = Self => { await email.send(); } - const fullUrl = `${origin}/#!/client/${instance.id}/billing-data`; + const fullUrl = `${url}client/${instance.id}/billing-data`; const message = $t('Changed client paymethod', { clientId: instance.id, clientName: instance.name, @@ -389,8 +395,7 @@ module.exports = Self => { const httpCtx = {req: loopBackContext.active}; const httpRequest = httpCtx.req.http.req; const $t = httpRequest.__; - const headers = httpRequest.headers; - const origin = headers.origin; + const url = await Self.app.models.Url.getUrl(); const models = Self.app.models; let previousWorker = {name: $t('None')}; @@ -411,7 +416,7 @@ module.exports = Self => { currentWorker.name = worker && worker.user().nickname; } - const fullUrl = `${origin}/#!/client/${client.id}/basic-data`; + const fullUrl = `${url}client/${client.id}/basic-data`; const message = $t('Client assignment has changed', { clientId: client.id, clientName: client.name, diff --git a/modules/client/back/models/credit-insurance.js b/modules/client/back/models/credit-insurance.js index 6f656d382..84bd90424 100644 --- a/modules/client/back/models/credit-insurance.js +++ b/modules/client/back/models/credit-insurance.js @@ -57,8 +57,8 @@ module.exports = function(Self) { const httpRequest = httpCtx.req.http.req; const $t = httpRequest.__; - const origin = httpRequest.headers.origin; - const fullPath = `${origin}/#!/client/${client.id}/credit-insurance/index`; + const url = await Self.app.models.Url.getUrl(); + const fullPath = `${url}client/${client.id}/credit-insurance/index`; const message = $t('MESSAGE_INSURANCE_CHANGE', { clientId: client.id, clientName: client.name, diff --git a/modules/entry/back/locale/buy/en.yml b/modules/entry/back/locale/buy/en.yml index 2db7c7be5..949aa8ed0 100644 --- a/modules/entry/back/locale/buy/en.yml +++ b/modules/entry/back/locale/buy/en.yml @@ -15,4 +15,4 @@ columns: weight: weight entryFk: entry itemFk: item - packageFk: package + packagingFk: package diff --git a/modules/entry/back/locale/buy/es.yml b/modules/entry/back/locale/buy/es.yml index 666bf7640..2aa7688eb 100644 --- a/modules/entry/back/locale/buy/es.yml +++ b/modules/entry/back/locale/buy/es.yml @@ -15,4 +15,4 @@ columns: weight: peso entryFk: entrada itemFk: artículo - packageFk: paquete + packagingFk: paquete diff --git a/modules/entry/back/methods/entry/addFromBuy.js b/modules/entry/back/methods/entry/addFromBuy.js index 82645b166..307c04b97 100644 --- a/modules/entry/back/methods/entry/addFromBuy.js +++ b/modules/entry/back/methods/entry/addFromBuy.js @@ -80,7 +80,7 @@ module.exports = Self => { comissionValue: buyUltimate.comissionValue, packageValue: buyUltimate.packageValue, location: buyUltimate.location, - packageFk: buyUltimate.packageFk, + packagingFk: buyUltimate.packagingFk, price1: buyUltimate.price1, price2: buyUltimate.price2, price3: buyUltimate.price3, diff --git a/modules/entry/back/methods/entry/getBuys.js b/modules/entry/back/methods/entry/getBuys.js index ebb271df1..90c1bb9d0 100644 --- a/modules/entry/back/methods/entry/getBuys.js +++ b/modules/entry/back/methods/entry/getBuys.js @@ -44,7 +44,7 @@ module.exports = Self => { 'grouping', 'groupingMode', 'quantity', - 'packageFk', + 'packagingFk', 'weight', 'buyingValue', 'price2', diff --git a/modules/entry/back/methods/entry/importBuys.js b/modules/entry/back/methods/entry/importBuys.js index 1e6f01a5e..812775a1b 100644 --- a/modules/entry/back/methods/entry/importBuys.js +++ b/modules/entry/back/methods/entry/importBuys.js @@ -108,7 +108,7 @@ module.exports = Self => { packing: buy.packing, grouping: buy.grouping, buyingValue: buy.buyingValue, - packageFk: buy.packageFk, + packagingFk: buy.packagingFk, groupingMode: lastBuy.groupingMode, weight: lastBuy.weight }); diff --git a/modules/entry/back/methods/entry/importBuysPreview.js b/modules/entry/back/methods/entry/importBuysPreview.js index 730ecab0a..de8fa732d 100644 --- a/modules/entry/back/methods/entry/importBuysPreview.js +++ b/modules/entry/back/methods/entry/importBuysPreview.js @@ -39,7 +39,7 @@ module.exports = Self => { }, myOptions); if (packaging) - buy.packageFk = packaging.id; + buy.packagingFk = packaging.id; const reference = await models.ItemMatchProperties.findOne({ fields: ['itemFk'], diff --git a/modules/entry/back/methods/entry/latestBuysFilter.js b/modules/entry/back/methods/entry/latestBuysFilter.js index 30d47a2a3..dbf7d2b17 100644 --- a/modules/entry/back/methods/entry/latestBuysFilter.js +++ b/modules/entry/back/methods/entry/latestBuysFilter.js @@ -153,64 +153,63 @@ module.exports = Self => { const date = Date.vnNew(); date.setHours(0, 0, 0, 0); stmt = new ParameterizedSQL(` - SELECT - i.image, - i.id AS itemFk, - i.size, - i.weightByPiece, - it.code, - i.typeFk, - i.family, - i.isActive, - i.minPrice, - i.description, - i.name, - i.subName, - i.packingOut, - i.tag5, - i.value5, - i.tag6, - i.value6, - i.tag7, - i.value7, - i.tag8, - i.value8, - i.tag9, - i.value9, - i.tag10, - i.value10, - t.name AS type, - intr.description AS intrastat, - ori.code AS origin, - b.entryFk, - b.id, - b.quantity, - b.buyingValue, - b.freightValue, - b.isIgnored, - b.packing, - b.grouping, - b.groupingMode, - b.comissionValue, - b.packageValue, - b.price2, - b.price3, - b.ektFk, - b.weight, - b.packageFk, - lb.landing - FROM cache.last_buy lb - LEFT JOIN cache.visible v ON v.item_id = lb.item_id - AND v.calc_id = @calc_id - JOIN item i ON i.id = lb.item_id - JOIN itemType it ON it.id = i.typeFk AND lb.warehouse_id = ? - JOIN buy b ON b.id = lb.buy_id - LEFT JOIN itemCategory ic ON ic.id = it.categoryFk - LEFT JOIN itemType t ON t.id = i.typeFk - LEFT JOIN intrastat intr ON intr.id = i.intrastatFk - LEFT JOIN origin ori ON ori.id = i.originFk - LEFT JOIN entry e ON e.id = b.entryFk AND e.created >= DATE_SUB(? ,INTERVAL 1 YEAR) - LEFT JOIN supplier s ON s.id = e.supplierFk` + SELECT i.image, + i.id AS itemFk, + i.size, + i.weightByPiece, + it.code, + i.typeFk, + i.family, + i.isActive, + i.minPrice, + i.description, + i.name, + i.subName, + i.packingOut, + i.tag5, + i.value5, + i.tag6, + i.value6, + i.tag7, + i.value7, + i.tag8, + i.value8, + i.tag9, + i.value9, + i.tag10, + i.value10, + t.name AS type, + intr.description AS intrastat, + ori.code AS origin, + b.entryFk, + b.id, + b.quantity, + b.buyingValue, + b.freightValue, + b.isIgnored, + b.packing, + b.grouping, + b.groupingMode, + b.comissionValue, + b.packageValue, + b.price2, + b.price3, + b.ektFk, + b.weight, + b.packagingFk, + lb.landing + FROM cache.last_buy lb + LEFT JOIN cache.visible v ON v.item_id = lb.item_id + AND v.calc_id = @calc_id + JOIN item i ON i.id = lb.item_id + JOIN itemType it ON it.id = i.typeFk AND lb.warehouse_id = ? + JOIN buy b ON b.id = lb.buy_id + LEFT JOIN itemCategory ic ON ic.id = it.categoryFk + LEFT JOIN itemType t ON t.id = i.typeFk + LEFT JOIN intrastat intr ON intr.id = i.intrastatFk + LEFT JOIN origin ori ON ori.id = i.originFk + LEFT JOIN entry e ON e.id = b.entryFk AND e.created >= DATE_SUB(? ,INTERVAL 1 YEAR) + LEFT JOIN supplier s ON s.id = e.supplierFk` , [userConfig.warehouseFk, date]); if (ctx.args.tags) { diff --git a/modules/entry/back/methods/entry/specs/importBuys.spec.js b/modules/entry/back/methods/entry/specs/importBuys.spec.js index 4f9204c6a..a352294b9 100644 --- a/modules/entry/back/methods/entry/specs/importBuys.spec.js +++ b/modules/entry/back/methods/entry/specs/importBuys.spec.js @@ -33,7 +33,7 @@ describe('entry import()', () => { packing: 1, size: 1, volume: 1200, - packageFk: '94' + packagingFk: '94' }, { itemFk: 4, @@ -43,7 +43,7 @@ describe('entry import()', () => { packing: 1, size: 25, volume: 1125, - packageFk: '94' + packagingFk: '94' } ] } diff --git a/modules/entry/back/methods/entry/specs/importBuysPreview.spec.js b/modules/entry/back/methods/entry/specs/importBuysPreview.spec.js index 41971a64c..c860e228e 100644 --- a/modules/entry/back/methods/entry/specs/importBuysPreview.spec.js +++ b/modules/entry/back/methods/entry/specs/importBuysPreview.spec.js @@ -10,12 +10,12 @@ describe('entry importBuysPreview()', () => { }); }); - it('should return the buys with the calculated packageFk', async() => { + it('should return the buys with the calculated packagingFk', async() => { const tx = await models.Entry.beginTransaction({}); const options = {transaction: tx}; try { - const expectedPackageFk = '3'; + const expectedPackagingFk = '3'; const buys = [ { itemFk: 1, @@ -39,7 +39,7 @@ describe('entry importBuysPreview()', () => { const randomIndex = Math.floor(Math.random() * result.length); const buy = result[randomIndex]; - expect(buy.packageFk).toEqual(expectedPackageFk); + expect(buy.packagingFk).toEqual(expectedPackagingFk); await tx.rollback(); } catch (e) { diff --git a/modules/entry/back/models/buy.json b/modules/entry/back/models/buy.json index af205533f..30379eaf6 100644 --- a/modules/entry/back/models/buy.json +++ b/modules/entry/back/models/buy.json @@ -103,7 +103,7 @@ "package": { "type": "belongsTo", "model": "Packaging", - "foreignKey": "packageFk" + "foreignKey": "packagingFk" }, "worker": { "type": "belongsTo", diff --git a/modules/entry/front/buy/import/index.html b/modules/entry/front/buy/import/index.html index ccb1a5dac..28396434c 100644 --- a/modules/entry/front/buy/import/index.html +++ b/modules/entry/front/buy/import/index.html @@ -83,14 +83,14 @@ {{::buy.packing | dashIfEmpty}} {{::buy.grouping | dashIfEmpty}} {{::buy.buyingValue | currency: 'EUR':2}} - + + ng-model="buy.packagingFk"> diff --git a/modules/entry/front/buy/index/index.html b/modules/entry/front/buy/index/index.html index 28fdabdb4..203d6c522 100644 --- a/modules/entry/front/buy/index/index.html +++ b/modules/entry/front/buy/index/index.html @@ -88,12 +88,12 @@ diff --git a/modules/entry/front/buy/index/index.js b/modules/entry/front/buy/index/index.js index 649e69286..c991b745b 100644 --- a/modules/entry/front/buy/index/index.js +++ b/modules/entry/front/buy/index/index.js @@ -4,7 +4,7 @@ import Section from 'salix/components/section'; export default class Controller extends Section { saveBuy(buy) { - const missingData = !buy.itemFk || !buy.quantity || !buy.packageFk; + const missingData = !buy.itemFk || !buy.quantity || !buy.packagingFk; if (missingData) return; let options; diff --git a/modules/entry/front/buy/index/index.spec.js b/modules/entry/front/buy/index/index.spec.js index 0e221302c..b9d5fab51 100644 --- a/modules/entry/front/buy/index/index.spec.js +++ b/modules/entry/front/buy/index/index.spec.js @@ -17,7 +17,7 @@ describe('Entry buy', () => { describe('saveBuy()', () => { it(`should call the buys patch route if the received buy has an ID`, () => { - const buy = {id: 1, itemFk: 1, quantity: 1, packageFk: 1}; + const buy = {id: 1, itemFk: 1, quantity: 1, packagingFk: 1}; const query = `Buys/${buy.id}`; diff --git a/modules/entry/front/latest-buys/index.html b/modules/entry/front/latest-buys/index.html index a2cf62da2..16e039cf0 100644 --- a/modules/entry/front/latest-buys/index.html +++ b/modules/entry/front/latest-buys/index.html @@ -104,7 +104,7 @@ Weight - + Package @@ -207,7 +207,7 @@ {{::buy.minPrice | currency: 'EUR':3}} {{::buy.ektFk | dashIfEmpty}} {{::buy.weight}} - {{::buy.packageFk}} + {{::buy.packagingFk}} {{::buy.packingOut}} {{::buy.landing | date: 'dd/MM/yyyy'}} diff --git a/modules/entry/front/latest-buys/index.js b/modules/entry/front/latest-buys/index.js index d9065193e..292c5b805 100644 --- a/modules/entry/front/latest-buys/index.js +++ b/modules/entry/front/latest-buys/index.js @@ -48,7 +48,7 @@ export default class Controller extends Section { } }, { - field: 'packageFk', + field: 'packagingFk', autocomplete: { url: 'Packagings', showField: 'id' @@ -133,7 +133,7 @@ export default class Controller extends Section { case 'price3': case 'ektFk': case 'weight': - case 'packageFk': + case 'packagingFk': return {[`b.${param}`]: value}; } } diff --git a/modules/entry/front/summary/index.html b/modules/entry/front/summary/index.html index d5d3ebf1e..655dcd66f 100644 --- a/modules/entry/front/summary/index.html +++ b/modules/entry/front/summary/index.html @@ -105,7 +105,7 @@ Quantity Stickers - Package + Package Weight Packing Grouping @@ -118,7 +118,7 @@ {{::line.quantity}} {{::line.stickers | dashIfEmpty}} - {{::line.packageFk | dashIfEmpty}} + {{::line.packagingFk | dashIfEmpty}} {{::line.weight}} diff --git a/modules/invoiceOut/back/methods/invoiceOut/makePdfAndNotify.js b/modules/invoiceOut/back/methods/invoiceOut/makePdfAndNotify.js index a48664b30..1de15b666 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/makePdfAndNotify.js +++ b/modules/invoiceOut/back/methods/invoiceOut/makePdfAndNotify.js @@ -59,10 +59,10 @@ module.exports = Self => { }; await Self.invoiceEmail(ctx, ref); } catch (err) { - const origin = ctx.req.headers.origin; + const url = await Self.app.models.Url.getUrl(); const message = ctx.req.__('Mail not sent', { clientId: client.id, - clientUrl: `${origin}/#!/claim/${id}/summary` + clientUrl: `${url}claim/${id}/summary` }); const salesPersonId = client.salesPersonFk; diff --git a/modules/item/back/methods/item/lastEntriesFilter.js b/modules/item/back/methods/item/lastEntriesFilter.js index 83a8fe7a2..5aafbb4f6 100644 --- a/modules/item/back/methods/item/lastEntriesFilter.js +++ b/modules/item/back/methods/item/lastEntriesFilter.js @@ -29,44 +29,44 @@ module.exports = Self => { Object.assign(myOptions, options); const stmt = new ParameterizedSQL( - `SELECT - w.id AS warehouseFk, - w.name AS warehouse, - tr.landed, - b.id AS buyFk, - b.entryFk, - b.isIgnored, - b.price2, - b.price3, - b.stickers, - b.packing, - b.grouping, - b.groupingMode, - b.weight, - i.stems, - b.quantity, - b.buyingValue + - b.freightValue + - b.comissionValue + - b.packageValue AS cost, - b.buyingValue, - b.freightValue, - b.comissionValue, - b.packageValue, - b.packageFk , - s.id AS supplierFk, - s.name AS supplier - FROM itemType it - RIGHT JOIN (entry e - LEFT JOIN supplier s ON s.id = e.supplierFk - RIGHT JOIN buy b ON b.entryFk = e.id - LEFT JOIN item i ON i.id = b.itemFk - LEFT JOIN ink ON ink.id = i.inkFk - LEFT JOIN travel tr ON tr.id = e.travelFk - LEFT JOIN warehouse w ON w.id = tr.warehouseInFk - LEFT JOIN origin o ON o.id = i.originFk - ) ON it.id = i.typeFk - LEFT JOIN edi.ekt ek ON b.ektFk = ek.id`); + `SELECT w.id AS warehouseFk, + w.name AS warehouse, + tr.landed, + b.id AS buyFk, + b.entryFk, + b.isIgnored, + b.price2, + b.price3, + b.stickers, + b.packing, + b.grouping, + b.groupingMode, + b.weight, + i.stems, + b.quantity, + b.buyingValue + + b.freightValue + + b.comissionValue + + b.packageValue AS cost, + b.buyingValue, + b.freightValue, + b.comissionValue, + b.packageValue, + b.packagingFk , + s.id AS supplierFk, + s.name AS supplier + FROM itemType it + RIGHT JOIN (entry e + LEFT JOIN supplier s ON s.id = e.supplierFk + RIGHT JOIN buy b ON b.entryFk = e.id + LEFT JOIN item i ON i.id = b.itemFk + LEFT JOIN ink ON ink.id = i.inkFk + LEFT JOIN travel tr ON tr.id = e.travelFk + LEFT JOIN warehouse w ON w.id = tr.warehouseInFk + LEFT JOIN origin o ON o.id = i.originFk + ) ON it.id = i.typeFk + LEFT JOIN edi.ekt ek ON b.ektFk = ek.id` + ); stmt.merge(conn.makeSuffix(filter)); return conn.executeStmt(stmt, myOptions); diff --git a/modules/item/back/models/item.json b/modules/item/back/models/item.json index e99dcd996..6db1f5efc 100644 --- a/modules/item/back/models/item.json +++ b/modules/item/back/models/item.json @@ -131,6 +131,9 @@ "nonRecycledPlastic": { "type": "number" }, + "minQuantity": { + "type": "number" + }, "packingOut": { "type": "number" }, @@ -154,6 +157,10 @@ "mysql":{ "columnName": "doPhoto" } + }, + "minQuantity": { + "type": "number", + "description": "Min quantity" } }, "relations": { diff --git a/modules/item/front/basic-data/index.html b/modules/item/front/basic-data/index.html index fba4d679c..426c17800 100644 --- a/modules/item/front/basic-data/index.html +++ b/modules/item/front/basic-data/index.html @@ -33,6 +33,8 @@ rule info="Full name calculates based on tags 1-3. Is not recommended to change it manually"> + + + + +
{{::name}}
+
+ #{{::id}} +
+
+ + + + +
- - -
{{::name}}
-
- #{{::id}} -
-
- - - - -
+ +
Quantity Cost Kg. - Cube + Cube Provider @@ -94,7 +94,7 @@ {{::entry.weight | dashIfEmpty}} - {{::entry.packageFk | dashIfEmpty}} + {{::entry.packagingFk | dashIfEmpty}} {{::entry.supplier | dashIfEmpty}} diff --git a/modules/item/front/last-entries/index.js b/modules/item/front/last-entries/index.js index 0c6804838..31616f8a7 100644 --- a/modules/item/front/last-entries/index.js +++ b/modules/item/front/last-entries/index.js @@ -71,7 +71,7 @@ class Controller extends Section { switch (param) { case 'id': case 'quantity': - case 'packageFk': + case 'packagingFk': return {[`b.${param}`]: value}; case 'supplierFk': return {[`s.id`]: value}; diff --git a/modules/item/front/request/index.html b/modules/item/front/request/index.html index c505b3a09..200ce3902 100644 --- a/modules/item/front/request/index.html +++ b/modules/item/front/request/index.html @@ -26,6 +26,7 @@ Ticket ID Shipped Description + Requester Requested Price Atender @@ -51,6 +52,13 @@ {{::request.description}} + + + {{::request.requesterName}} + + {{::request.quantity}} {{::request.price | currency: 'EUR':2}} diff --git a/modules/item/front/summary/index.html b/modules/item/front/summary/index.html index 6ec738c7e..5fe84591f 100644 --- a/modules/item/front/summary/index.html +++ b/modules/item/front/summary/index.html @@ -128,6 +128,9 @@ + +

diff --git a/modules/item/front/summary/locale/es.yml b/modules/item/front/summary/locale/es.yml index 2e78841ae..80988c491 100644 --- a/modules/item/front/summary/locale/es.yml +++ b/modules/item/front/summary/locale/es.yml @@ -2,3 +2,4 @@ Barcode: Códigos de barras Other data: Otros datos Go to the item: Ir al artículo WarehouseFk: Calculado sobre el almacén de {{ warehouseName }} +Minimum sales quantity: Cantidad mínima de venta diff --git a/modules/order/back/methods/order/catalogFilter.js b/modules/order/back/methods/order/catalogFilter.js index 0d83f9f4a..722f3e096 100644 --- a/modules/order/back/methods/order/catalogFilter.js +++ b/modules/order/back/methods/order/catalogFilter.js @@ -100,31 +100,32 @@ module.exports = Self => { )); stmt = new ParameterizedSQL(` - SELECT - i.id, - i.name, - i.subName, - i.image, - i.tag5, - i.value5, - i.tag6, - i.value6, - i.tag7, - i.value7, - i.tag8, - i.value8, - i.stars, - tci.price, - tci.available, - w.lastName AS lastName, - w.firstName, - tci.priceKg, - ink.hex + SELECT i.id, + i.name, + i.subName, + i.image, + i.tag5, + i.value5, + i.tag6, + i.value6, + i.tag7, + i.value7, + i.tag8, + i.value8, + i.stars, + tci.price, + tci.available, + w.lastName, + w.firstName, + tci.priceKg, + ink.hex, + i.minQuantity FROM tmp.ticketCalculateItem tci JOIN vn.item i ON i.id = tci.itemFk JOIN vn.itemType it ON it.id = i.typeFk JOIN vn.worker w on w.id = it.workerFk - LEFT JOIN vn.ink ON ink.id = i.inkFk`); + LEFT JOIN vn.ink ON ink.id = i.inkFk + `); // Apply order by tag if (orderBy.isTag) { diff --git a/modules/order/front/catalog-view/index.html b/modules/order/front/catalog-view/index.html index fca728855..5d60211ed 100644 --- a/modules/order/front/catalog-view/index.html +++ b/modules/order/front/catalog-view/index.html @@ -8,12 +8,12 @@
- @@ -37,13 +37,28 @@ value="{{::item.value7}}">
- - + + + + + + + + + + {{::item.minQuantity}} + + @@ -69,4 +84,4 @@ - \ No newline at end of file + diff --git a/modules/order/front/catalog-view/locale/es.yml b/modules/order/front/catalog-view/locale/es.yml index 82fe5e9e8..8fb3c7896 100644 --- a/modules/order/front/catalog-view/locale/es.yml +++ b/modules/order/front/catalog-view/locale/es.yml @@ -1 +1,2 @@ Order created: Orden creada +Minimal quantity: Cantidad mínima \ No newline at end of file diff --git a/modules/order/front/catalog-view/style.scss b/modules/order/front/catalog-view/style.scss index 87f70cde5..1e48745ca 100644 --- a/modules/order/front/catalog-view/style.scss +++ b/modules/order/front/catalog-view/style.scss @@ -44,4 +44,7 @@ vn-order-catalog { height: 30px; position: relative; } -} \ No newline at end of file + .alert { + color: $color-alert; + } +} diff --git a/modules/ticket/back/methods/sale/deleteSales.js b/modules/ticket/back/methods/sale/deleteSales.js index 5d1463a66..0207815a9 100644 --- a/modules/ticket/back/methods/sale/deleteSales.js +++ b/modules/ticket/back/methods/sale/deleteSales.js @@ -1,5 +1,3 @@ -let UserError = require('vn-loopback/util/user-error'); - module.exports = Self => { Self.remoteMethodCtx('deleteSales', { description: 'Deletes the selected sales', @@ -70,11 +68,11 @@ module.exports = Self => { const salesPerson = ticket.client().salesPersonUser(); if (salesPerson) { - const origin = ctx.req.headers.origin; + const url = await Self.app.models.Url.getUrl(); const message = $t('Deleted sales from ticket', { ticketId: ticketId, - ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`, + ticketUrl: `${url}ticket/${ticketId}/sale`, deletions: deletions }); await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions); diff --git a/modules/ticket/back/methods/sale/refund.js b/modules/ticket/back/methods/sale/refund.js index 0aecaad24..c3697a82f 100644 --- a/modules/ticket/back/methods/sale/refund.js +++ b/modules/ticket/back/methods/sale/refund.js @@ -55,7 +55,7 @@ module.exports = Self => { const refoundZoneId = refundAgencyMode.zones()[0].id; - if (salesIds) { + if (salesIds.length) { const salesFilter = { where: {id: {inq: salesIds}}, include: { @@ -91,16 +91,14 @@ module.exports = Self => { await models.SaleComponent.create(components, myOptions); } } - if (!refundTicket) { const servicesFilter = { where: {id: {inq: servicesIds}} }; const services = await models.TicketService.find(servicesFilter, myOptions); - const ticketsIds = [...new Set(services.map(service => service.ticketFk))]; + const firstTicketId = services[0].ticketFk; const now = Date.vnNew(); - const [firstTicketId] = ticketsIds; // eslint-disable-next-line max-len refundTicket = await createTicketRefund(firstTicketId, now, refundAgencyMode, refoundZoneId, withWarehouse, myOptions, ctx); @@ -114,8 +112,8 @@ module.exports = Self => { for (const service of services) { await models.TicketService.create({ description: service.description, - quantity: service.quantity, - price: - service.price, + quantity: - service.quantity, + price: service.price, taxClassFk: service.taxClassFk, ticketFk: refundTicket.id, ticketServiceTypeFk: service.ticketServiceTypeFk, diff --git a/modules/ticket/back/methods/sale/reserve.js b/modules/ticket/back/methods/sale/reserve.js index 2dc368af6..36db791fc 100644 --- a/modules/ticket/back/methods/sale/reserve.js +++ b/modules/ticket/back/methods/sale/reserve.js @@ -1,6 +1,3 @@ - -let UserError = require('vn-loopback/util/user-error'); - module.exports = Self => { Self.remoteMethodCtx('reserve', { description: 'Change the state of a ticket', @@ -65,7 +62,8 @@ module.exports = Self => { promises.push(reservedSale); - changesMade += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity}) ${$t('State')}: ${$t(oldState)} ➔ *${$t(newState)}*`; + changesMade += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity}) + ${$t('State')}: ${$t(oldState)} ➔ *${$t(newState)}*`; } } @@ -87,11 +85,11 @@ module.exports = Self => { const salesPerson = ticket.client().salesPersonUser(); if (salesPerson) { - const origin = ctx.req.headers.origin; + const url = await Self.app.models.Url.getUrl(); const message = $t('Changed sale reserved state', { ticketId: ticketId, - ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`, + ticketUrl: `${url}ticket/${ticketId}/sale`, changes: changesMade }); await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions); diff --git a/modules/ticket/back/methods/sale/specs/updateQuantity.spec.js b/modules/ticket/back/methods/sale/specs/updateQuantity.spec.js deleted file mode 100644 index 8064ea30b..000000000 --- a/modules/ticket/back/methods/sale/specs/updateQuantity.spec.js +++ /dev/null @@ -1,162 +0,0 @@ -const models = require('vn-loopback/server/server').models; -const LoopBackContext = require('loopback-context'); - -describe('sale updateQuantity()', () => { - beforeAll(async() => { - const activeCtx = { - accessToken: {userId: 9}, - http: { - req: { - headers: {origin: 'http://localhost'} - } - } - }; - spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ - active: activeCtx - }); - }); - - const ctx = { - req: { - accessToken: {userId: 9}, - headers: {origin: 'localhost:5000'}, - __: () => {} - } - }; - - it('should throw an error if the quantity is greater than it should be', async() => { - const ctx = { - req: { - accessToken: {userId: 1}, - headers: {origin: 'localhost:5000'}, - __: () => {} - } - }; - const tx = await models.Sale.beginTransaction({}); - - let error; - try { - const options = {transaction: tx}; - - await models.Sale.updateQuantity(ctx, 17, 99, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error).toEqual(new Error('The new quantity should be smaller than the old one')); - }); - - it('should add quantity if the quantity is greater than it should be and is role advanced', async() => { - const tx = await models.Sale.beginTransaction({}); - const saleId = 17; - const buyerId = 35; - const ctx = { - req: { - accessToken: {userId: buyerId}, - headers: {origin: 'localhost:5000'}, - __: () => {} - } - }; - - try { - const options = {transaction: tx}; - - const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*'); - - expect(isRoleAdvanced).toEqual(true); - - const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options); - - expect(originalLine.quantity).toEqual(30); - - const newQuantity = originalLine.quantity + 1; - await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); - - const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options); - - expect(modifiedLine.quantity).toEqual(newQuantity); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); - - it('should update the quantity of a given sale current line', async() => { - const tx = await models.Sale.beginTransaction({}); - const saleId = 25; - const newQuantity = 4; - - try { - const options = {transaction: tx}; - - const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options); - - expect(originalLine.quantity).toEqual(20); - - await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); - - const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options); - - expect(modifiedLine.quantity).toEqual(newQuantity); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); - - it('should throw an error if the quantity is negative and it is not a refund ticket', async() => { - const ctx = { - req: { - accessToken: {userId: 1}, - headers: {origin: 'localhost:5000'}, - __: () => {} - } - }; - const saleId = 17; - const newQuantity = -10; - - const tx = await models.Sale.beginTransaction({}); - - let error; - try { - const options = {transaction: tx}; - - await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - error = e; - } - - expect(error).toEqual(new Error('You can only add negative amounts in refund tickets')); - }); - - it('should update a negative quantity when is a ticket refund', async() => { - const tx = await models.Sale.beginTransaction({}); - const saleId = 13; - const newQuantity = -10; - - try { - const options = {transaction: tx}; - - await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); - - const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options); - - expect(modifiedLine.quantity).toEqual(newQuantity); - - await tx.rollback(); - } catch (e) { - await tx.rollback(); - throw e; - } - }); -}); diff --git a/modules/ticket/back/methods/sale/updatePrice.js b/modules/ticket/back/methods/sale/updatePrice.js index 62e4ebd42..191fd09e3 100644 --- a/modules/ticket/back/methods/sale/updatePrice.js +++ b/modules/ticket/back/methods/sale/updatePrice.js @@ -1,5 +1,3 @@ -let UserError = require('vn-loopback/util/user-error'); - module.exports = Self => { Self.remoteMethodCtx('updatePrice', { description: 'Changes the price of a sale', @@ -100,7 +98,7 @@ module.exports = Self => { const salesPerson = sale.ticket().client().salesPersonUser(); if (salesPerson) { - const origin = ctx.req.headers.origin; + const url = await Self.app.models.Url.getUrl(); const message = $t('Changed sale price', { ticketId: sale.ticket().id, itemId: sale.itemFk, @@ -108,8 +106,8 @@ module.exports = Self => { quantity: sale.quantity, oldPrice: oldPrice, newPrice: newPrice, - ticketUrl: `${origin}/#!/ticket/${sale.ticket().id}/sale`, - itemUrl: `${origin}/#!/item/${sale.itemFk}/summary` + ticketUrl: `${url}ticket/${sale.ticket().id}/sale`, + itemUrl: `${url}item/${sale.itemFk}/summary` }); await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions); } diff --git a/modules/ticket/back/methods/sale/updateQuantity.js b/modules/ticket/back/methods/sale/updateQuantity.js index edbc34e42..1a497da24 100644 --- a/modules/ticket/back/methods/sale/updateQuantity.js +++ b/modules/ticket/back/methods/sale/updateQuantity.js @@ -1,4 +1,3 @@ -let UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethodCtx('updateQuantity', { @@ -64,32 +63,22 @@ module.exports = Self => { const sale = await models.Sale.findById(id, filter, myOptions); - const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*'); - if (newQuantity > sale.quantity && !isRoleAdvanced) - throw new UserError('The new quantity should be smaller than the old one'); - - const ticketRefund = await models.TicketRefund.findOne({ - where: {refundTicketFk: sale.ticketFk}, - fields: ['id']} - , myOptions); - if (newQuantity < 0 && !ticketRefund) - throw new UserError('You can only add negative amounts in refund tickets'); - const oldQuantity = sale.quantity; const result = await sale.updateAttributes({quantity: newQuantity}, myOptions); const salesPerson = sale.ticket().client().salesPersonUser(); if (salesPerson) { - const origin = ctx.req.headers.origin; + const url = await Self.app.models.Url.getUrl(); const message = $t('Changed sale quantity', { ticketId: sale.ticket().id, itemId: sale.itemFk, concept: sale.concept, oldQuantity: oldQuantity, newQuantity: newQuantity, - ticketUrl: `${origin}/#!/ticket/${sale.ticket().id}/sale`, - itemUrl: `${origin}/#!/item/${sale.itemFk}/summary` + ticketUrl: `${url}ticket/${sale.ticket().id}/sale`, + itemUrl: `${url}item/${sale.itemFk}/summary` }); + await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions); } diff --git a/modules/ticket/back/methods/ticket-request/confirm.js b/modules/ticket/back/methods/ticket-request/confirm.js index 9cd7f4d68..00310f33c 100644 --- a/modules/ticket/back/methods/ticket-request/confirm.js +++ b/modules/ticket/back/methods/ticket-request/confirm.js @@ -84,7 +84,7 @@ module.exports = Self => { const query = `CALL vn.sale_calculateComponent(?, NULL)`; await Self.rawSql(query, [sale.id], myOptions); - const origin = ctx.req.headers.origin; + const url = await Self.app.models.Url.getUrl(); const requesterId = request.requesterFk; const message = $t('Bought units from buy request', { @@ -92,8 +92,8 @@ module.exports = Self => { concept: sale.concept, itemId: sale.itemFk, ticketId: sale.ticketFk, - url: `${origin}/#!/ticket/${sale.ticketFk}/summary`, - urlItem: `${origin}/#!/item/${sale.itemFk}/summary` + url: `${url}ticket/${sale.ticketFk}/summary`, + urlItem: `${url}item/${sale.itemFk}/summary` }); await models.Chat.sendCheckingPresence(ctx, requesterId, message, myOptions); diff --git a/modules/ticket/back/methods/ticket-request/deny.js b/modules/ticket/back/methods/ticket-request/deny.js index 92f020083..44f1e48a1 100644 --- a/modules/ticket/back/methods/ticket-request/deny.js +++ b/modules/ticket/back/methods/ticket-request/deny.js @@ -50,12 +50,12 @@ module.exports = Self => { const request = await Self.app.models.TicketRequest.findById(ctx.args.id, null, myOptions); await request.updateAttributes(params, myOptions); - const origin = ctx.req.headers.origin; + const url = await Self.app.models.Url.getUrl(); const requesterId = request.requesterFk; const message = $t('Deny buy request', { ticketId: request.ticketFk, - url: `${origin}/#!/ticket/${request.ticketFk}/request/index`, + url: `${url}ticket/${request.ticketFk}/request/index`, observation: params.response }); diff --git a/modules/ticket/back/methods/ticket-request/filter.js b/modules/ticket/back/methods/ticket-request/filter.js index f27ea5018..10aaf02e5 100644 --- a/modules/ticket/back/methods/ticket-request/filter.js +++ b/modules/ticket/back/methods/ticket-request/filter.js @@ -99,6 +99,8 @@ module.exports = Self => { switch (value) { case 'pending': return {'tr.isOk': null}; + case 'accepted': + return {'tr.isOk': 1}; default: return {'tr.isOk': value}; } @@ -122,8 +124,7 @@ module.exports = Self => { filter = mergeFilters(filter, {where}); const stmt = new ParameterizedSQL( - `SELECT - tr.id, + `SELECT tr.id, tr.ticketFk, tr.quantity, tr.price, @@ -133,18 +134,19 @@ module.exports = Self => { tr.saleFk, tr.requesterFk, tr.isOk, - s.quantity AS saleQuantity, + s.quantity saleQuantity, s.itemFk, - i.name AS itemDescription, + i.name itemDescription, t.shipped, - DATE(t.shipped) AS shippedDate, + DATE(t.shipped) shippedDate, t.nickname, t.warehouseFk, t.clientFk, - w.name AS warehouse, - u.nickname AS salesPersonNickname, - ua.name AS attenderName, - c.salesPersonFk + w.name warehouse, + u.nickname salesPersonNickname, + ua.name attenderName, + c.salesPersonFk, + ua2.name requesterName FROM ticketRequest tr LEFT JOIN ticketWeekly tw on tw.ticketFk = tr.ticketFk LEFT JOIN ticket t ON t.id = tr.ticketFk @@ -155,7 +157,8 @@ module.exports = Self => { LEFT JOIN worker wk ON wk.id = c.salesPersonFk LEFT JOIN account.user u ON u.id = wk.id LEFT JOIN worker wka ON wka.id = tr.attenderFk - LEFT JOIN account.user ua ON ua.id = wka.id`); + LEFT JOIN account.user ua ON ua.id = wka.id + LEFT JOIN account.user ua2 ON ua2.id = tr.requesterFk`); stmt.merge(conn.makeSuffix(filter)); return conn.executeStmt(stmt, myOptions); diff --git a/modules/ticket/back/methods/ticket/addSale.js b/modules/ticket/back/methods/ticket/addSale.js index cbf884273..3455ec2c4 100644 --- a/modules/ticket/back/methods/ticket/addSale.js +++ b/modules/ticket/back/methods/ticket/addSale.js @@ -63,17 +63,6 @@ module.exports = Self => { } }, myOptions); - const itemInfo = await models.Item.getVisibleAvailable( - itemId, - ticket.warehouseFk, - ticket.shipped, - myOptions - ); - - const isPackaging = item.family == 'EMB'; - if (!isPackaging && itemInfo.available < quantity) - throw new UserError(`This item is not available`); - const newSale = await models.Sale.create({ ticketFk: id, itemFk: item.id, @@ -94,11 +83,11 @@ module.exports = Self => { const salesPerson = ticket.client().salesPersonUser(); if (salesPerson) { - const origin = ctx.req.headers.origin; + const url = await Self.app.models.Url.getUrl(); const message = $t('Added sale to ticket', { ticketId: id, - ticketUrl: `${origin}/#!/ticket/${id}/sale`, + ticketUrl: `${url}ticket/${id}/sale`, addition: addition }); await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message); diff --git a/modules/ticket/back/methods/ticket/componentUpdate.js b/modules/ticket/back/methods/ticket/componentUpdate.js index 8aad8959b..f7c36f108 100644 --- a/modules/ticket/back/methods/ticket/componentUpdate.js +++ b/modules/ticket/back/methods/ticket/componentUpdate.js @@ -237,7 +237,7 @@ module.exports = Self => { const salesPersonId = originalTicket.client().salesPersonFk; if (salesPersonId) { - const origin = ctx.req.headers.origin; + const url = await Self.app.models.Url.getUrl(); let changesMade = ''; for (let change in newProperties) { @@ -249,7 +249,7 @@ module.exports = Self => { const message = $t('Changed this data from the ticket', { ticketId: args.id, - ticketUrl: `${origin}/#!/ticket/${args.id}/sale`, + ticketUrl: `${url}ticket/${args.id}/sale`, changes: changesMade }); await models.Chat.sendCheckingPresence(ctx, salesPersonId, message); diff --git a/modules/ticket/back/methods/ticket/merge.js b/modules/ticket/back/methods/ticket/merge.js index 3c0da077f..1106cef06 100644 --- a/modules/ticket/back/methods/ticket/merge.js +++ b/modules/ticket/back/methods/ticket/merge.js @@ -25,7 +25,7 @@ module.exports = Self => { Self.merge = async(ctx, tickets, options) => { const httpRequest = ctx.req; const $t = httpRequest.__; - const origin = httpRequest.headers.origin; + const url = await Self.app.models.Url.getUrl(); const models = Self.app.models; const myOptions = {}; let tx; @@ -40,8 +40,8 @@ module.exports = Self => { try { for (let ticket of tickets) { - const originFullPath = `${origin}/#!/ticket/${ticket.originId}/summary`; - const destinationFullPath = `${origin}/#!/ticket/${ticket.destinationId}/summary`; + const originFullPath = `${url}ticket/${ticket.originId}/summary`; + const destinationFullPath = `${url}ticket/${ticket.destinationId}/summary`; const message = $t('Ticket merged', { originDated: dateUtil.toString(new Date(ticket.originShipped)), destinationDated: dateUtil.toString(new Date(ticket.destinationShipped)), diff --git a/modules/ticket/back/methods/ticket/restore.js b/modules/ticket/back/methods/ticket/restore.js index 722c3294e..e268c3891 100644 --- a/modules/ticket/back/methods/ticket/restore.js +++ b/modules/ticket/back/methods/ticket/restore.js @@ -48,10 +48,10 @@ module.exports = Self => { // Send notification to salesPerson const salesPersonId = ticket.client().salesPersonFk; if (salesPersonId) { - const origin = ctx.req.headers.origin; + const url = await Self.app.models.Url.getUrl(); const message = $t(`I have restored the ticket id`, { id: id, - url: `${origin}/#!/ticket/${id}/summary` + url: `${url}ticket/${id}/summary` }); await models.Chat.sendCheckingPresence(ctx, salesPersonId, message); } diff --git a/modules/ticket/back/methods/ticket/setDeleted.js b/modules/ticket/back/methods/ticket/setDeleted.js index fcd9972fe..9a9fd9056 100644 --- a/modules/ticket/back/methods/ticket/setDeleted.js +++ b/modules/ticket/back/methods/ticket/setDeleted.js @@ -119,10 +119,10 @@ module.exports = Self => { // Send notification to salesPerson const salesPersonUser = ticket.client().salesPersonUser(); if (salesPersonUser && sales.length) { - const origin = ctx.req.headers.origin; + const url = await Self.app.models.Url.getUrl(); const message = $t(`I have deleted the ticket id`, { id: id, - url: `${origin}/#!/ticket/${id}/summary` + url: `${url}ticket/${id}/summary` }); await models.Chat.send(ctx, `@${salesPersonUser.name}`, message); } @@ -146,7 +146,8 @@ module.exports = Self => { JOIN vn.sectorCollection sc ON sc.id = scsg.sectorCollectionFk JOIN vn.saleGroupDetail sgd ON sgd.saleGroupFk = sg.id JOIN vn.sale s ON s.id = sgd.saleFk - WHERE s.ticketFk = ?;`, [ticket.id], myOptions); + WHERE s.ticketFk = ?;`, [ticket.id], myOptions + ); if (tx) await tx.commit(); diff --git a/modules/ticket/back/methods/ticket/updateDiscount.js b/modules/ticket/back/methods/ticket/updateDiscount.js index e092ee4ed..2e8bec27a 100644 --- a/modules/ticket/back/methods/ticket/updateDiscount.js +++ b/modules/ticket/back/methods/ticket/updateDiscount.js @@ -165,11 +165,10 @@ module.exports = Self => { const salesPerson = ticket.client().salesPersonUser(); if (salesPerson) { - const origin = ctx.req.headers.origin; - + const url = await Self.app.models.Url.getUrl(); const message = $t('Changed sale discount', { ticketId: id, - ticketUrl: `${origin}/#!/ticket/${id}/sale`, + ticketUrl: `${url}ticket/${id}/sale`, changes: changesMade }); await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message, myOptions); diff --git a/modules/ticket/back/models/sale.js b/modules/ticket/back/models/sale.js index ae247fc24..1c86ddc0c 100644 --- a/modules/ticket/back/models/sale.js +++ b/modules/ticket/back/models/sale.js @@ -1,3 +1,6 @@ +const UserError = require('vn-loopback/util/user-error'); +const LoopBackContext = require('loopback-context'); + module.exports = Self => { require('../methods/sale/getClaimableFromTicket')(Self); require('../methods/sale/reserve')(Self); @@ -13,4 +16,100 @@ module.exports = Self => { Self.validatesPresenceOf('concept', { message: `Concept cannot be blank` }); + + Self.observe('before save', async ctx => { + const models = Self.app.models; + const changes = ctx.data || ctx.instance; + const instance = ctx.currentInstance; + + const newQuantity = changes?.quantity; + if (newQuantity == null) return; + + const loopBackContext = LoopBackContext.getCurrentContext(); + ctx.req = loopBackContext.active; + if (await models.ACL.checkAccessAcl(ctx, 'Sale', 'canForceQuantity', 'WRITE')) return; + + const ticketId = changes?.ticketFk || instance?.ticketFk; + const itemId = changes?.itemFk || instance?.itemFk; + const oldQuantity = instance?.quantity ?? null; + const quantityAdded = newQuantity - oldQuantity; + + const ticket = await models.Ticket.findById( + ticketId, + { + fields: ['id', 'clientFk', 'warehouseFk', 'addressFk', 'agencyModeFk', 'shipped', 'landed'], + include: { + relation: 'client', + scope: { + fields: ['id', 'clientTypeFk'], + include: { + relation: 'type', + scope: { + fields: ['code', 'description'] + } + } + } + } + }, + ctx.options); + if (ticket?.client()?.type()?.code === 'loses') return; + + const isRefund = await models.TicketRefund.findOne({ + fields: ['id'], + where: {refundTicketFk: ticketId} + }, ctx.options); + if (isRefund) return; + + if (newQuantity < 0) + throw new UserError('You can only add negative amounts in refund tickets'); + + const item = await models.Item.findOne({ + fields: ['family', 'minQuantity'], + where: {id: itemId}, + }, ctx.options); + if (item.family == 'EMB') return; + + if (await models.ACL.checkAccessAcl(ctx, 'Sale', 'isInPreparing', '*')) return; + + await models.Sale.rawSql(`CALL catalog_calcFromItem(?,?,?,?)`, [ + ticket.landed, + ticket.addressFk, + ticket.agencyModeFk, + itemId + ], + ctx.options); + + const [itemInfo] = await models.Sale.rawSql(`SELECT available FROM tmp.ticketCalculateItem`, null, ctx.options); + + if (!itemInfo?.available || itemInfo.available < quantityAdded) + throw new UserError(`This item is not available`); + + if (await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*')) return; + + if (newQuantity < item.minQuantity && newQuantity != itemInfo?.available) + throw new UserError('The amount cannot be less than the minimum'); + + if (ctx.isNewInstance || newQuantity <= oldQuantity) return; + + const [saleGrouping] = await models.Sale.rawSql(` + SELECT t.price newPrice + FROM tmp.ticketComponentPrice t + ORDER BY (t.grouping <= ?) DESC, t.grouping ASC + LIMIT 1`, + [quantityAdded], + ctx.options); + + await models.Sale.rawSql(` + DROP TEMPORARY TABLE IF EXISTS + tmp.ticketCalculateItem, + tmp.ticketComponentPrice, + tmp.ticketComponent, + tmp.ticketLot, + tmp.zoneGetShipped; + `, null, ctx.options); + + if (!saleGrouping?.newPrice || saleGrouping.newPrice > instance.price) + throw new UserError('The price of the item changed'); + }); }; + diff --git a/modules/ticket/back/models/specs/sale.spec.js b/modules/ticket/back/models/specs/sale.spec.js new file mode 100644 index 000000000..4af44c991 --- /dev/null +++ b/modules/ticket/back/models/specs/sale.spec.js @@ -0,0 +1,361 @@ +/* eslint max-len: ["error", { "code": 150 }]*/ + +const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); + +describe('sale model ', () => { + const ctx = { + req: { + accessToken: {userId: 9}, + headers: {origin: 'localhost:5000'}, + __: () => {} + } + }; + function getActiveCtx(userId) { + return { + active: { + accessToken: {userId}, + http: { + req: { + headers: {origin: 'http://localhost'} + } + } + } + }; + } + + describe('quantity field ', () => { + it('should add quantity if the quantity is greater than it should be and is role advanced', async() => { + const saleId = 17; + const buyerId = 35; + const ctx = { + req: { + accessToken: {userId: buyerId}, + headers: {origin: 'localhost:5000'}, + __: () => {} + } + }; + const tx = await models.Sale.beginTransaction({}); + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(buyerId)); + spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => { + if (sqlStatement.includes('catalog_calcFromItem')) { + sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY + SELECT 100 as available;`; + params = null; + } + return models.Ticket.rawSql(sqlStatement, params, options); + }); + + try { + const options = {transaction: tx}; + + const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*'); + + expect(isRoleAdvanced).toEqual(true); + + const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options); + + expect(originalLine.quantity).toEqual(30); + + const newQuantity = originalLine.quantity + 1; + await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); + + const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options); + + expect(modifiedLine.quantity).toEqual(newQuantity); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should update the quantity of a given sale current line', async() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(9)); + + const tx = await models.Sale.beginTransaction({}); + const saleId = 25; + const newQuantity = 4; + + try { + const options = {transaction: tx}; + + const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options); + + expect(originalLine.quantity).toEqual(20); + + await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); + + const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options); + + expect(modifiedLine.quantity).toEqual(newQuantity); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should throw an error if the quantity is negative and it is not a refund ticket', async() => { + const ctx = { + req: { + accessToken: {userId: 1}, + headers: {origin: 'localhost:5000'}, + __: () => {} + } + }; + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1)); + + const saleId = 17; + const newQuantity = -10; + + const tx = await models.Sale.beginTransaction({}); + + let error; + try { + const options = {transaction: tx}; + + await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error).toEqual(new Error('You can only add negative amounts in refund tickets')); + }); + + it('should update a negative quantity when is a ticket refund', async() => { + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(9)); + + const tx = await models.Sale.beginTransaction({}); + const saleId = 13; + const newQuantity = -10; + + try { + const options = {transaction: tx}; + + await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); + + const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options); + + expect(modifiedLine.quantity).toEqual(newQuantity); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should throw an error if the quantity is less than the minimum quantity of the item', async() => { + const ctx = { + req: { + accessToken: {userId: 1}, + headers: {origin: 'localhost:5000'}, + __: () => {} + } + }; + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1)); + + const tx = await models.Sale.beginTransaction({}); + const itemId = 2; + const saleId = 17; + const minQuantity = 30; + const newQuantity = minQuantity - 1; + + let error; + try { + const options = {transaction: tx}; + + const item = await models.Item.findById(itemId, null, options); + await item.updateAttribute('minQuantity', minQuantity, options); + spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => { + if (sqlStatement.includes('catalog_calcFromItem')) { + sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY + SELECT 100 as available;`; + params = null; + } + return models.Ticket.rawSql(sqlStatement, params, options); + }); + + await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error).toEqual(new Error('The amount cannot be less than the minimum')); + }); + + it('should change quantity if has minimum quantity and new quantity is equal than item available', async() => { + const ctx = { + req: { + accessToken: {userId: 1}, + headers: {origin: 'localhost:5000'}, + __: () => {} + } + }; + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1)); + + const tx = await models.Sale.beginTransaction({}); + const itemId = 2; + const saleId = 17; + const minQuantity = 30; + const newQuantity = minQuantity - 1; + + try { + const options = {transaction: tx}; + + const item = await models.Item.findById(itemId, null, options); + await item.updateAttribute('minQuantity', minQuantity, options); + + spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => { + if (sqlStatement.includes('catalog_calcFromItem')) { + sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY + SELECT ${newQuantity} as available;`; + params = null; + } + return models.Ticket.rawSql(sqlStatement, params, options); + }); + + await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + describe('newPrice', () => { + it('should increase quantity if you have enough available and the new price is the same as the previous one', async() => { + const ctx = { + req: { + accessToken: {userId: 1}, + headers: {origin: 'localhost:5000'}, + __: () => {} + } + }; + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1)); + + const tx = await models.Sale.beginTransaction({}); + const itemId = 2; + const saleId = 17; + const minQuantity = 30; + const newQuantity = 31; + + try { + const options = {transaction: tx}; + + const item = await models.Item.findById(itemId, null, options); + await item.updateAttribute('minQuantity', minQuantity, options); + spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => { + if (sqlStatement.includes('catalog_calcFromItem')) { + sqlStatement = ` + CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY SELECT ${newQuantity} as available; + CREATE OR REPLACE TEMPORARY TABLE tmp.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 7.07 as price;`; + params = null; + } + return models.Ticket.rawSql(sqlStatement, params, options); + }); + + await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should increase quantity when the new price is lower than the previous one', async() => { + const ctx = { + req: { + accessToken: {userId: 1}, + headers: {origin: 'localhost:5000'}, + __: () => {} + } + }; + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1)); + + const tx = await models.Sale.beginTransaction({}); + const itemId = 2; + const saleId = 17; + const minQuantity = 30; + const newQuantity = 31; + + try { + const options = {transaction: tx}; + + const item = await models.Item.findById(itemId, null, options); + await item.updateAttribute('minQuantity', minQuantity, options); + spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => { + if (sqlStatement.includes('catalog_calcFromItem')) { + sqlStatement = ` + CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY SELECT ${newQuantity} as available; + CREATE OR REPLACE TEMPORARY TABLE tmp.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 1 as price;`; + params = null; + } + return models.Ticket.rawSql(sqlStatement, params, options); + }); + + await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should throw error when increase quantity and the new price is higher than the previous one', async() => { + const ctx = { + req: { + accessToken: {userId: 1}, + headers: {origin: 'localhost:5000'}, + __: () => {} + } + }; + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1)); + + const tx = await models.Sale.beginTransaction({}); + const itemId = 2; + const saleId = 17; + const minQuantity = 30; + const newQuantity = 31; + + let error; + try { + const options = {transaction: tx}; + + const item = await models.Item.findById(itemId, null, options); + await item.updateAttribute('minQuantity', minQuantity, options); + spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => { + if (sqlStatement.includes('catalog_calcFromItem')) { + sqlStatement = ` + CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY SELECT ${newQuantity} as available; + CREATE OR REPLACE TEMPORARY TABLE tmp.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 100000 as price;`; + params = null; + } + return models.Ticket.rawSql(sqlStatement, params, options); + }); + + await models.Sale.updateQuantity(ctx, saleId, newQuantity, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + error = e; + } + + expect(error).toEqual(new Error('The price of the item changed')); + }); + }); + }); +}); diff --git a/modules/ticket/front/advance-search-panel/index.js b/modules/ticket/front/advance-search-panel/index.js index 8ddbe78d4..218fb14d3 100644 --- a/modules/ticket/front/advance-search-panel/index.js +++ b/modules/ticket/front/advance-search-panel/index.js @@ -16,8 +16,8 @@ class Controller extends SearchPanel { this.$http.get('ItemPackingTypes', {filter}).then(res => { for (let ipt of res.data) { itemPackingTypes.push({ - code: ipt.code, - description: this.$t(ipt.description) + description: this.$t(ipt.description), + code: ipt.code }); } this.itemPackingTypes = itemPackingTypes; diff --git a/modules/ticket/front/advance/index.js b/modules/ticket/front/advance/index.js index 0cec41227..6f8a92ebe 100644 --- a/modules/ticket/front/advance/index.js +++ b/modules/ticket/front/advance/index.js @@ -163,26 +163,16 @@ export default class Controller extends Section { return {'futureId': value}; case 'liters': return {'liters': value}; - case 'lines': - return {'lines': value}; case 'futureLiters': return {'futureLiters': value}; + case 'lines': + return {'lines': value}; case 'futureLines': return {'futureLines': value}; case 'ipt': - return {or: - [ - {'ipt': {like: `%${value}%`}}, - {'ipt': null} - ] - }; + return {'ipt': {like: `%${value}%`}}; case 'futureIpt': - return {or: - [ - {'futureIpt': {like: `%${value}%`}}, - {'futureIpt': null} - ] - }; + return {'futureIpt': {like: `%${value}%`}}; case 'totalWithVat': return {'totalWithVat': value}; case 'futureTotalWithVat': diff --git a/modules/ticket/front/index/locale/es.yml b/modules/ticket/front/index/locale/es.yml index afa3d654e..89828d4d9 100644 --- a/modules/ticket/front/index/locale/es.yml +++ b/modules/ticket/front/index/locale/es.yml @@ -18,3 +18,4 @@ Multiple invoice: Factura múltiple Make invoice...: Crear factura... Invoice selected tickets: Facturar tickets seleccionados Are you sure to invoice tickets: ¿Seguro que quieres facturar {{ticketsAmount}} tickets? +Rounding: Redondeo diff --git a/modules/ticket/front/sale/index.html b/modules/ticket/front/sale/index.html index b10df317b..729822764 100644 --- a/modules/ticket/front/sale/index.html +++ b/modules/ticket/front/sale/index.html @@ -311,7 +311,7 @@ clear-disabled="true" suffix="%"> - + { - this.currentWorkerMana = res.data; - }); } getUsesMana() { diff --git a/modules/ticket/front/sale/index.spec.js b/modules/ticket/front/sale/index.spec.js index 9da8e6e7c..b36e78893 100644 --- a/modules/ticket/front/sale/index.spec.js +++ b/modules/ticket/front/sale/index.spec.js @@ -120,12 +120,10 @@ describe('Ticket', () => { const expectedAmount = 250; $httpBackend.expect('GET', 'Tickets/1/getSalesPersonMana').respond(200, expectedAmount); $httpBackend.expect('GET', 'Sales/usesMana').respond(200); - $httpBackend.expect('GET', 'WorkerManas/getCurrentWorkerMana').respond(200, expectedAmount); controller.getMana(); $httpBackend.flush(); expect(controller.edit.mana).toEqual(expectedAmount); - expect(controller.currentWorkerMana).toEqual(expectedAmount); }); }); diff --git a/modules/ticket/front/services/index.html b/modules/ticket/front/services/index.html index bc288a8a2..5128873c4 100644 --- a/modules/ticket/front/services/index.html +++ b/modules/ticket/front/services/index.html @@ -29,7 +29,7 @@ disabled="watcher.dataChanged() || !$ctrl.checkeds.length" label="Pay" ng-click="$ctrl.createRefund()" - vn-acl="invoicing, claimManager, salesAssistant" + vn-acl="invoicing, claimManager, salesAssistant, buyer" vn-acl-action="remove"> @@ -37,7 +37,9 @@ + disabled="!service.id" + vn-acl="invoicing, claimManager, salesAssistant, buyer" + vn-acl-action="remove"> + step="0.01" + min="0"> { s.nickname AS cargoSupplierNickname, s.name AS supplierName, CAST(SUM(b.weight * b.stickers) as DECIMAL(10,0)) as loadedKg, - CAST(SUM(vc.aerealVolumetricDensity * b.stickers * IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000) as DECIMAL(10,0)) as volumeKg + CAST( + SUM( + vc.aerealVolumetricDensity * + b.stickers * + IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000 + ) as DECIMAL(10,0) + ) as volumeKg FROM travel t LEFT JOIN supplier s ON s.id = t.cargoSupplierFk LEFT JOIN entry e ON e.travelFk = t.id LEFT JOIN buy b ON b.entryFk = e.id - LEFT JOIN packaging pkg ON pkg.id = b.packageFk + LEFT JOIN packaging pkg ON pkg.id = b.packagingFk LEFT JOIN item i ON i.id = b.itemFk LEFT JOIN itemType it ON it.id = i.typeFk JOIN warehouse w ON w.id = t.warehouseInFk @@ -169,11 +175,17 @@ module.exports = Self => { e.evaNotes, e.invoiceAmount, CAST(SUM(b.weight * b.stickers) AS DECIMAL(10,0)) as loadedkg, - CAST(SUM(vc.aerealVolumetricDensity * b.stickers * IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000) AS DECIMAL(10,0)) as volumeKg + CAST( + SUM( + vc.aerealVolumetricDensity * + b.stickers * + IF(pkg.volume, pkg.volume, pkg.width * pkg.depth * pkg.height) / 1000000 + ) AS DECIMAL(10,0) + ) as volumeKg FROM tmp.travel tr JOIN entry e ON e.travelFk = tr.id JOIN buy b ON b.entryFk = e.id - JOIN packaging pkg ON pkg.id = b.packageFk + JOIN packaging pkg ON pkg.id = b.packagingFk JOIN item i ON i.id = b.itemFk JOIN itemType it ON it.id = i.typeFk JOIN supplier s ON s.id = e.supplierFk diff --git a/modules/travel/back/methods/travel/getEntries.js b/modules/travel/back/methods/travel/getEntries.js index 71bb0d8fb..50088ccfa 100644 --- a/modules/travel/back/methods/travel/getEntries.js +++ b/modules/travel/back/methods/travel/getEntries.js @@ -47,7 +47,7 @@ module.exports = Self => { LEFT JOIN vn.buy b ON b.entryFk = e.id LEFT JOIN vn.supplier s ON e.supplierFk = s.id JOIN vn.item i ON i.id = b.itemFk - LEFT JOIN vn.packaging p ON p.id = b.packageFk + LEFT JOIN vn.packaging p ON p.id = b.packagingFk JOIN vn.packaging pcc ON pcc.id = 'cc' JOIN vn.packaging ppallet ON ppallet.id = 'pallet 100' JOIN vn.packagingConfig pconfig diff --git a/modules/worker/back/methods/worker-mana/getCurrentWorkerMana.js b/modules/worker/back/methods/worker-mana/getCurrentWorkerMana.js deleted file mode 100644 index fa34af475..000000000 --- a/modules/worker/back/methods/worker-mana/getCurrentWorkerMana.js +++ /dev/null @@ -1,26 +0,0 @@ -module.exports = Self => { - Self.remoteMethodCtx('getCurrentWorkerMana', { - description: 'Returns the mana of the logged worker', - accessType: 'READ', - accepts: [], - returns: { - type: 'number', - root: true - }, - http: { - path: `/getCurrentWorkerMana`, - verb: 'GET' - } - }); - - Self.getCurrentWorkerMana = async ctx => { - let userId = ctx.req.accessToken.userId; - - let workerMana = await Self.app.models.WorkerMana.findOne({ - where: {workerFk: userId}, - fields: 'amount' - }); - - return workerMana ? workerMana.amount : 0; - }; -}; diff --git a/modules/worker/back/methods/worker-mana/specs/getCurrentWorkerMana.spec.js b/modules/worker/back/methods/worker-mana/specs/getCurrentWorkerMana.spec.js deleted file mode 100644 index 8d626e720..000000000 --- a/modules/worker/back/methods/worker-mana/specs/getCurrentWorkerMana.spec.js +++ /dev/null @@ -1,15 +0,0 @@ -const app = require('vn-loopback/server/server'); - -describe('workerMana getCurrentWorkerMana()', () => { - it('should get the mana of the logged worker', async() => { - let mana = await app.models.WorkerMana.getCurrentWorkerMana({req: {accessToken: {userId: 18}}}); - - expect(mana).toEqual(124); - }); - - it('should return 0 if the user doesnt uses mana', async() => { - let mana = await app.models.WorkerMana.getCurrentWorkerMana({req: {accessToken: {userId: 9}}}); - - expect(mana).toEqual(0); - }); -}); diff --git a/modules/worker/back/methods/worker-time-control/sendMail.js b/modules/worker/back/methods/worker-time-control/sendMail.js index 2c5143612..6f67bbea3 100644 --- a/modules/worker/back/methods/worker-time-control/sendMail.js +++ b/modules/worker/back/methods/worker-time-control/sendMail.js @@ -66,46 +66,36 @@ module.exports = Self => { stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.timeControlCalculate'); stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.timeBusinessCalculate'); + const destroyAllWhere = { + timed: {between: [started, ended]}, + isSendMail: true + }; + const updateAllWhere = { + year: args.year, + week: args.week + }; + + const tmpUserSQL = ` + CREATE OR REPLACE TEMPORARY TABLE tmp.user + SELECT id as userFk + FROM vn.worker`; + let tmpUser = new ParameterizedSQL(tmpUserSQL); + if (args.workerId) { - await models.WorkerTimeControl.destroyAll({ - userFk: args.workerId, - timed: {between: [started, ended]}, - isSendMail: true - }, myOptions); - - const where = { - workerFk: args.workerId, - year: args.year, - week: args.week - }; - await models.WorkerTimeControlMail.updateAll(where, { - updated: Date.vnNew(), state: 'SENDED' - }, myOptions); - - stmt = new ParameterizedSQL('DROP TEMPORARY TABLE IF EXISTS tmp.`user`'); - stmts.push(stmt); - stmt = new ParameterizedSQL('CREATE TEMPORARY TABLE tmp.`user` SELECT id userFk FROM account.user WHERE id = ?', [args.workerId]); - stmts.push(stmt); - } else { - await models.WorkerTimeControl.destroyAll({ - timed: {between: [started, ended]}, - isSendMail: true - }, myOptions); - - const where = { - year: args.year, - week: args.week - }; - await models.WorkerTimeControlMail.updateAll(where, { - updated: Date.vnNew(), state: 'SENDED' - }, myOptions); - - stmt = new ParameterizedSQL('DROP TEMPORARY TABLE IF EXISTS tmp.`user`'); - stmts.push(stmt); - stmt = new ParameterizedSQL('CREATE TEMPORARY TABLE IF NOT EXISTS tmp.`user` SELECT id as userFk FROM vn.worker w JOIN account.`user` u ON u.id = w.id WHERE id IS NOT NULL'); - stmts.push(stmt); + destroyAllWhere.userFk = args.workerId; + updateAllWhere.workerFk = args.workerId; + tmpUser = new ParameterizedSQL(tmpUserSQL + ' WHERE id = ?', [args.workerId]); } + await models.WorkerTimeControl.destroyAll(destroyAllWhere, myOptions); + + await models.WorkerTimeControlMail.updateAll(updateAllWhere, { + updated: Date.vnNew(), + state: 'SENDED' + }, myOptions); + + stmts.push(tmpUser); + stmt = new ParameterizedSQL( `CALL vn.timeControl_calculate(?, ?) `, [started, ended]); diff --git a/modules/worker/back/methods/worker/createAbsence.js b/modules/worker/back/methods/worker/createAbsence.js index cb2cf8330..d628d0a2b 100644 --- a/modules/worker/back/methods/worker/createAbsence.js +++ b/modules/worker/back/methods/worker/createAbsence.js @@ -109,13 +109,13 @@ module.exports = Self => { const absenceType = await models.AbsenceType.findById(args.absenceTypeId, null, myOptions); const account = await models.VnUser.findById(userId, null, myOptions); const subordinated = await models.VnUser.findById(id, null, myOptions); - const origin = ctx.req.headers.origin; + const url = await Self.app.models.Url.getUrl(); const body = $t('Created absence', { author: account.nickname, employee: subordinated.nickname, absenceType: absenceType.name, dated: formatDate(args.dated), - workerUrl: `${origin}/#!/worker/${id}/calendar` + workerUrl: `${url}worker/${id}/calendar` }); await models.Mail.create({ subject: $t('Absence change notification on the labour calendar'), diff --git a/modules/worker/back/methods/worker/deleteAbsence.js b/modules/worker/back/methods/worker/deleteAbsence.js index c315f5178..b71d077a4 100644 --- a/modules/worker/back/methods/worker/deleteAbsence.js +++ b/modules/worker/back/methods/worker/deleteAbsence.js @@ -60,13 +60,13 @@ module.exports = Self => { const absenceType = await models.AbsenceType.findById(absence.dayOffTypeFk, null, myOptions); const account = await models.VnUser.findById(userId, null, myOptions); const subordinated = await models.VnUser.findById(labour.workerFk, null, myOptions); - const origin = ctx.req.headers.origin; + const url = await Self.app.models.Url.getUrl(); const body = $t('Deleted absence', { author: account.nickname, employee: subordinated.nickname, absenceType: absenceType.name, dated: formatDate(absence.dated), - workerUrl: `${origin}/#!/worker/${id}/calendar` + workerUrl: `${url}worker/${id}/calendar` }); await models.Mail.create({ subject: $t('Absence change notification on the labour calendar'), diff --git a/modules/worker/back/methods/worker/search.js b/modules/worker/back/methods/worker/search.js index cd0a466ea..7fe9e0666 100644 --- a/modules/worker/back/methods/worker/search.js +++ b/modules/worker/back/methods/worker/search.js @@ -46,7 +46,7 @@ module.exports = Self => { SELECT DISTINCT w.id, w.code, u.name, u.nickname, u.active, b.departmentFk FROM worker w JOIN account.user u ON u.id = w.id - JOIN business b ON b.workerFk = w.id + LEFT JOIN business b ON b.workerFk = w.id ) w`); stmt.merge(conn.makeSuffix(filter)); diff --git a/modules/worker/back/methods/worker/setPassword.js b/modules/worker/back/methods/worker/setPassword.js new file mode 100644 index 000000000..43d3d946f --- /dev/null +++ b/modules/worker/back/methods/worker/setPassword.js @@ -0,0 +1,48 @@ +const UserError = require('vn-loopback/util/user-error'); +module.exports = Self => { + Self.remoteMethodCtx('setPassword', { + description: 'Set a new password', + accepts: [ + { + arg: 'workerFk', + type: 'number', + required: true, + description: 'The worker id', + }, + { + arg: 'newPass', + type: 'String', + required: true, + description: 'The new worker password' + } + ], + http: { + path: `/:id/setPassword`, + verb: 'PATCH' + } + }); + Self.setPassword = async(ctx, options) => { + const models = Self.app.models; + const myOptions = {}; + const {args} = ctx; + let tx; + if (typeof options == 'object') + Object.assign(myOptions, options); + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + try { + const isSubordinate = await models.Worker.isSubordinate(ctx, args.workerFk, myOptions); + if (!isSubordinate) throw new UserError('You don\'t have enough privileges.'); + + await models.VnUser.setPassword(args.workerFk, args.newPass, myOptions); + await models.VnUser.updateAll({id: args.workerFk}, {emailVerified: true}, myOptions); + + if (tx) await tx.commit(); + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/worker/back/methods/worker/specs/setPassword.spec.js b/modules/worker/back/methods/worker/specs/setPassword.spec.js new file mode 100644 index 000000000..fbb403b24 --- /dev/null +++ b/modules/worker/back/methods/worker/specs/setPassword.spec.js @@ -0,0 +1,61 @@ +const UserError = require('vn-loopback/util/user-error'); + +const models = require('vn-loopback/server/server').models; + +describe('worker setPassword()', () => { + let ctx; + beforeAll(() => { + ctx = { + req: { + accessToken: {}, + headers: {origin: 'http://localhost'} + }, + args: {workerFk: 9} + }; + }); + + beforeEach(() => { + ctx.req.accessToken.userId = 20; + ctx.args.newPass = 'H3rn4d3z#'; + }); + + it('should change the password', async() => { + const tx = await models.Worker.beginTransaction({}); + + try { + const options = {transaction: tx}; + await models.Worker.setPassword(ctx, options); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should throw an error: Password does not meet requirements', async() => { + const tx = await models.Collection.beginTransaction({}); + ctx.args.newPass = 'Hi'; + try { + const options = {transaction: tx}; + await models.Worker.setPassword(ctx, options); + await tx.rollback(); + } catch (e) { + expect(e.sqlMessage).toEqual('Password does not meet requirements'); + await tx.rollback(); + } + }); + + it('should throw an error: You don\'t have enough privileges.', async() => { + ctx.req.accessToken.userId = 5; + const tx = await models.Collection.beginTransaction({}); + try { + const options = {transaction: tx}; + await models.Worker.setPassword(ctx, options); + await tx.rollback(); + } catch (e) { + expect(e).toEqual(new UserError(`You don't have enough privileges.`)); + await tx.rollback(); + } + }); +}); diff --git a/modules/worker/back/models/worker-mana.js b/modules/worker/back/models/worker-mana.js deleted file mode 100644 index 99a0f7694..000000000 --- a/modules/worker/back/models/worker-mana.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = Self => { - require('../methods/worker-mana/getCurrentWorkerMana')(Self); -}; diff --git a/modules/worker/back/models/worker.js b/modules/worker/back/models/worker.js index ccae3a6e6..985d83e9f 100644 --- a/modules/worker/back/models/worker.js +++ b/modules/worker/back/models/worker.js @@ -18,6 +18,7 @@ module.exports = Self => { require('../methods/worker/allocatePDA')(Self); require('../methods/worker/search')(Self); require('../methods/worker/isAuthorized')(Self); + require('../methods/worker/setPassword')(Self); Self.validatesUniquenessOf('locker', { message: 'This locker has already been assigned' diff --git a/modules/worker/front/card/index.js b/modules/worker/front/card/index.js index b8b533c5d..9a40e31c2 100644 --- a/modules/worker/front/card/index.js +++ b/modules/worker/front/card/index.js @@ -8,7 +8,7 @@ class Controller extends ModuleCard { { relation: 'user', scope: { - fields: ['name'], + fields: ['name', 'emailVerified'], include: { relation: 'emailUser', scope: { diff --git a/modules/worker/front/descriptor/index.html b/modules/worker/front/descriptor/index.html index 758f639ff..aa6b80300 100644 --- a/modules/worker/front/descriptor/index.html +++ b/modules/worker/front/descriptor/index.html @@ -11,6 +11,9 @@ ? 'Click to allow the user to be disabled' : 'Click to exclude the user from getting disabled'}} + + Change password +
@@ -72,4 +75,29 @@ - + + + + + + + + + + + + diff --git a/modules/worker/front/descriptor/index.js b/modules/worker/front/descriptor/index.js index 07e16c0d6..13ffa6f2f 100644 --- a/modules/worker/front/descriptor/index.js +++ b/modules/worker/front/descriptor/index.js @@ -1,5 +1,6 @@ import ngModule from '../module'; import Descriptor from 'salix/components/descriptor'; +const UserError = require('vn-loopback/util/user-error'); class Controller extends Descriptor { constructor($element, $, $rootScope) { super($element, $); @@ -12,9 +13,11 @@ class Controller extends Descriptor { set worker(value) { this.entity = value; - if (value) this.getIsExcluded(); + + if (this.entity && !this.entity.user.emailVerified) + this.getPassRequirements(); } getIsExcluded() { @@ -38,7 +41,7 @@ class Controller extends Descriptor { { relation: 'user', scope: { - fields: ['name'], + fields: ['name', 'emailVerified'], include: { relation: 'emailUser', scope: { @@ -66,10 +69,29 @@ class Controller extends Descriptor { } ] }; - return this.getData(`Workers/${this.id}`, {filter}) .then(res => this.entity = res.data); } + + getPassRequirements() { + this.$http.get('UserPasswords/findOne') + .then(res => { + this.passRequirements = res.data; + }); + } + + setPassword() { + if (!this.newPassword) + throw new UserError(`You must enter a new password`); + if (this.newPassword != this.repeatPassword) + throw new UserError(`Passwords don't match`); + this.$http.patch( + `Workers/${this.entity.id}/setPassword`, + {workerFk: this.entity.id, newPass: this.newPassword} + ) .then(() => { + this.vnApp.showSuccess(this.$translate.instant('Password changed!')); + }); + } } Controller.$inject = ['$element', '$scope', '$rootScope']; diff --git a/modules/worker/front/descriptor/index.spec.js b/modules/worker/front/descriptor/index.spec.js index dfb800415..d158a9e8e 100644 --- a/modules/worker/front/descriptor/index.spec.js +++ b/modules/worker/front/descriptor/index.spec.js @@ -23,4 +23,24 @@ describe('vnWorkerDescriptor', () => { expect(controller.worker).toEqual(response); }); }); + + describe('setPassword()', () => { + it('should throw an error: You must enter a new password', () => { + try { + controller.setPassword(); + } catch (error) { + expect(error.message).toEqual('You must enter a new password'); + } + }); + + it('should throw an error: Passwords don\'t match', () => { + controller.newPassword = 'aaa'; + controller.repeatPassword = 'bbb'; + try { + controller.setPassword(); + } catch (error) { + expect(error.message).toEqual('Passwords don\'t match'); + } + }); + }); }); diff --git a/modules/zone/back/methods/zone/toggleIsIncluded.js b/modules/zone/back/methods/zone/toggleIsIncluded.js index bf8c86f46..98c64c4a0 100644 --- a/modules/zone/back/methods/zone/toggleIsIncluded.js +++ b/modules/zone/back/methods/zone/toggleIsIncluded.js @@ -30,18 +30,21 @@ module.exports = Self => { Self.toggleIsIncluded = async(id, geoId, isIncluded, options) => { const models = Self.app.models; const myOptions = {}; - if (typeof options == 'object') Object.assign(myOptions, options); if (isIncluded === undefined) return models.ZoneIncluded.destroyAll({zoneFk: id, geoFk: geoId}, myOptions); - else { - return models.ZoneIncluded.upsert({ - zoneFk: id, - geoFk: geoId, - isIncluded: isIncluded - }, myOptions); - } + + const zoneIncluded = await models.ZoneIncluded.findOne({where: {zoneFk: id, geoFk: geoId}}, myOptions); + + if (zoneIncluded) + return zoneIncluded.updateAttribute('isIncluded', isIncluded, myOptions); + + return models.ZoneIncluded.create({ + zoneFk: id, + geoFk: geoId, + isIncluded: isIncluded + }, myOptions); }; }; diff --git a/modules/zone/back/models/zone-included.json b/modules/zone/back/models/zone-included.json index 61633a3c7..deba73f34 100644 --- a/modules/zone/back/models/zone-included.json +++ b/modules/zone/back/models/zone-included.json @@ -7,8 +7,8 @@ } }, "properties": { - "zoneFk": { - "id": true, + "id": { + "id": true, "type": "number" }, "isIncluded": { diff --git a/print/templates/reports/delivery-note/delivery-note.html b/print/templates/reports/delivery-note/delivery-note.html index 0be5a30f0..92dd1b126 100644 --- a/print/templates/reports/delivery-note/delivery-note.html +++ b/print/templates/reports/delivery-note/delivery-note.html @@ -117,7 +117,7 @@ {{service.price | currency('EUR', $i18n.locale)}} {{service.taxDescription}} - {{service.price | currency('EUR', $i18n.locale)}} + {{service.total | currency('EUR', $i18n.locale)}} diff --git a/print/templates/reports/delivery-note/sql/services.sql b/print/templates/reports/delivery-note/sql/services.sql index d64e8dc26..ec8a3e7ac 100644 --- a/print/templates/reports/delivery-note/sql/services.sql +++ b/print/templates/reports/delivery-note/sql/services.sql @@ -1,8 +1,9 @@ SELECT tc.code taxDescription, ts.description, - ts.quantity, - ts.price + ts.quantity, + ts.price, + ts.quantity * ts.price total FROM ticketService ts JOIN taxClass tc ON tc.id = ts.taxClassFk WHERE ts.ticketFk = ? \ No newline at end of file