diff --git a/CHANGELOG.md b/CHANGELOG.md index cf7d8465a..dde790aaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,16 +5,24 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2312.01] - 2023-04-06 + +### Added +- + +### Changed +- + +### Fixed +- + ## [2310.01] - 2023-03-23 ### Added -- - -### Changed -- +- (Trabajadores -> Control de horario) Ahora se puede confirmar/no confirmar el registro horario de cada semana desde esta sección ### Fixed -- (Clientes -> Listado extendido) Resuelto error al filtrar por clientes inactivos desde la columna "Activo" +- (Clientes -> Listado extendido) Resuelto error al filtrar por clientes inactivos desde la columna "Activo" - (General) Al pasar el ratón por encima del icono de "Borrar" en un campo, se hacía más grande afectando a la interfaz ## [2308.01] - 2023-03-09 diff --git a/Dockerfile b/Dockerfile index a59725f77..ee87cd0d0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,6 +10,7 @@ RUN apt-get update \ curl \ ca-certificates \ gnupg2 \ + graphicsmagick \ && curl -fsSL https://deb.nodesource.com/setup_14.x | bash - \ && apt-get install -y --no-install-recommends nodejs \ && npm install -g npm@8.19.2 diff --git a/back/methods/collection/previousLabel.js b/back/methods/collection/previousLabel.js index fb2df8133..e3dac1ab4 100644 --- a/back/methods/collection/previousLabel.js +++ b/back/methods/collection/previousLabel.js @@ -1,5 +1,3 @@ -const {Report} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('previousLabel', { description: 'Returns the previa label pdf', @@ -33,17 +31,5 @@ module.exports = Self => { } }); - Self.previousLabel = async(ctx, id) => { - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const report = new Report('previa-label', params); - const stream = await report.toPdfStream(); - - return [stream, 'application/pdf', `filename="previa-${id}.pdf"`]; - }; + Self.previousLabel = (ctx, id) => Self.printReport(ctx, id, 'previa-label'); }; diff --git a/back/methods/dms/saveSign.js b/back/methods/dms/saveSign.js deleted file mode 100644 index ed462a301..000000000 --- a/back/methods/dms/saveSign.js +++ /dev/null @@ -1,215 +0,0 @@ -const md5 = require('md5'); -const fs = require('fs-extra'); - -module.exports = Self => { - Self.remoteMethodCtx('saveSign', { - description: 'Save sign', - accessType: 'WRITE', - accepts: - [ - { - arg: 'signContent', - type: 'string', - required: true, - description: 'The sign content' - }, { - arg: 'tickets', - type: ['number'], - required: true, - description: 'The tickets' - }, { - arg: 'signedTime', - type: 'date', - description: 'The signed time' - }, { - arg: 'addressFk', - type: 'number', - required: true, - description: 'The address fk' - } - ], - returns: { - type: 'Object', - root: true - }, - http: { - path: `/saveSign`, - verb: 'POST' - } - }); - - async function createGestDoc(ticketId, userFk) { - const models = Self.app.models; - if (!await gestDocExists(ticketId)) { - const result = await models.Ticket.findOne({ - where: { - id: ticketId - }, - include: [ - { - relation: 'warehouse', - scope: { - fields: ['id'] - } - }, { - relation: 'client', - scope: { - fields: ['name'] - } - }, { - relation: 'route', - scope: { - fields: ['id'] - } - } - ] - }); - - const warehouseFk = result.warehouseFk; - const companyFk = result.companyFk; - const client = result.client.name; - const route = result.route.id; - - const resultDmsType = await models.DmsType.findOne({ - where: { - code: 'Ticket' - } - }); - - const resultDms = await models.Dms.create({ - dmsTypeFk: resultDmsType.id, - reference: ticketId, - description: `Ticket ${ticketId} Cliente ${client} Ruta ${route}`, - companyFk: companyFk, - warehouseFk: warehouseFk, - workerFk: userFk - }); - - return resultDms.insertId; - } - } - - async function gestDocExists(ticket) { - const models = Self.app.models; - const result = await models.TicketDms.findOne({ - where: { - ticketFk: ticket - }, - fields: ['dmsFk'] - }); - - if (result == null) - return false; - - const isSigned = await models.Ticket.findOne({ - where: { - id: ticket - }, - fields: ['isSigned'] - }); - - if (isSigned) - return true; - else - await models.Dms.destroyById(ticket); - } - - async function dmsRecover(ticket, signContent) { - const models = Self.app.models; - await models.DmsRecover.create({ - ticketFk: ticket, - sign: signContent - }); - } - - async function ticketGestdoc(ticket, dmsFk) { - const models = Self.app.models; - models.TicketDms.replaceOrCreate({ - ticketFk: ticket, - dmsFk: dmsFk - }); - - const queryVnTicketSetState = `CALL vn.ticket_setState(?, ?)`; - - await Self.rawSql(queryVnTicketSetState, [ticket, 'DELIVERED']); - } - - async function updateGestdoc(file, ticket) { - const models = Self.app.models; - models.Dms.updateOne({ - where: { - id: ticket - }, - file: file, - contentType: 'image/png' - }); - } - - Self.saveSign = async(ctx, signContent, tickets, signedTime) => { - const models = Self.app.models; - let tx = await Self.beginTransaction({}); - try { - const userId = ctx.req.accessToken.userId; - - const dmsDir = `storage/dms`; - - let image = null; - - for (let i = 0; i < tickets.length; i++) { - const alertLevel = await models.TicketState.findOne({ - where: { - ticketFk: tickets[i] - }, - fields: ['alertLevel'] - }); - - signedTime ? signedTime != undefined : signedTime = Date.vnNew(); - - if (alertLevel >= 2) { - let dir; - let id = null; - let fileName = null; - - if (!await gestDocExists(tickets[i])) { - id = await createGestDoc(tickets[i], userId); - - const hashDir = md5(id).substring(0, 3); - dir = `${dmsDir}/${hashDir}`; - - if (!fs.existsSync(dir)) - fs.mkdirSync(dir); - - fileName = `${id}.png`; - image = `${dir}/${fileName}`; - } else - - if (image != null) { - if (!fs.existsSync(dir)) - dmsRecover(tickets[i], signContent); - else { - fs.writeFile(image, signContent, 'base64', async function(err) { - if (err) { - await tx.rollback(); - return err.message; - } - }); - } - } else - dmsRecover(tickets[i], signContent); - - if (id != null && fileName.length > 0) { - ticketGestdoc(tickets[i], id); - updateGestdoc(id, fileName); - } - } - } - - if (tx) await tx.commit(); - - return 'OK'; - } catch (err) { - await tx.rollback(); - throw err.message; - } - }; -}; diff --git a/back/models/delivery.json b/back/models/delivery.json index 65a0eef1b..c66c31b45 100644 --- a/back/models/delivery.json +++ b/back/models/delivery.json @@ -9,17 +9,29 @@ "properties": { "id": { "id": true, - "type": "number", - "forceId": false + "type": "number" }, - "date": { + "created": { "type": "date" }, - "m3":{ + "longitude":{ "type": "number" }, - "warehouseFk":{ + "latitude":{ + "type": "number" + }, + "dated":{ + "type": "date" + }, + "ticketFk":{ "type": "number" } - } + }, + "relations": { + "ticket": { + "type": "belongsTo", + "model": "Ticket", + "foreignKey": "ticketFk" + } + } } diff --git a/back/models/dms.js b/back/models/dms.js index fc586201f..24c072f56 100644 --- a/back/models/dms.js +++ b/back/models/dms.js @@ -6,7 +6,6 @@ module.exports = Self => { require('../methods/dms/removeFile')(Self); require('../methods/dms/updateFile')(Self); require('../methods/dms/deleteTrashFiles')(Self); - require('../methods/dms/saveSign')(Self); Self.checkRole = async function(ctx, id) { const models = Self.app.models; diff --git a/db/changes/231001/00-delivery.sql b/db/changes/231001/00-delivery.sql new file mode 100644 index 000000000..3a9269183 --- /dev/null +++ b/db/changes/231001/00-delivery.sql @@ -0,0 +1,74 @@ +DROP TABLE `vn`.`dmsRecover`; + +ALTER TABLE `vn`.`delivery` DROP FOREIGN KEY delivery_FK; +ALTER TABLE `vn`.`delivery` DROP COLUMN addressFk; +ALTER TABLE `vn`.`delivery` ADD ticketFk INT NOT NULL; +ALTER TABLE `vn`.`delivery` ADD CONSTRAINT delivery_ticketFk_FK FOREIGN KEY (`ticketFk`) REFERENCES `vn`.`ticket`(`id`); + +DELETE FROM `salix`.`ACL` WHERE `property` = 'saveSign'; +INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`) + VALUES + ('Ticket','saveSign','WRITE','ALLOW','employee'); + +DROP PROCEDURE IF EXISTS vn.route_getTickets; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`route_getTickets`(vRouteFk INT) +BEGIN +/** + * Pasado un RouteFk devuelve la información + * de sus tickets. + * + * @param vRouteFk + * + * @select Información de los tickets + */ + + SELECT + t.id Id, + t.clientFk Client, + a.id Address, + t.packages Packages, + a.street AddressName, + a.postalCode PostalCode, + a.city City, + sub2.itemPackingTypeFk PackingType, + c.phone ClientPhone, + c.mobile ClientMobile, + a.phone AddressPhone, + a.mobile AddressMobile, + d.longitude Longitude, + d.latitude Latitude, + wm.mediaValue SalePersonPhone, + tob.Note Note, + t.isSigned Signed + FROM ticket t + JOIN client c ON t.clientFk = c.id + JOIN address a ON t.addressFk = a.id + LEFT JOIN delivery d ON t.id = d.ticketFk + LEFT JOIN workerMedia wm ON wm.workerFk = c.salesPersonFk + LEFT JOIN + (SELECT tob.description Note, t.id + FROM ticketObservation tob + JOIN ticket t ON tob.ticketFk = t.id + JOIN observationType ot ON ot.id = tob.observationTypeFk + WHERE t.routeFk = vRouteFk + AND ot.code = 'delivery' + )tob ON tob.id = t.id + LEFT JOIN + (SELECT sub.ticketFk, + CONCAT('(', GROUP_CONCAT(DISTINCT sub.itemPackingTypeFk ORDER BY sub.items DESC SEPARATOR ','), ') ') itemPackingTypeFk + FROM (SELECT s.ticketFk , i.itemPackingTypeFk, COUNT(*) items + FROM ticket t + JOIN sale s ON s.ticketFk = t.id + JOIN item i ON i.id = s.itemFk + WHERE t.routeFk = vRouteFk + GROUP BY t.id,i.itemPackingTypeFk)sub + GROUP BY sub.ticketFk + ) sub2 ON sub2.ticketFk = t.id + WHERE t.routeFk = vRouteFk + GROUP BY t.id + ORDER BY t.priority; +END$$ +DELIMITER ; diff --git a/db/changes/231001/.gitkeep b/db/changes/231201/.gitkeep similarity index 100% rename from db/changes/231001/.gitkeep rename to db/changes/231201/.gitkeep diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index a0c72d726..3ab34e1d5 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -2825,4 +2825,11 @@ INSERT INTO `vn`.`deviceProductionUser` (`deviceProductionFk`, `userFk`, `create (1, 1, util.VN_NOW()), (3, 3, util.VN_NOW()); +INSERT INTO `vn`.`workerTimeControlMail` (`id`, `workerFk`, `year`, `week`, `state`, `updated`, `sendedCounter`, `reason`) + VALUES + (1, 9, 2000, 49, 'REVISE', util.VN_NOW(), 1, 'test2'), + (2, 9, 2000, 50, 'SENDED', util.VN_NOW(), 1, NULL), + (3, 9, 2000, 51, 'CONFIRMED', util.VN_NOW(), 1, NULL), + (4, 9, 2001, 1, 'SENDED', util.VN_NOW(), 1, NULL); + diff --git a/db/dump/structure.sql b/db/dump/structure.sql index 63754d536..90e4c4bc9 100644 --- a/db/dump/structure.sql +++ b/db/dump/structure.sql @@ -81220,3 +81220,4 @@ USE `vn`; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2023-02-21 8:14:30 + diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 2ace69567..f20d75310 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -524,7 +524,7 @@ export default { }, itemLog: { anyLineCreated: 'vn-item-log > vn-log vn-tbody > vn-tr', - fifthLineCreatedProperty: 'vn-item-log > vn-log vn-tbody > vn-tr:nth-child(5) table tr:nth-child(2) td.after', + fifthLineCreatedProperty: 'vn-item-log > vn-log vn-tbody > vn-tr:nth-child(5) table tr:nth-child(4) td.after', }, ticketSummary: { header: 'vn-ticket-summary > vn-card > h5', diff --git a/e2e/paths/04-item/10_item_log.spec.js b/e2e/paths/04-item/10_item_log.spec.js index 2a885fe6f..46979a761 100644 --- a/e2e/paths/04-item/10_item_log.spec.js +++ b/e2e/paths/04-item/10_item_log.spec.js @@ -59,6 +59,6 @@ describe('Item log path', () => { const fifthLineCreatedProperty = await page .waitToGetProperty(selectors.itemLog.fifthLineCreatedProperty, 'innerText'); - expect(fifthLineCreatedProperty).toEqual('Coral y materiales similares'); + expect(fifthLineCreatedProperty).toEqual('05080000'); }); }); diff --git a/e2e/paths/11-zone/02_descriptor.spec.js b/e2e/paths/11-zone/02_descriptor.spec.js index 1de84d601..12a1c8f68 100644 --- a/e2e/paths/11-zone/02_descriptor.spec.js +++ b/e2e/paths/11-zone/02_descriptor.spec.js @@ -37,6 +37,6 @@ describe('Zone descriptor path', () => { await page.accessToSection('ticket.card.log'); const lastChanges = await page.waitToGetProperty(selectors.ticketLog.changes, 'innerText'); - expect(lastChanges).toContain('Arreglar'); + expect(lastChanges).toContain('1'); }); }); diff --git a/front/core/components/calendar/index.html b/front/core/components/calendar/index.html index 086fe4338..4d02f9ec0 100644 --- a/front/core/components/calendar/index.html +++ b/front/core/components/calendar/index.html @@ -24,7 +24,7 @@
{{::day.localeChar}}
@@ -57,4 +57,4 @@
- \ No newline at end of file + diff --git a/front/core/components/calendar/index.js b/front/core/components/calendar/index.js index 17ccbf041..0e39267a7 100644 --- a/front/core/components/calendar/index.js +++ b/front/core/components/calendar/index.js @@ -15,9 +15,9 @@ export default class Calendar extends FormInput { constructor($element, $scope, vnWeekDays, moment) { super($element, $scope); this.weekDays = vnWeekDays.locales; - this.defaultDate = Date.vnNew(); this.displayControls = true; this.moment = moment; + this.defaultDate = Date.vnNew(); } /** @@ -207,14 +207,23 @@ export default class Calendar extends FormInput { } repeatLast() { - if (!this.formatDay) return; + if (this.formatDay) { + const days = this.element.querySelectorAll('.days > .day'); + for (let i = 0; i < days.length; i++) { + this.formatDay({ + $day: this.days[i], + $element: days[i] + }); + } + } - let days = this.element.querySelectorAll('.days > .day'); - for (let i = 0; i < days.length; i++) { - this.formatDay({ - $day: this.days[i], - $element: days[i] - }); + if (this.formatWeek) { + const weeks = this.element.querySelectorAll('.weeks > .day'); + for (const week of weeks) { + this.formatWeek({ + $element: week + }); + } } } } @@ -228,6 +237,7 @@ ngModule.vnComponent('vnCalendar', { hasEvents: '&?', getClass: '&?', formatDay: '&?', + formatWeek: '&?', displayControls: ' { + Self.printReport = async function(ctx, id, reportName) { + const args = Object.assign({}, ctx.args); + const params = {lang: ctx.req.getLocale()}; + + delete args.ctx; + for (const param in args) + params[param] = args[param]; + + const report = new Report(reportName, params); + const stream = await report.toPdfStream(); + + let fileName = `${reportName}`; + if (id) fileName += `-${id}`; + + return [stream, 'application/pdf', `filename="${fileName}.pdf"`]; + }; + + Self.printEmail = async function(ctx, id, templateName) { + const {accessToken} = ctx.req; + const args = Object.assign({}, ctx.args); + const params = {lang: ctx.req.getLocale()}; + + delete args.ctx; + for (const param in args) + params[param] = args[param]; + + params.isPreview = true; + params.access_token = accessToken.id; + + const report = new Email(templateName, params); + const html = await report.render(); + + let fileName = `${templateName}`; + if (id) fileName += `-${id}`; + + return [html, 'text/html', `filename=${fileName}.pdf"`]; + }; + + Self.sendTemplate = async function(ctx, templateName) { + const args = Object.assign({}, ctx.args); + const params = { + recipient: args.recipient, + lang: ctx.req.getLocale() + }; + + delete args.ctx; + for (const param in args) + params[param] = args[param]; + + const email = new Email(templateName, params); + + return email.send(); + }; +}; diff --git a/loopback/common/models/loggable.js b/loopback/common/models/loggable.js index 28a6075d0..360c84566 100644 --- a/loopback/common/models/loggable.js +++ b/loopback/common/models/loggable.js @@ -1,4 +1,3 @@ -const pick = require('object.pick'); const LoopBackContext = require('loopback-context'); module.exports = function(Self) { @@ -6,344 +5,11 @@ module.exports = function(Self) { Self.super_.setup.call(this); }; - Self.observe('after save', async function(ctx) { - const loopBackContext = LoopBackContext.getCurrentContext(); - await logInModel(ctx, loopBackContext); - }); - Self.observe('before save', async function(ctx) { - const appModels = ctx.Model.app.models; - const definition = ctx.Model.definition; - const options = {}; - - // Check for transactions - if (ctx.options && ctx.options.transaction) - options.transaction = ctx.options.transaction; - - let oldInstance; - let newInstance; - - if (ctx.data) { - const changes = pick(ctx.currentInstance, Object.keys(ctx.data)); - newInstance = ctx.data; - oldInstance = changes; - - if (ctx.where && !ctx.currentInstance) { - const fields = Object.keys(ctx.data); - const modelName = definition.name; - - ctx.oldInstances = await appModels[modelName].find({ - where: ctx.where, - fields: fields - }, options); - } - } - - // Get changes from created instance - if (ctx.isNewInstance) - newInstance = ctx.instance.__data; - - ctx.hookState.oldInstance = oldInstance; - ctx.hookState.newInstance = newInstance; + ctx.options.httpCtx = LoopBackContext.getCurrentContext(); }); Self.observe('before delete', async function(ctx) { - const appModels = ctx.Model.app.models; - const definition = ctx.Model.definition; - const relations = ctx.Model.relations; - - let options = {}; - if (ctx.options && ctx.options.transaction) - options.transaction = ctx.options.transaction; - - if (ctx.where) { - let affectedModel = definition.name; - let deletedInstances = await appModels[affectedModel].find({ - where: ctx.where - }, options); - - let relation = definition.settings.log.relation; - - if (relation) { - let primaryKey = relations[relation].keyFrom; - - let arrangedDeletedInstances = []; - for (let i = 0; i < deletedInstances.length; i++) { - if (primaryKey) - deletedInstances[i].originFk = deletedInstances[i][primaryKey]; - let arrangedInstance = await fkToValue(deletedInstances[i], ctx); - arrangedDeletedInstances[i] = arrangedInstance; - } - ctx.hookState.oldInstance = arrangedDeletedInstances; - } - } + ctx.options.httpCtx = LoopBackContext.getCurrentContext(); }); - - Self.observe('after delete', async function(ctx) { - const loopBackContext = LoopBackContext.getCurrentContext(); - if (ctx.hookState.oldInstance) - logDeletedInstances(ctx, loopBackContext); - }); - - async function logDeletedInstances(ctx, loopBackContext) { - const appModels = ctx.Model.app.models; - const definition = ctx.Model.definition; - let options = {}; - if (ctx.options && ctx.options.transaction) - options.transaction = ctx.options.transaction; - - ctx.hookState.oldInstance.forEach(async instance => { - let userFk; - if (loopBackContext) - userFk = loopBackContext.active.accessToken.userId; - - let changedModelValue = definition.settings.log.changedModelValue; - let logRecord = { - originFk: instance.originFk, - userFk: userFk, - action: 'delete', - changedModel: definition.name, - changedModelId: instance.id, - changedModelValue: instance[changedModelValue], - oldInstance: instance, - newInstance: {} - }; - - delete instance.originFk; - - let logModel = definition.settings.log.model; - await appModels[logModel].create(logRecord, options); - }); - } - - // Get log values from a foreign key - async function fkToValue(instance, ctx) { - const appModels = ctx.Model.app.models; - const relations = ctx.Model.relations; - let options = {}; - - // Check for transactions - if (ctx.options && ctx.options.transaction) - options.transaction = ctx.options.transaction; - - const instanceCopy = JSON.parse(JSON.stringify(instance)); - const result = {}; - for (const key in instanceCopy) { - let value = instanceCopy[key]; - - if (value instanceof Object) - continue; - - if (value === undefined) continue; - - if (value) { - for (let relationName in relations) { - const relation = relations[relationName]; - if (relation.keyFrom == key && key != 'id') { - const model = relation.modelTo; - const modelName = relation.modelTo.modelName; - const properties = model && model.definition.properties; - const settings = model && model.definition.settings; - - const recordSet = await appModels[modelName].findById(value, null, options); - - const hasShowField = settings.log && settings.log.showField; - let showField = hasShowField && recordSet - && recordSet[settings.log.showField]; - - if (!showField) { - const showFieldNames = [ - 'name', - 'description', - 'code', - 'nickname' - ]; - for (field of showFieldNames) { - const propField = properties && properties[field]; - const recordField = recordSet && recordSet[field]; - - if (propField && recordField) { - showField = field; - break; - } - } - } - - if (showField && recordSet && recordSet[showField]) { - value = recordSet[showField]; - break; - } - - value = recordSet && recordSet.id || value; - break; - } - } - } - result[key] = value; - } - return result; - } - - async function logInModel(ctx, loopBackContext) { - const appModels = ctx.Model.app.models; - const definition = ctx.Model.definition; - const defSettings = ctx.Model.definition.settings; - const relations = ctx.Model.relations; - - const options = {}; - if (ctx.options && ctx.options.transaction) - options.transaction = ctx.options.transaction; - - let primaryKey; - for (let property in definition.properties) { - if (definition.properties[property].id) { - primaryKey = property; - break; - } - } - - if (!primaryKey) throw new Error('Primary key not found'); - let originId; - - // RELATIONS LOG - let changedModelId; - - if (ctx.instance && !defSettings.log.relation) { - originId = ctx.instance.id; - changedModelId = ctx.instance.id; - } else if (defSettings.log.relation) { - primaryKey = relations[defSettings.log.relation].keyFrom; - - if (ctx.where && ctx.where[primaryKey]) - originId = ctx.where[primaryKey]; - else if (ctx.instance) { - originId = ctx.instance[primaryKey]; - changedModelId = ctx.instance.id; - } - } else { - originId = ctx.currentInstance.id; - changedModelId = ctx.currentInstance.id; - } - - // Sets the changedModelValue to save and the instances changed in case its an updateAll - let showField = defSettings.log.showField; - let where; - if (showField && (!ctx.instance || !ctx.instance[showField]) && ctx.where) { - changedModelId = []; - where = []; - let changedInstances = await appModels[definition.name].find({ - where: ctx.where, - fields: ['id', showField, primaryKey] - }, options); - - changedInstances.forEach(element => { - where.push(element[showField]); - changedModelId.push(element.id); - originId = element[primaryKey]; - }); - } else if (ctx.hookState.oldInstance) - where = ctx.instance[showField]; - - // Set oldInstance, newInstance, userFk and action - let oldInstance = {}; - if (ctx.hookState.oldInstance) - Object.assign(oldInstance, ctx.hookState.oldInstance); - - let newInstance = {}; - if (ctx.hookState.newInstance) - Object.assign(newInstance, ctx.hookState.newInstance); - let userFk; - if (loopBackContext) - userFk = loopBackContext.active.accessToken.userId; - - let action = setActionType(ctx); - - removeUnloggable(definition, oldInstance); - removeUnloggable(definition, newInstance); - - oldInstance = await fkToValue(oldInstance, ctx); - newInstance = await fkToValue(newInstance, ctx); - - // Prevent log with no new changes - const hasNewChanges = Object.keys(newInstance).length; - if (!hasNewChanges) return; - - let logRecord = { - originFk: originId, - userFk: userFk, - action: action, - changedModel: definition.name, - changedModelId: changedModelId, // Model property with an different data type will throw a NaN error - changedModelValue: where, - oldInstance: oldInstance, - newInstance: newInstance - }; - - let logsToSave = setLogsToSave(where, changedModelId, logRecord, ctx); - let logModel = defSettings.log.model; - - await appModels[logModel].create(logsToSave, options); - } - - /** - * Removes unwanted properties - * @param {*} definition Model definition - * @param {*} properties Modified object properties - */ - function removeUnloggable(definition, properties) { - const objectCopy = Object.assign({}, properties); - const propList = Object.keys(objectCopy); - const propDefs = new Map(); - - for (let property in definition.properties) { - const propertyDef = definition.properties[property]; - - propDefs.set(property, propertyDef); - } - - for (let property of propList) { - const propertyDef = propDefs.get(property); - const firstChar = property.substring(0, 1); - const isPrivate = firstChar == '$'; - - if (isPrivate || !propertyDef) - delete properties[property]; - - if (!propertyDef) continue; - - if (propertyDef.log === false || isPrivate) - delete properties[property]; - else if (propertyDef.logValue === false) - properties[property] = null; - } - } - - // this function retuns all the instances changed in case this is an updateAll - function setLogsToSave(changedInstances, changedInstancesIds, logRecord, ctx) { - let promises = []; - if (changedInstances && typeof changedInstances == 'object') { - for (let i = 0; i < changedInstances.length; i++) { - logRecord.changedModelId = changedInstancesIds[i]; - logRecord.changedModelValue = changedInstances[i]; - if (ctx.oldInstances) - logRecord.oldInstance = ctx.oldInstances[i]; - promises.push(JSON.parse(JSON.stringify(logRecord))); - } - } else - return logRecord; - - return promises; - } - - function setActionType(ctx) { - let oldInstance = ctx.hookState.oldInstance; - let newInstance = ctx.hookState.newInstance; - - if (oldInstance && newInstance) - return 'update'; - else if (!oldInstance && newInstance) - return 'insert'; - - return 'delete'; - } }; diff --git a/loopback/common/models/vn-model.js b/loopback/common/models/vn-model.js index cc3eede8e..cebd37eac 100644 --- a/loopback/common/models/vn-model.js +++ b/loopback/common/models/vn-model.js @@ -7,6 +7,7 @@ module.exports = function(Self) { require('../methods/vn-model/getSetValues')(Self); require('../methods/vn-model/getEnumValues')(Self); + require('../methods/vn-model/printService')(Self); Object.assign(Self, { setup() { diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 788c2fa96..95bf16d66 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -270,5 +270,6 @@ "Warehouse inventory not set": "El almacén inventario no está establecido", "This locker has already been assigned": "Esta taquilla ya ha sido asignada", "Tickets with associated refunds": "No se pueden borrar tickets con abonos asociados. Este ticket está asociado al abono Nº {{id}}", - "Not exist this branch": "La rama no existe" + "Not exist this branch": "La rama no existe", + "This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado" } diff --git a/loopback/server/connectors/vn-mysql.js b/loopback/server/connectors/vn-mysql.js index fde0ddcf6..5c1ceaa32 100644 --- a/loopback/server/connectors/vn-mysql.js +++ b/loopback/server/connectors/vn-mysql.js @@ -2,8 +2,41 @@ const mysql = require('mysql'); const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; const MySQL = require('loopback-connector-mysql').MySQL; const EnumFactory = require('loopback-connector-mysql').EnumFactory; +const Transaction = require('loopback-connector').Transaction; const fs = require('fs'); +const limitSet = new Set([ + 'save', + 'updateOrCreate', + 'replaceOrCreate', + 'replaceById', + 'update' +]); + +const opOpts = { + update: [ + 'update', + 'replaceById', + // |insert + 'save', + 'updateOrCreate', + 'replaceOrCreate' + ], + delete: [ + 'destroy', + 'destroyAll' + ], + insert: [ + 'create' + ] +}; + +const opMap = new Map(); +for (const op in opOpts) { + for (const met of opOpts[op]) + opMap.set(met, op); +} + class VnMySQL extends MySQL { /** * Promisified version of execute(). @@ -219,6 +252,277 @@ class VnMySQL extends MySQL { this.makePagination(filter) ]); } + + create(model, data, opts, cb) { + const ctx = {data}; + this.invokeMethod('create', + arguments, model, ctx, opts, cb); + } + + createAll(model, data, opts, cb) { + const ctx = {data}; + this.invokeMethod('createAll', + arguments, model, ctx, opts, cb); + } + + save(model, data, opts, cb) { + const ctx = {data}; + this.invokeMethod('save', + arguments, model, ctx, opts, cb); + } + + updateOrCreate(model, data, opts, cb) { + const ctx = {data}; + this.invokeMethod('updateOrCreate', + arguments, model, ctx, opts, cb); + } + + replaceOrCreate(model, data, opts, cb) { + const ctx = {data}; + this.invokeMethod('replaceOrCreate', + arguments, model, ctx, opts, cb); + } + + destroyAll(model, where, opts, cb) { + const ctx = {where}; + this.invokeMethod('destroyAll', + arguments, model, ctx, opts, cb); + } + + update(model, where, data, opts, cb) { + const ctx = {where, data}; + this.invokeMethod('update', + arguments, model, ctx, opts, cb); + } + + replaceById(model, id, data, opts, cb) { + const ctx = {id, data}; + this.invokeMethod('replaceById', + arguments, model, ctx, opts, cb); + } + + isLoggable(model) { + const Model = this.getModelDefinition(model).model; + const settings = Model.definition.settings; + return settings.base && settings.base === 'Loggable'; + } + + invokeMethod(method, args, model, ctx, opts, cb) { + if (!this.isLoggable(model)) + return super[method].apply(this, args); + + this.invokeMethodP(method, [...args], model, ctx, opts) + .then(res => cb(...res), cb); + } + + async invokeMethodP(method, args, model, ctx, opts) { + const Model = this.getModelDefinition(model).model; + const settings = Model.definition.settings; + let tx; + if (!opts.transaction) { + tx = await Transaction.begin(this, {}); + opts = Object.assign({transaction: tx, httpCtx: opts.httpCtx}, opts); + } + + try { + // Fetch old values (update|delete) or login + let where, id, data, idName, limit, op, oldInstances, newInstances; + const hasGrabUser = settings.log && settings.log.grabUser; + if(hasGrabUser){ + const userId = opts.httpCtx && opts.httpCtx.active.accessToken.userId; + const user = await Model.app.models.Account.findById(userId, {fields: ['name']}, opts); + await this.executeP(`CALL account.myUser_loginWithName(?)`, [user.name], opts); + } + else { + where = ctx.where; + id = ctx.id; + data = ctx.data; + idName = this.idName(model); + + limit = limitSet.has(method); + + op = opMap.get(method); + + if (!where) { + if (id) where = {[idName]: id}; + else where = {[idName]: data[idName]}; + } + + // Fetch old values + switch (op) { + case 'update': + case 'delete': + // Single entity operation + const stmt = this.buildSelectStmt(op, data, idName, model, where, limit); + stmt.merge(`FOR UPDATE`); + oldInstances = await this.executeStmt(stmt, opts); + } + } + + const res = await new Promise(resolve => { + const fnArgs = args.slice(0, -2); + fnArgs.push(opts, (...args) => resolve(args)); + super[method].apply(this, fnArgs); + }); + + if(hasGrabUser) + await this.executeP(`CALL account.myUser_logout()`, null, opts); + else { + // Fetch new values + const ids = []; + + switch (op) { + case 'insert': + case 'update': { + switch (method) { + case 'createAll': + for (const row of res[1]) + ids.push(row[idName]); + break; + case 'create': + ids.push(res[1]); + break; + case 'update': + if (data[idName] != null) + ids.push(data[idName]); + break; + } + + const newWhere = ids.length ? {[idName]: ids} : where; + + const stmt = this.buildSelectStmt(op, data, idName, model, newWhere, limit); + newInstances = await this.executeStmt(stmt, opts); + } + } + + await this.createLogRecord(oldInstances, newInstances, model, opts); + } + if (tx) await tx.commit(); + return res; + } catch (err) { + if (tx) tx.rollback(); + throw err; + } + } + + buildSelectStmt(op, data, idName, model, where, limit) { + const Model = this.getModelDefinition(model).model; + const properties = Object.keys(Model.definition.properties); + + const fields = data ? Object.keys(data) : []; + if (op == 'delete') + properties.forEach(property => fields.push(property)); + else { + const log = Model.definition.settings.log; + fields.push(idName); + if (log.relation) fields.push(Model.relations[log.relation].keyFrom); + if (log.showField) fields.push(log.showField); + else { + const showFieldNames = ['name', 'description', 'code', 'nickname']; + for (const field of showFieldNames) { + if (properties.includes(field)) { + log.showField = field; + fields.push(field); + break; + } + } + } + } + + const stmt = new ParameterizedSQL( + 'SELECT ' + + this.buildColumnNames(model, {fields}) + + ' FROM ' + + this.tableEscaped(model) + ); + stmt.merge(this.buildWhere(model, where)); + if (limit) stmt.merge(`LIMIT 1`); + + return stmt; + } + + async createLogRecord(oldInstances, newInstances, model, opts) { + function setActionType() { + if (oldInstances && newInstances) + return 'update'; + else if (!oldInstances && newInstances) + return 'insert'; + return 'delete'; + } + + const action = setActionType(); + if (!newInstances && action != 'delete') return; + + const Model = this.getModelDefinition(model).model; + const models = Model.app.models; + const definition = Model.definition; + const log = definition.settings.log; + + const primaryKey = this.idName(model); + const originRelation = log.relation; + const originFkField = originRelation + ? Model.relations[originRelation].keyFrom + : primaryKey; + + // Prevent adding logs when deleting a principal entity (Client, Zone...) + if (action == 'delete' && !originRelation) return; + + function map(instances) { + const map = new Map(); + if (!instances) return; + for (const instance of instances) + map.set(instance[primaryKey], instance); + return map; + } + + const changedModel = definition.name; + const userFk = opts.httpCtx && opts.httpCtx.active.accessToken.userId; + const oldMap = map(oldInstances); + const newMap = map(newInstances); + const ids = (oldMap || newMap).keys(); + + const logEntries = []; + + function insertValuesLogEntry(logEntry, instance) { + logEntry.originFk = instance[originFkField]; + logEntry.changedModelId = instance[primaryKey]; + if (log.showField) logEntry.changedModelValue = instance[log.showField]; + } + + for (const id of ids) { + const oldI = oldMap && oldMap.get(id); + const newI = newMap && newMap.get(id); + + const logEntry = { + action, + userFk, + changedModel, + }; + + if (newI) { + insertValuesLogEntry(logEntry, newI); + // Delete unchanged properties + if (oldI) { + Object.keys(oldI).forEach(prop => { + const hasChanges = oldI[prop] instanceof Date ? + oldI[prop]?.getTime() != newI[prop]?.getTime() : + oldI[prop] != newI[prop]; + + if (!hasChanges) { + delete oldI[prop]; + delete newI[prop]; + } + }); + } + } else + insertValuesLogEntry(logEntry, oldI); + + logEntry.oldInstance = oldI; + logEntry.newInstance = newI; + logEntries.push(logEntry); + } + await models[log.model].create(logEntries, opts); + } } exports.VnMySQL = VnMySQL; diff --git a/loopback/util/log.js b/loopback/util/log.js index e26022c35..76e87781d 100644 --- a/loopback/util/log.js +++ b/loopback/util/log.js @@ -91,7 +91,11 @@ exports.getChanges = (original, changes) => { const isPrivate = firstChar == '$'; if (isPrivate) return; - if (changes[property] != original[property]) { + const hasChanges = original[property] instanceof Date ? + changes[property]?.getTime() != original[property]?.getTime() : + changes[property] != original[property]; + + if (hasChanges) { newChanges[property] = changes[property]; if (original[property] != undefined) diff --git a/modules/claim/back/methods/claim/claimPickupPdf.js b/modules/claim/back/methods/claim/claimPickupPdf.js index 0e3abe908..4927efa0f 100644 --- a/modules/claim/back/methods/claim/claimPickupPdf.js +++ b/modules/claim/back/methods/claim/claimPickupPdf.js @@ -1,5 +1,3 @@ -const { Report } = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('claimPickupPdf', { description: 'Returns the claim pickup order pdf', @@ -39,17 +37,5 @@ module.exports = Self => { } }); - Self.claimPickupPdf = async(ctx, id) => { - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const report = new Report('claim-pickup-order', params); - const stream = await report.toPdfStream(); - - return [stream, 'application/pdf', `filename="doc-${id}.pdf"`]; - }; + Self.claimPickupPdf = (ctx, id) => Self.printReport(ctx, id, 'claim-pickup-order'); }; diff --git a/modules/claim/front/descriptor/index.html b/modules/claim/front/descriptor/index.html index f346ecf17..5fd198440 100644 --- a/modules/claim/front/descriptor/index.html +++ b/modules/claim/front/descriptor/index.html @@ -86,7 +86,20 @@ icon="icon-ticket"> -
+
+ + +
+
+ + +
diff --git a/modules/claim/front/locale/es.yml b/modules/claim/front/locale/es.yml index 5abdc1535..419e62f56 100644 --- a/modules/claim/front/locale/es.yml +++ b/modules/claim/front/locale/es.yml @@ -18,3 +18,5 @@ Claim deleted!: Reclamación eliminada! claim: reclamación Photos: Fotos 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/methods/client/campaignMetricsEmail.js b/modules/client/back/methods/client/campaignMetricsEmail.js index bb57f90a0..3a1bac5e6 100644 --- a/modules/client/back/methods/client/campaignMetricsEmail.js +++ b/modules/client/back/methods/client/campaignMetricsEmail.js @@ -51,19 +51,5 @@ module.exports = Self => { } }); - Self.campaignMetricsEmail = async ctx => { - const args = Object.assign({}, ctx.args); - const params = { - recipient: args.recipient, - lang: ctx.req.getLocale() - }; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const email = new Email('campaign-metrics', params); - - return email.send(); - }; + Self.campaignMetricsEmail = ctx => Self.sendTemplate(ctx, 'campaign-metrics'); }; diff --git a/modules/client/back/methods/client/campaignMetricsPdf.js b/modules/client/back/methods/client/campaignMetricsPdf.js index 14194d62b..e163b0619 100644 --- a/modules/client/back/methods/client/campaignMetricsPdf.js +++ b/modules/client/back/methods/client/campaignMetricsPdf.js @@ -1,5 +1,3 @@ -const {Report} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('campaignMetricsPdf', { description: 'Returns the campaign metrics pdf', @@ -50,17 +48,5 @@ module.exports = Self => { } }); - Self.campaignMetricsPdf = async(ctx, id) => { - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const report = new Report('campaign-metrics', params); - const stream = await report.toPdfStream(); - - return [stream, 'application/pdf', `filename="doc-${id}.pdf"`]; - }; + Self.campaignMetricsPdf = (ctx, id) => Self.printReport(ctx, id, 'campaign-metrics'); }; diff --git a/modules/client/back/methods/client/clientDebtStatementEmail.js b/modules/client/back/methods/client/clientDebtStatementEmail.js index 8bcdc900f..1b3ab13d8 100644 --- a/modules/client/back/methods/client/clientDebtStatementEmail.js +++ b/modules/client/back/methods/client/clientDebtStatementEmail.js @@ -46,19 +46,5 @@ module.exports = Self => { } }); - Self.clientDebtStatementEmail = async ctx => { - const args = Object.assign({}, ctx.args); - const params = { - recipient: args.recipient, - lang: ctx.req.getLocale() - }; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const email = new Email('client-debt-statement', params); - - return email.send(); - }; + Self.clientDebtStatementEmail = ctx => Self.sendTemplate(ctx, 'client-debt-statement'); }; diff --git a/modules/client/back/methods/client/clientDebtStatementHtml.js b/modules/client/back/methods/client/clientDebtStatementHtml.js index bfed696bc..8752b48d2 100644 --- a/modules/client/back/methods/client/clientDebtStatementHtml.js +++ b/modules/client/back/methods/client/clientDebtStatementHtml.js @@ -1,5 +1,3 @@ -const {Email} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('clientDebtStatementHtml', { description: 'Returns the client debt statement email preview', @@ -45,21 +43,5 @@ module.exports = Self => { } }); - Self.clientDebtStatementHtml = async(ctx, id) => { - const {accessToken} = ctx.req; - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - params.isPreview = true; - params.access_token = accessToken.id; - - const report = new Email('client-debt-statement', params); - const html = await report.render(); - - return [html, 'text/html', `filename="mail-${id}.pdf"`]; - }; + Self.clientDebtStatementHtml = (ctx, id) => Self.printEmail(ctx, id, 'client-debt-statement'); }; diff --git a/modules/client/back/methods/client/clientDebtStatementPdf.js b/modules/client/back/methods/client/clientDebtStatementPdf.js index 8e2dca314..845527ace 100644 --- a/modules/client/back/methods/client/clientDebtStatementPdf.js +++ b/modules/client/back/methods/client/clientDebtStatementPdf.js @@ -1,5 +1,3 @@ -const {Report} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('clientDebtStatementPdf', { description: 'Returns the client debt statement pdf', @@ -45,17 +43,5 @@ module.exports = Self => { } }); - Self.clientDebtStatementPdf = async(ctx, id) => { - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const report = new Report('client-debt-statement', params); - const stream = await report.toPdfStream(); - - return [stream, 'application/pdf', `filename="doc-${id}.pdf"`]; - }; + Self.clientDebtStatementPdf = (ctx, id) => Self.printReport(ctx, id, 'client-debt-statement'); }; diff --git a/modules/client/back/methods/client/clientWelcomeEmail.js b/modules/client/back/methods/client/clientWelcomeEmail.js index 318ee18e6..accf12bb8 100644 --- a/modules/client/back/methods/client/clientWelcomeEmail.js +++ b/modules/client/back/methods/client/clientWelcomeEmail.js @@ -1,5 +1,3 @@ -const {Email} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('clientWelcomeEmail', { description: 'Sends the client welcome email with an attached PDF', @@ -41,19 +39,5 @@ module.exports = Self => { } }); - Self.clientWelcomeEmail = async ctx => { - const args = Object.assign({}, ctx.args); - const params = { - recipient: args.recipient, - lang: ctx.req.getLocale() - }; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const email = new Email('client-welcome', params); - - return email.send(); - }; + Self.clientWelcomeEmail = ctx => Self.sendTemplate(ctx, 'client-welcome'); }; diff --git a/modules/client/back/methods/client/clientWelcomeHtml.js b/modules/client/back/methods/client/clientWelcomeHtml.js index dfb560326..093a06d8e 100644 --- a/modules/client/back/methods/client/clientWelcomeHtml.js +++ b/modules/client/back/methods/client/clientWelcomeHtml.js @@ -1,5 +1,3 @@ -const {Email} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('clientWelcomeHtml', { description: 'Returns the client welcome email preview', @@ -40,19 +38,5 @@ module.exports = Self => { } }); - Self.clientWelcomeHtml = async(ctx, id) => { - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - params.isPreview = true; - - const report = new Email('client-welcome', params); - const html = await report.render(); - - return [html, 'text/html', `filename="mail-${id}.pdf"`]; - }; + Self.clientWelcomeHtml = (ctx, id) => Self.printEmail(ctx, id, 'client-welcome'); }; diff --git a/modules/client/back/methods/client/creditRequestEmail.js b/modules/client/back/methods/client/creditRequestEmail.js index b6a60e971..0255949e0 100644 --- a/modules/client/back/methods/client/creditRequestEmail.js +++ b/modules/client/back/methods/client/creditRequestEmail.js @@ -1,5 +1,3 @@ -const {Email} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('clientCreditEmail', { description: 'Sends the credit request email with an attached PDF', @@ -10,7 +8,7 @@ module.exports = Self => { type: 'number', required: true, description: 'The client id', - http: {source: 'path'} + http: {source: 'path'}, }, { arg: 'recipient', @@ -22,38 +20,25 @@ module.exports = Self => { arg: 'replyTo', type: 'string', description: 'The sender email to reply to', - required: false + required: false, }, { arg: 'recipientId', type: 'number', - description: 'The recipient id to send to the recipient preferred language', - required: false - } + description: + 'The recipient id to send to the recipient preferred language', + required: false, + }, ], returns: { type: ['object'], - root: true + root: true, }, http: { path: '/:id/credit-request-email', - verb: 'POST' - } + verb: 'POST', + }, }); - Self.clientCreditEmail = async ctx => { - const args = Object.assign({}, ctx.args); - const params = { - recipient: args.recipient, - lang: ctx.req.getLocale() - }; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const email = new Email('credit-request', params); - - return email.send(); - }; + Self.clientCreditEmail = ctx => Self.sendTemplate(ctx, 'credit-request'); }; diff --git a/modules/client/back/methods/client/creditRequestHtml.js b/modules/client/back/methods/client/creditRequestHtml.js index 6b2d7fe4e..fbd6cacb9 100644 --- a/modules/client/back/methods/client/creditRequestHtml.js +++ b/modules/client/back/methods/client/creditRequestHtml.js @@ -1,5 +1,3 @@ -const {Email} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('creditRequestHtml', { description: 'Returns the credit request email preview', @@ -40,21 +38,5 @@ module.exports = Self => { } }); - Self.creditRequestHtml = async(ctx, id) => { - const {accessToken} = ctx.req; - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - params.isPreview = true; - params.access_token = accessToken.id; - - const report = new Email('credit-request', params); - const html = await report.render(); - - return [html, 'text/html', `filename="mail-${id}.pdf"`]; - }; + Self.creditRequestHtml = (ctx, id) => Self.printEmail(ctx, id, 'credit-request'); }; diff --git a/modules/client/back/methods/client/creditRequestPdf.js b/modules/client/back/methods/client/creditRequestPdf.js index 2e3dc0619..a4f4ed128 100644 --- a/modules/client/back/methods/client/creditRequestPdf.js +++ b/modules/client/back/methods/client/creditRequestPdf.js @@ -1,5 +1,3 @@ -const {Report} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('creditRequestPdf', { description: 'Returns the credit request pdf', @@ -40,17 +38,5 @@ module.exports = Self => { } }); - Self.creditRequestPdf = async(ctx, id) => { - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const report = new Report('credit-request', params); - const stream = await report.toPdfStream(); - - return [stream, 'application/pdf', `filename="doc-${id}.pdf"`]; - }; + Self.creditRequestPdf = (ctx, id) => Self.printReport(ctx, id, 'credit-request'); }; diff --git a/modules/client/back/methods/client/extendedListFilter.js b/modules/client/back/methods/client/extendedListFilter.js index 8e02cd413..27bbe2a35 100644 --- a/modules/client/back/methods/client/extendedListFilter.js +++ b/modules/client/back/methods/client/extendedListFilter.js @@ -97,7 +97,7 @@ module.exports = Self => { const stmts = []; const stmt = new ParameterizedSQL( - `SELECT + `SELECT c.id, c.name, c.socialName, diff --git a/modules/client/back/methods/client/getCard.js b/modules/client/back/methods/client/getCard.js index 99c59f757..a2365ee25 100644 --- a/modules/client/back/methods/client/getCard.js +++ b/modules/client/back/methods/client/getCard.js @@ -80,7 +80,7 @@ module.exports = function(Self) { const data = await Self.rawSql(query, [id, date], myOptions); client.debt = data[0].debt; - client.unpaid = await Self.app.models.ClientUnpaid.findOne({id}, myOptions); + client.unpaid = await Self.app.models.ClientUnpaid.findById(id, null, myOptions); return client; }; diff --git a/modules/client/back/methods/client/incotermsAuthorizationEmail.js b/modules/client/back/methods/client/incotermsAuthorizationEmail.js index 2a4fe593a..4a21f20b0 100644 --- a/modules/client/back/methods/client/incotermsAuthorizationEmail.js +++ b/modules/client/back/methods/client/incotermsAuthorizationEmail.js @@ -1,5 +1,3 @@ -const {Email} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('incotermsAuthorizationEmail', { description: 'Sends the incoterms authorization email with an attached PDF', @@ -47,19 +45,5 @@ module.exports = Self => { } }); - Self.incotermsAuthorizationEmail = async ctx => { - const args = Object.assign({}, ctx.args); - const params = { - recipient: args.recipient, - lang: ctx.req.getLocale() - }; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const email = new Email('incoterms-authorization', params); - - return email.send(); - }; + Self.incotermsAuthorizationEmail = ctx => Self.sendTemplate(ctx, 'incoterms-authorization'); }; diff --git a/modules/client/back/methods/client/incotermsAuthorizationHtml.js b/modules/client/back/methods/client/incotermsAuthorizationHtml.js index 875495d93..0a6bba0a8 100644 --- a/modules/client/back/methods/client/incotermsAuthorizationHtml.js +++ b/modules/client/back/methods/client/incotermsAuthorizationHtml.js @@ -1,5 +1,3 @@ -const {Email} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('incotermsAuthorizationHtml', { description: 'Returns the incoterms authorization email preview', @@ -46,21 +44,5 @@ module.exports = Self => { } }); - Self.incotermsAuthorizationHtml = async(ctx, id) => { - const {accessToken} = ctx.req; - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - params.isPreview = true; - params.access_token = accessToken.id; - - const report = new Email('incoterms-authorization', params); - const html = await report.render(); - - return [html, 'text/html', `filename="mail-${id}.pdf"`]; - }; + Self.incotermsAuthorizationHtml = (ctx, id) => Self.printEmail(ctx, id, 'incoterms-authorization'); }; diff --git a/modules/client/back/methods/client/incotermsAuthorizationPdf.js b/modules/client/back/methods/client/incotermsAuthorizationPdf.js index 9a8a8d296..d37e473f1 100644 --- a/modules/client/back/methods/client/incotermsAuthorizationPdf.js +++ b/modules/client/back/methods/client/incotermsAuthorizationPdf.js @@ -1,5 +1,3 @@ -const {Report} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('incotermsAuthorizationPdf', { description: 'Returns the incoterms authorization pdf', @@ -46,17 +44,5 @@ module.exports = Self => { } }); - Self.incotermsAuthorizationPdf = async(ctx, id) => { - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const report = new Report('incoterms-authorization', params); - const stream = await report.toPdfStream(); - - return [stream, 'application/pdf', `filename="doc-${id}.pdf"`]; - }; + Self.incotermsAuthorizationPdf = (ctx, id) => Self.printReport(ctx, id, 'incoterms-authorization'); }; diff --git a/modules/client/back/methods/client/letterDebtorNdEmail.js b/modules/client/back/methods/client/letterDebtorNdEmail.js index e188c6e0a..396acdb97 100644 --- a/modules/client/back/methods/client/letterDebtorNdEmail.js +++ b/modules/client/back/methods/client/letterDebtorNdEmail.js @@ -1,5 +1,3 @@ -const {Email} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('letterDebtorNdEmail', { description: 'Sends the second debtor letter email with an attached PDF', @@ -47,19 +45,5 @@ module.exports = Self => { } }); - Self.letterDebtorNdEmail = async ctx => { - const args = Object.assign({}, ctx.args); - const params = { - recipient: args.recipient, - lang: ctx.req.getLocale() - }; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const email = new Email('letter-debtor-nd', params); - - return email.send(); - }; + Self.letterDebtorNdEmail = ctx => Self.sendTemplate(ctx, 'letter-debtor-nd'); }; diff --git a/modules/client/back/methods/client/letterDebtorNdHtml.js b/modules/client/back/methods/client/letterDebtorNdHtml.js index 320fbaef3..f14f96dea 100644 --- a/modules/client/back/methods/client/letterDebtorNdHtml.js +++ b/modules/client/back/methods/client/letterDebtorNdHtml.js @@ -1,5 +1,3 @@ -const {Email} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('letterDebtorNdHtml', { description: 'Returns the second letter debtor email preview', @@ -46,21 +44,5 @@ module.exports = Self => { } }); - Self.letterDebtorNdHtml = async(ctx, id) => { - const {accessToken} = ctx.req; - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - params.isPreview = true; - params.access_token = accessToken.id; - - const report = new Email('letter-debtor-nd', params); - const html = await report.render(); - - return [html, 'text/html', `filename="mail-${id}.pdf"`]; - }; + Self.letterDebtorNdHtml = (ctx, id) => Self.printEmail(ctx, id, 'letter-debtor-nd'); }; diff --git a/modules/client/back/methods/client/letterDebtorPdf.js b/modules/client/back/methods/client/letterDebtorPdf.js index 421d531e6..943869143 100644 --- a/modules/client/back/methods/client/letterDebtorPdf.js +++ b/modules/client/back/methods/client/letterDebtorPdf.js @@ -1,5 +1,3 @@ -const {Report} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('letterDebtorPdf', { description: 'Returns the letter debtor pdf', @@ -46,17 +44,5 @@ module.exports = Self => { } }); - Self.letterDebtorPdf = async(ctx, id) => { - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const report = new Report('letter-debtor', params); - const stream = await report.toPdfStream(); - - return [stream, 'application/pdf', `filename="doc-${id}.pdf"`]; - }; + Self.letterDebtorPdf = (ctx, id) => Self.printReport(ctx, id, 'letter-debtor'); }; diff --git a/modules/client/back/methods/client/letterDebtorStEmail.js b/modules/client/back/methods/client/letterDebtorStEmail.js index ee39a101b..c76204fbc 100644 --- a/modules/client/back/methods/client/letterDebtorStEmail.js +++ b/modules/client/back/methods/client/letterDebtorStEmail.js @@ -1,5 +1,3 @@ -const {Email} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('letterDebtorStEmail', { description: 'Sends the printer setup email with an attached PDF', @@ -47,19 +45,5 @@ module.exports = Self => { } }); - Self.letterDebtorStEmail = async ctx => { - const args = Object.assign({}, ctx.args); - const params = { - recipient: args.recipient, - lang: ctx.req.getLocale() - }; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const email = new Email('letter-debtor-st', params); - - return email.send(); - }; + Self.letterDebtorStEmail = ctx => Self.sendTemplate(ctx, 'letter-debtor-st'); }; diff --git a/modules/client/back/methods/client/letterDebtorStHtml.js b/modules/client/back/methods/client/letterDebtorStHtml.js index acf75272b..a1dcf00d7 100644 --- a/modules/client/back/methods/client/letterDebtorStHtml.js +++ b/modules/client/back/methods/client/letterDebtorStHtml.js @@ -1,5 +1,3 @@ -const {Email} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('letterDebtorStHtml', { description: 'Returns the letter debtor email preview', @@ -46,21 +44,5 @@ module.exports = Self => { } }); - Self.letterDebtorStHtml = async(ctx, id) => { - const {accessToken} = ctx.req; - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - params.isPreview = true; - params.access_token = accessToken.id; - - const report = new Email('letter-debtor-st', params); - const html = await report.render(); - - return [html, 'text/html', `filename="mail-${id}.pdf"`]; - }; + Self.letterDebtorStHtml = (ctx, id) => Self.printEmail(ctx, id, 'letter-debtor-st'); }; diff --git a/modules/client/back/methods/client/printerSetupEmail.js b/modules/client/back/methods/client/printerSetupEmail.js index 254948659..2a0903e90 100644 --- a/modules/client/back/methods/client/printerSetupEmail.js +++ b/modules/client/back/methods/client/printerSetupEmail.js @@ -1,5 +1,3 @@ -const {Email} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('printerSetupEmail', { description: 'Sends the printer setup email with an attached PDF', @@ -41,19 +39,5 @@ module.exports = Self => { } }); - Self.printerSetupEmail = async ctx => { - const args = Object.assign({}, ctx.args); - const params = { - recipient: args.recipient, - lang: ctx.req.getLocale() - }; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const email = new Email('printer-setup', params); - - return email.send(); - }; + Self.printerSetupEmail = ctx => Self.sendTemplate(ctx, 'printer-setup'); }; diff --git a/modules/client/back/methods/client/printerSetupHtml.js b/modules/client/back/methods/client/printerSetupHtml.js index 1ef1843e0..c9d94d1c2 100644 --- a/modules/client/back/methods/client/printerSetupHtml.js +++ b/modules/client/back/methods/client/printerSetupHtml.js @@ -1,5 +1,3 @@ -const {Email} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('printerSetupHtml', { description: 'Returns the printer setup email preview', @@ -40,19 +38,5 @@ module.exports = Self => { } }); - Self.printerSetupHtml = async(ctx, id) => { - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - params.isPreview = true; - - const report = new Email('printer-setup', params); - const html = await report.render(); - - return [html, 'text/html', `filename="mail-${id}.pdf"`]; - }; + Self.printerSetupHtml = (ctx, id) => Self.printEmail(ctx, id, 'printer-setup'); }; diff --git a/modules/client/back/methods/client/sepaCoreEmail.js b/modules/client/back/methods/client/sepaCoreEmail.js index 93c9d4302..20931eb03 100644 --- a/modules/client/back/methods/client/sepaCoreEmail.js +++ b/modules/client/back/methods/client/sepaCoreEmail.js @@ -1,5 +1,3 @@ -const {Email} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('sepaCoreEmail', { description: 'Sends the campaign metrics email with an attached PDF', @@ -47,19 +45,5 @@ module.exports = Self => { } }); - Self.sepaCoreEmail = async ctx => { - const args = Object.assign({}, ctx.args); - const params = { - recipient: args.recipient, - lang: ctx.req.getLocale() - }; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const email = new Email('sepa-core', params); - - return email.send(); - }; + Self.sepaCoreEmail = ctx => Self.sendTemplate(ctx, 'sepa-core'); }; diff --git a/modules/client/back/methods/receipt/balanceCompensationPdf.js b/modules/client/back/methods/receipt/balanceCompensationPdf.js index ff8713253..e790d54a1 100644 --- a/modules/client/back/methods/receipt/balanceCompensationPdf.js +++ b/modules/client/back/methods/receipt/balanceCompensationPdf.js @@ -1,5 +1,3 @@ -const { Report } = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('balanceCompensationPdf', { description: 'Returns the the debit balances compensation pdf', @@ -10,7 +8,7 @@ module.exports = Self => { type: 'number', required: true, description: 'The receipt id', - http: { source: 'path' } + http: {source: 'path'} } ], returns: [ @@ -34,17 +32,5 @@ module.exports = Self => { } }); - Self.balanceCompensationPdf = async(ctx, id) => { - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const report = new Report('balance-compensation', params); - const stream = await report.toPdfStream(); - - return [stream, 'application/pdf', `filename="doc-${id}.pdf"`]; - }; + Self.balanceCompensationPdf = (ctx, id) => Self.printReport(ctx, id, 'balance-compensation'); }; diff --git a/modules/client/back/methods/receipt/receiptPdf.js b/modules/client/back/methods/receipt/receiptPdf.js index f55e05040..433f386db 100644 --- a/modules/client/back/methods/receipt/receiptPdf.js +++ b/modules/client/back/methods/receipt/receiptPdf.js @@ -1,5 +1,3 @@ -const {Report} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('receiptPdf', { description: 'Returns the receipt pdf', @@ -39,17 +37,5 @@ module.exports = Self => { } }); - Self.receiptPdf = async(ctx, id) => { - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const report = new Report('receipt', params); - const stream = await report.toPdfStream(); - - return [stream, 'application/pdf', `filename="doc-${id}.pdf"`]; - }; + Self.receiptPdf = (ctx, id) => Self.printReport(ctx, id, 'receipt'); }; diff --git a/modules/client/back/models/client.js b/modules/client/back/models/client.js index 2d8e7bd27..c41085b79 100644 --- a/modules/client/back/models/client.js +++ b/modules/client/back/models/client.js @@ -279,6 +279,18 @@ module.exports = Self => { // Credit changes if (changes.credit !== undefined) await Self.changeCredit(ctx, finalState, changes); + + const oldInstance = {}; + if (!ctx.isNewInstance) { + const newProps = Object.keys(changes); + Object.keys(orgData.__data).forEach(prop => { + if (newProps.includes(prop)) + oldInstance[prop] = orgData[prop]; + }); + } + + ctx.hookState.oldInstance = oldInstance; + ctx.hookState.newInstance = changes; }); Self.observe('after save', async ctx => { diff --git a/modules/client/back/models/receipt.json b/modules/client/back/models/receipt.json index 19107f561..da7879df9 100644 --- a/modules/client/back/models/receipt.json +++ b/modules/client/back/models/receipt.json @@ -62,6 +62,11 @@ "type": "belongsTo", "model": "Bank", "foreignKey": "bankFk" + }, + "supplier": { + "type": "belongsTo", + "model": "Supplier", + "foreignKey": "companyFk" } } -} \ No newline at end of file +} diff --git a/modules/client/front/descriptor/index.html b/modules/client/front/descriptor/index.html index edf3cc8c3..5aaecbdb0 100644 --- a/modules/client/front/descriptor/index.html +++ b/modules/client/front/descriptor/index.html @@ -70,11 +70,12 @@ icon="icon-no036" ng-if="$ctrl.client.isTaxDataChecked == false"> - - + diff --git a/modules/client/front/unpaid/index.js b/modules/client/front/unpaid/index.js index fcf620b54..1585b808d 100644 --- a/modules/client/front/unpaid/index.js +++ b/modules/client/front/unpaid/index.js @@ -6,9 +6,17 @@ export default class Controller extends Section { if (hasData && !this.clientUnpaid.dated) this.clientUnpaid.dated = Date.vnNew(); } + + onSubmit() { + this.$.watcher.submit() + .then(() => this.card.reload()); + } } ngModule.vnComponent('vnClientUnpaid', { template: require('./index.html'), - controller: Controller + controller: Controller, + require: { + card: '^vnClientCard' + } }); diff --git a/modules/entry/back/methods/entry/entryOrderPdf.js b/modules/entry/back/methods/entry/entryOrderPdf.js index e6d37fdb6..f77832162 100644 --- a/modules/entry/back/methods/entry/entryOrderPdf.js +++ b/modules/entry/back/methods/entry/entryOrderPdf.js @@ -1,5 +1,3 @@ -const {Report} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('entryOrderPdf', { description: 'Returns the entry order pdf', @@ -38,17 +36,5 @@ module.exports = Self => { } }); - Self.entryOrderPdf = async(ctx, id) => { - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const report = new Report('entry-order', params); - const stream = await report.toPdfStream(); - - return [stream, 'application/pdf', `filename="doc-${id}.pdf"`]; - }; + Self.entryOrderPdf = (ctx, id) => Self.printReport(ctx, id, 'entry-order'); }; diff --git a/modules/invoiceIn/back/methods/invoice-in/invoiceInEmail.js b/modules/invoiceIn/back/methods/invoice-in/invoiceInEmail.js index 0768541a8..a0af3da69 100644 --- a/modules/invoiceIn/back/methods/invoice-in/invoiceInEmail.js +++ b/modules/invoiceIn/back/methods/invoice-in/invoiceInEmail.js @@ -1,5 +1,3 @@ -const {Email} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('invoiceInEmail', { description: 'Sends the invoice in email with an attached PDF', @@ -35,19 +33,5 @@ module.exports = Self => { } }); - Self.invoiceInEmail = async ctx => { - const args = Object.assign({}, ctx.args); - const params = { - recipient: args.recipient, - lang: ctx.req.getLocale() - }; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const email = new Email('invoiceIn', params); - - return email.send(); - }; + Self.invoiceInEmail = ctx => Self.sendTemplate(ctx, 'invoiceIn'); }; diff --git a/modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js b/modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js index e7962c93f..681a19fc6 100644 --- a/modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js +++ b/modules/invoiceIn/back/methods/invoice-in/invoiceInPdf.js @@ -1,5 +1,3 @@ -const {Report} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('invoiceInPdf', { description: 'Returns the invoiceIn pdf', @@ -34,17 +32,5 @@ module.exports = Self => { } }); - Self.invoiceInPdf = async(ctx, id) => { - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - delete args.ctx; - - for (const param in args) - params[param] = args[param]; - - const report = new Report('invoiceIn', params); - const stream = await report.toPdfStream(); - - return [stream, 'application/pdf', `filename="doc-${id}.pdf"`]; - }; + Self.invoiceInPdf = (ctx, id) => Self.printReport(ctx, id, 'invoiceIn'); }; diff --git a/modules/invoiceOut/back/methods/invoiceOut/exportationPdf.js b/modules/invoiceOut/back/methods/invoiceOut/exportationPdf.js index e947c5144..7a2526b35 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/exportationPdf.js +++ b/modules/invoiceOut/back/methods/invoiceOut/exportationPdf.js @@ -1,5 +1,3 @@ -const {Report} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('exportationPdf', { description: 'Returns the exportation pdf', @@ -39,17 +37,5 @@ module.exports = Self => { } }); - Self.exportationPdf = async(ctx, reference) => { - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const report = new Report('exportation', params); - const stream = await report.toPdfStream(); - - return [stream, 'application/pdf', `filename="doc-${reference}.pdf"`]; - }; + Self.exportationPdf = (ctx, reference) => Self.printReport(ctx, reference, 'exportation'); }; diff --git a/modules/item/back/methods/item-image-queue/download.js b/modules/item/back/methods/item-image-queue/download.js new file mode 100644 index 000000000..cdc0fe049 --- /dev/null +++ b/modules/item/back/methods/item-image-queue/download.js @@ -0,0 +1,191 @@ +const axios = require('axios'); +const uuid = require('uuid'); +const fs = require('fs/promises'); +const { createWriteStream } = require('fs'); +const path = require('path'); +const gm = require('gm'); + +module.exports = Self => { + Self.remoteMethod('download', { + description: 'Processes the image download queue', + accessType: 'WRITE', + http: { + path: `/download`, + verb: 'POST', + }, + }); + + Self.download = async () => { + const models = Self.app.models; + const tempContainer = await models.TempContainer.container( + 'salix-image' + ); + const tempPath = path.join( + tempContainer.client.root, + tempContainer.name + ); + const maxAttempts = 3; + const collectionName = 'catalog'; + + const tx = await Self.beginTransaction({}); + + let tempFilePath; + let queueRow; + try { + const myOptions = { transaction: tx }; + + queueRow = await Self.findOne( + { + fields: ['id', 'itemFk', 'url', 'attempts'], + where: { + url: { neq: null }, + attempts: { + lt: maxAttempts, + }, + }, + order: 'priority, attempts, updated', + }, + myOptions + ); + + if (!queueRow) return; + + const collection = await models.ImageCollection.findOne( + { + fields: [ + 'id', + 'maxWidth', + 'maxHeight', + 'model', + 'property', + ], + where: { name: collectionName }, + include: { + relation: 'sizes', + scope: { + fields: ['width', 'height', 'crop'], + }, + }, + }, + myOptions + ); + + const fileName = `${uuid.v4()}.png`; + tempFilePath = path.join(tempPath, fileName); + + // Insert image row + await models.Image.create( + { + name: fileName, + collectionFk: collectionName, + updated: Date.vnNow(), + }, + myOptions + ); + + // Update item + const model = models[collection.model]; + if (!model) throw new Error('No matching model found'); + + const item = await model.findById(queueRow.itemFk, null, myOptions); + if (item) { + await item.updateAttribute( + collection.property, + fileName, + myOptions + ); + } + + // Download remote image + const response = await axios.get(queueRow.url, { + responseType: 'stream', + }); + + const writeStream = createWriteStream(tempFilePath); + await new Promise((resolve, reject) => { + writeStream.on('open', () => response.data.pipe(writeStream)); + writeStream.on('finish', () => resolve()); + writeStream.on('error', error => reject(error)); + }); + + // Resize + const container = await models.ImageContainer.container( + collectionName + ); + const rootPath = container.client.root; + const collectionDir = path.join(rootPath, collectionName); + + // To max size + const { maxWidth, maxHeight } = collection; + const fullSizePath = path.join(collectionDir, 'full'); + const toFullSizePath = `${fullSizePath}/${fileName}`; + + await fs.mkdir(fullSizePath, { recursive: true }); + await new Promise((resolve, reject) => { + gm(tempFilePath) + .resize(maxWidth, maxHeight, '>') + .setFormat('png') + .write(toFullSizePath, function (err) { + if (err) reject(err); + if (!err) resolve(); + }); + }); + + // To collection sizes + for (const size of collection.sizes()) { + const { width, height } = size; + + const sizePath = path.join(collectionDir, `${width}x${height}`); + const toSizePath = `${sizePath}/${fileName}`; + + await fs.mkdir(sizePath, { recursive: true }); + await new Promise((resolve, reject) => { + const gmInstance = gm(tempFilePath); + + if (size.crop) { + gmInstance + .resize(width, height, '^') + .gravity('Center') + .crop(width, height); + } + + if (!size.crop) gmInstance.resize(width, height, '>'); + + gmInstance + .setFormat('png') + .write(toSizePath, function (err) { + if (err) reject(err); + if (!err) resolve(); + }); + }); + } + + try { + await fs.unlink(tempFilePath); + } catch (error) { } + + await queueRow.destroy(myOptions); + + // Restart queue + Self.download(); + + await tx.commit(); + } catch (error) { + await tx.rollback(); + + if (queueRow.attempts < maxAttempts) { + await queueRow.updateAttributes({ + error: error, + attempts: queueRow.attempts + 1, + updated: Date.vnNew(), + }); + } + + try { + await fs.unlink(tempFilePath); + } catch (error) { } + + Self.download(); + } + }; +}; diff --git a/modules/item/back/methods/item-image-queue/downloadImages.js b/modules/item/back/methods/item-image-queue/downloadImages.js index c4d7f4b98..7f53df95a 100644 --- a/modules/item/back/methods/item-image-queue/downloadImages.js +++ b/modules/item/back/methods/item-image-queue/downloadImages.js @@ -62,7 +62,7 @@ module.exports = Self => { writeStream.on('open', () => response.pipe(writeStream)); writeStream.on('error', async error => await errorHandler(image.itemFk, error, filePath)); - writeStream.on('finish', writeStream.end()); + writeStream.on('finish', () => writeStream.end()); writeStream.on('close', async function() { try { diff --git a/modules/item/back/methods/item/labelPdf.js b/modules/item/back/methods/item/labelPdf.js index 747869b37..c2462353d 100644 --- a/modules/item/back/methods/item/labelPdf.js +++ b/modules/item/back/methods/item/labelPdf.js @@ -1,5 +1,3 @@ -const {Report} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('labelPdf', { description: 'Returns the item label pdf', @@ -56,17 +54,5 @@ module.exports = Self => { } }); - Self.labelPdf = async(ctx, id) => { - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const report = new Report('item-label', params); - const stream = await report.toPdfStream(); - - return [stream, 'application/pdf', `filename="item-${id}.pdf"`]; - }; + Self.labelPdf = (ctx, id) => Self.printReport(ctx, id, 'item-label'); }; diff --git a/modules/item/back/models/item-image-queue.js b/modules/item/back/models/item-image-queue.js index e2059ddac..35a467693 100644 --- a/modules/item/back/models/item-image-queue.js +++ b/modules/item/back/models/item-image-queue.js @@ -1,3 +1,3 @@ module.exports = Self => { - require('../methods/item-image-queue/downloadImages')(Self); + require('../methods/item-image-queue/download')(Self); }; diff --git a/modules/route/back/methods/route/driverRouteEmail.js b/modules/route/back/methods/route/driverRouteEmail.js index 4d5279d2d..82b005e44 100644 --- a/modules/route/back/methods/route/driverRouteEmail.js +++ b/modules/route/back/methods/route/driverRouteEmail.js @@ -1,5 +1,3 @@ -const {Email} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('driverRouteEmail', { description: 'Sends the driver route email with an attached PDF', @@ -41,19 +39,5 @@ module.exports = Self => { } }); - Self.driverRouteEmail = async ctx => { - const args = Object.assign({}, ctx.args); - const params = { - recipient: args.recipient, - lang: ctx.req.getLocale() - }; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const email = new Email('driver-route', params); - - return email.send(); - }; + Self.driverRouteEmail = ctx => Self.sendTemplate(ctx, 'driver-route'); }; diff --git a/modules/route/back/methods/route/driverRoutePdf.js b/modules/route/back/methods/route/driverRoutePdf.js index 65748afad..f0cd75f0e 100644 --- a/modules/route/back/methods/route/driverRoutePdf.js +++ b/modules/route/back/methods/route/driverRoutePdf.js @@ -1,5 +1,3 @@ -const {Report} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('driverRoutePdf', { description: 'Returns the driver route pdf', @@ -39,17 +37,5 @@ module.exports = Self => { } }); - Self.driverRoutePdf = async(ctx, id) => { - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const report = new Report('driver-route', params); - const stream = await report.toPdfStream(); - - return [stream, 'application/pdf', `filename="doc-${id}.pdf"`]; - }; + Self.driverRoutePdf = (ctx, id) => Self.printReport(ctx, id, 'driver-route'); }; diff --git a/modules/supplier/back/methods/supplier/campaignMetricsPdf.js b/modules/supplier/back/methods/supplier/campaignMetricsPdf.js index 7bd65ffcb..f7ff900e7 100644 --- a/modules/supplier/back/methods/supplier/campaignMetricsPdf.js +++ b/modules/supplier/back/methods/supplier/campaignMetricsPdf.js @@ -1,5 +1,3 @@ -const {Report} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('campaignMetricsPdf', { description: 'Returns the campaign metrics pdf', @@ -49,17 +47,5 @@ module.exports = Self => { } }); - Self.campaignMetricsPdf = async(ctx, id) => { - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const report = new Report('supplier-campaign-metrics', params); - const stream = await report.toPdfStream(); - - return [stream, 'application/pdf', `filename="doc-${id}.pdf"`]; - }; + Self.campaignMetricsPdf = (ctx, id) => Self.printReport(ctx, id, 'supplier-campaign-metrics'); }; diff --git a/modules/ticket/back/methods/ticket/collectionLabel.js b/modules/ticket/back/methods/ticket/collectionLabel.js index 7601961fb..74b5b2c0a 100644 --- a/modules/ticket/back/methods/ticket/collectionLabel.js +++ b/modules/ticket/back/methods/ticket/collectionLabel.js @@ -1,5 +1,3 @@ -const {Report} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('collectionLabel', { description: 'Returns the collection label', @@ -39,17 +37,5 @@ module.exports = Self => { } }); - Self.collectionLabel = async(ctx, id) => { - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const report = new Report('collection-label', params); - const stream = await report.toPdfStream(); - - return [stream, 'application/pdf', `filename="doc-${id}.pdf"`]; - }; + Self.collectionLabel = (ctx, id) => Self.printReport(ctx, id, 'collection-label'); }; diff --git a/modules/ticket/back/methods/ticket/componentUpdate.js b/modules/ticket/back/methods/ticket/componentUpdate.js index 94b91e237..c23d1669e 100644 --- a/modules/ticket/back/methods/ticket/componentUpdate.js +++ b/modules/ticket/back/methods/ticket/componentUpdate.js @@ -165,18 +165,29 @@ module.exports = Self => { 'shipped', 'landed', 'isDeleted', - 'routeFk' + 'routeFk', + 'nickname' ], include: [ { relation: 'client', scope: { fields: 'salesPersonFk' - } - }] + }, + include: [ + { + relation: 'address', + scope: { + fields: 'nickname' + } + } + ] + }, + ] }, myOptions); args.routeFk = null; + if (args.isWithoutNegatives === false) delete args.isWithoutNegatives; const updatedTicket = Object.assign({}, args); delete updatedTicket.ctx; delete updatedTicket.option; @@ -224,37 +235,41 @@ module.exports = Self => { } const changes = loggable.getChanges(originalTicket, updatedTicket); - const oldProperties = await loggable.translateValues(Self, changes.old); - const newProperties = await loggable.translateValues(Self, changes.new); + const hasChanges = Object.keys(changes.old).length > 0 || Object.keys(changes.new).length > 0; - await models.TicketLog.create({ - originFk: args.id, - userFk: userId, - action: 'update', - changedModel: 'Ticket', - changedModelId: args.id, - oldInstance: oldProperties, - newInstance: newProperties - }, myOptions); + if (hasChanges) { + const oldProperties = await loggable.translateValues(Self, changes.old); + const newProperties = await loggable.translateValues(Self, changes.new); - const salesPersonId = originalTicket.client().salesPersonFk; - if (salesPersonId) { - const origin = ctx.req.headers.origin; + await models.TicketLog.create({ + originFk: args.id, + userFk: userId, + action: 'update', + changedModel: 'Ticket', + changedModelId: args.id, + oldInstance: oldProperties, + newInstance: newProperties + }, myOptions); - let changesMade = ''; - for (let change in newProperties) { - let value = newProperties[change]; - let oldValue = oldProperties[change]; + const salesPersonId = originalTicket.client().salesPersonFk; + if (salesPersonId) { + const origin = ctx.req.headers.origin; - changesMade += `\r\n~${$t(change)}: ${oldValue}~ ➔ *${$t(change)}: ${value}*`; + let changesMade = ''; + for (let change in newProperties) { + let value = newProperties[change]; + let oldValue = oldProperties[change]; + + changesMade += `\r\n~${$t(change)}: ${oldValue}~ ➔ *${$t(change)}: ${value}*`; + } + + const message = $t('Changed this data from the ticket', { + ticketId: args.id, + ticketUrl: `${origin}/#!/ticket/${args.id}/sale`, + changes: changesMade + }); + await models.Chat.sendCheckingPresence(ctx, salesPersonId, message); } - - const message = $t('Changed this data from the ticket', { - ticketId: args.id, - ticketUrl: `${origin}/#!/ticket/${args.id}/sale`, - changes: changesMade - }); - await models.Chat.sendCheckingPresence(ctx, salesPersonId, message); } res.id = args.id; diff --git a/modules/ticket/back/methods/ticket/deliveryNoteEmail.js b/modules/ticket/back/methods/ticket/deliveryNoteEmail.js index cf9f5dd62..4b275b45e 100644 --- a/modules/ticket/back/methods/ticket/deliveryNoteEmail.js +++ b/modules/ticket/back/methods/ticket/deliveryNoteEmail.js @@ -1,5 +1,3 @@ -const {Email} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('deliveryNoteEmail', { description: 'Sends the delivery note email with an attached PDF', @@ -47,19 +45,5 @@ module.exports = Self => { } }); - Self.deliveryNoteEmail = async ctx => { - const args = Object.assign({}, ctx.args); - const params = { - recipient: args.recipient, - lang: ctx.req.getLocale() - }; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const email = new Email('delivery-note', params); - - return email.send(); - }; + Self.deliveryNoteEmail = ctx => Self.sendTemplate(ctx, 'delivery-note'); }; diff --git a/modules/ticket/back/methods/ticket/deliveryNotePdf.js b/modules/ticket/back/methods/ticket/deliveryNotePdf.js index b2b3f7198..628e16bcd 100644 --- a/modules/ticket/back/methods/ticket/deliveryNotePdf.js +++ b/modules/ticket/back/methods/ticket/deliveryNotePdf.js @@ -1,5 +1,3 @@ -const {Report} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('deliveryNotePdf', { description: 'Returns the delivery note pdf', @@ -46,17 +44,5 @@ module.exports = Self => { } }); - Self.deliveryNotePdf = async(ctx, id) => { - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const report = new Report('delivery-note', params); - const stream = await report.toPdfStream(); - - return [stream, 'application/pdf', `filename="doc-${id}.pdf"`]; - }; + Self.deliveryNotePdf = (ctx, id) => Self.printReport(ctx, id, 'delivery-note'); }; diff --git a/modules/ticket/back/methods/ticket/expeditionPalletLabel.js b/modules/ticket/back/methods/ticket/expeditionPalletLabel.js index 2215263dd..9830364d6 100644 --- a/modules/ticket/back/methods/ticket/expeditionPalletLabel.js +++ b/modules/ticket/back/methods/ticket/expeditionPalletLabel.js @@ -1,5 +1,3 @@ -const {Report} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('expeditionPalletLabel', { description: 'Returns the expedition pallet label', @@ -39,17 +37,5 @@ module.exports = Self => { } }); - Self.expeditionPalletLabel = async(ctx, id) => { - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const report = new Report('expedition-pallet-label', params); - const stream = await report.toPdfStream(); - - return [stream, 'application/pdf', `filename="doc-${id}.pdf"`]; - }; + Self.expeditionPalletLabel = (ctx, id) => Self.printReport(ctx, id, 'expedition-pallet-label'); }; diff --git a/modules/ticket/back/methods/ticket/saveSign.js b/modules/ticket/back/methods/ticket/saveSign.js new file mode 100644 index 000000000..ab1c32d1b --- /dev/null +++ b/modules/ticket/back/methods/ticket/saveSign.js @@ -0,0 +1,132 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('saveSign', { + description: 'Save sign', + accessType: 'WRITE', + accepts: + [ + { + arg: 'tickets', + type: ['number'], + required: true, + description: 'The tickets' + }, + { + arg: 'location', + type: 'object', + description: 'The employee location the moment the sign is saved' + }, + { + arg: 'signedTime', + type: 'date', + description: 'The signed time' + } + ], + http: { + path: `/saveSign`, + verb: 'POST' + } + }); + + Self.saveSign = async(ctx, options) => { + const args = Object.assign({}, ctx.args); + const models = Self.app.models; + const myOptions = {}; + let tx; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + async function setLocation(ticketId) { + await models.Delivery.create({ + ticketFk: ticketId, + longitude: args.location.Longitude, + latitude: args.location.Latitude, + dated: args.signedTime || new Date() + }, myOptions); + } + + async function gestDocExists(ticketId) { + const ticketDms = await models.TicketDms.findOne({ + where: {ticketFk: ticketId}, + fields: ['dmsFk'] + }, myOptions); + + if (!ticketDms) return false; + + const ticket = await models.Ticket.findById(ticketId, {fields: ['isSigned']}, myOptions); + if (ticket.isSigned == true) + return true; + else + await models.Dms.destroyAll({where: {reference: ticketId}}, myOptions); + + return false; + } + + async function createGestDoc(id) { + const ticket = await models.Ticket.findById(id, + {include: [ + { + relation: 'warehouse', + scope: { + fields: ['id'] + } + }, { + relation: 'client', + scope: { + fields: ['name'] + } + }, { + relation: 'route', + scope: { + fields: ['id'] + } + } + ] + }, myOptions); + const dmsType = await models.DmsType.findOne({where: {code: 'Ticket'}, fields: ['id']}, myOptions); + const ctxUploadFile = Object.assign({}, ctx); + ctxUploadFile.args = { + warehouseId: ticket.warehouseFk, + companyId: ticket.companyFk, + dmsTypeId: dmsType.id, + reference: id, + description: `Ticket ${id} Cliente ${ticket.client().name} Ruta ${ticket.route().id}`, + hasFile: true + }; + await models.Ticket.uploadFile(ctxUploadFile, id, myOptions); + } + + try { + for (let i = 0; i < args.tickets.length; i++) { + const ticketState = await models.TicketState.findOne( + {where: {ticketFk: args.tickets[i]}, + fields: ['alertLevel'] + }, myOptions); + + const packedAlertLevel = await models.AlertLevel.findOne({where: {code: 'PACKED'}, + fields: ['id'] + }, myOptions); + + if (ticketState.alertLevel < packedAlertLevel.id) + throw new UserError('This ticket cannot be signed because it has not been boxed'); + else if (!await gestDocExists(args.tickets[i])) { + if (args.location) setLocation(args.tickets[i]); + await createGestDoc(args.tickets[i]); + await Self.rawSql(`CALL vn.ticket_setState(?, ?)`, [args.tickets[i], 'DELIVERED'], myOptions); + } + } + + if (tx) await tx.commit(); + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/ticket/back/methods/ticket/specs/merge.spec.js b/modules/ticket/back/methods/ticket/specs/merge.spec.js index 6b533e47c..78eb0c8f3 100644 --- a/modules/ticket/back/methods/ticket/specs/merge.spec.js +++ b/modules/ticket/back/methods/ticket/specs/merge.spec.js @@ -2,13 +2,13 @@ const models = require('vn-loopback/server/server').models; const LoopBackContext = require('loopback-context'); describe('ticket merge()', () => { - const tickets = [{ + const tickets = { originId: 13, destinationId: 12, originShipped: Date.vnNew(), destinationShipped: Date.vnNew(), workerFk: 1 - }]; + }; const activeCtx = { accessToken: {userId: 9}, @@ -37,14 +37,14 @@ describe('ticket merge()', () => { const options = {transaction: tx}; const chatNotificationBeforeMerge = await models.Chat.find(); - await models.Ticket.merge(ctx, tickets, options); + await models.Ticket.merge(ctx, [tickets], options); - const createdTicketLog = await models.TicketLog.find({where: {originFk: tickets[0].originId}}, options); - const deletedTicket = await models.Ticket.findOne({where: {id: tickets[0].originId}}, options); - const salesTicketFuture = await models.Sale.find({where: {ticketFk: tickets[0].destinationId}}, options); + const createdTicketLog = await models.TicketLog.find({where: {originFk: tickets.originId}}, options); + const deletedTicket = await models.Ticket.findOne({where: {id: tickets.originId}}, options); + const salesTicketFuture = await models.Sale.find({where: {ticketFk: tickets.destinationId}}, options); const chatNotificationAfterMerge = await models.Chat.find(); - expect(createdTicketLog.length).toEqual(1); + expect(createdTicketLog.length).toEqual(2); expect(deletedTicket.isDeleted).toEqual(true); expect(salesTicketFuture.length).toEqual(2); expect(chatNotificationBeforeMerge.length).toEqual(chatNotificationAfterMerge.length - 2); diff --git a/modules/ticket/back/methods/ticket/specs/saveSign.spec.js b/modules/ticket/back/methods/ticket/specs/saveSign.spec.js new file mode 100644 index 000000000..6b532a5d1 --- /dev/null +++ b/modules/ticket/back/methods/ticket/specs/saveSign.spec.js @@ -0,0 +1,32 @@ +const models = require('vn-loopback/server/server').models; + +describe('Ticket saveSign()', () => { + const FormData = require('form-data'); + const data = new FormData(); + let ctx = {req: { + accessToken: {userId: 9}, + headers: { + ...data.getHeaders() + } + + }}; + + it(`should throw error if the ticket's alert level is lower than 2`, async() => { + const tx = await models.TicketDms.beginTransaction({}); + const ticketWithOkState = 12; + let error; + try { + const options = {transaction: tx}; + ctx.args = {tickets: [ticketWithOkState]}; + + await models.Ticket.saveSign(ctx, options); + + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error).toBeDefined(); + }); +}); diff --git a/modules/ticket/back/methods/ticket/transferSales.js b/modules/ticket/back/methods/ticket/transferSales.js index 57f620f3d..48035648c 100644 --- a/modules/ticket/back/methods/ticket/transferSales.js +++ b/modules/ticket/back/methods/ticket/transferSales.js @@ -105,8 +105,8 @@ module.exports = Self => { originFk: id, userFk: userId, action: 'update', - changedModel: 'Ticket', - changedModelId: id, + changedModel: 'Sale', + changedModelId: sale.id, oldInstance: { item: originalSaleData.itemFk, quantity: originalSaleData.quantity, @@ -126,8 +126,8 @@ module.exports = Self => { originFk: ticketId, userFk: userId, action: 'update', - changedModel: 'Ticket', - changedModelId: ticketId, + changedModel: 'Sale', + changedModelId: sale.id, oldInstance: { item: originalSaleData.itemFk, quantity: originalSaleData.quantity, @@ -177,16 +177,16 @@ module.exports = Self => { // Update original sale const rest = originalSale.quantity - sale.quantity; - query = `UPDATE sale + query = `UPDATE sale SET quantity = ? WHERE id = ?`; await Self.rawSql(query, [rest, sale.id], options); // Clone sale with new quantity - query = `INSERT INTO sale (itemFk, ticketFk, concept, quantity, originalQuantity, price, discount, priceFixed, + query = `INSERT INTO sale (itemFk, ticketFk, concept, quantity, originalQuantity, price, discount, priceFixed, reserved, isPicked, isPriceFixed, isAdded) - SELECT itemFk, ?, concept, ?, originalQuantity, price, discount, priceFixed, - reserved, isPicked, isPriceFixed, isAdded + SELECT itemFk, ?, concept, ?, originalQuantity, price, discount, priceFixed, + reserved, isPicked, isPriceFixed, isAdded FROM sale WHERE id = ?`; await Self.rawSql(query, [ticketId, sale.quantity, sale.id], options); diff --git a/modules/ticket/back/models/ticket-methods.js b/modules/ticket/back/models/ticket-methods.js index 73df0579c..3992e7307 100644 --- a/modules/ticket/back/models/ticket-methods.js +++ b/modules/ticket/back/models/ticket-methods.js @@ -39,4 +39,5 @@ module.exports = function(Self) { require('../methods/ticket/isRoleAdvanced')(Self); require('../methods/ticket/collectionLabel')(Self); require('../methods/ticket/expeditionPalletLabel')(Self); + require('../methods/ticket/saveSign')(Self); }; diff --git a/modules/ticket/back/models/ticket.json b/modules/ticket/back/models/ticket.json index 09b01d213..1cf2642a5 100644 --- a/modules/ticket/back/models/ticket.json +++ b/modules/ticket/back/models/ticket.json @@ -36,7 +36,7 @@ "type": "number" }, "updated": { - "type": "date", + "type": "date", "mysql": { "columnName": "created" } @@ -44,6 +44,9 @@ "isDeleted": { "type": "boolean" }, + "isSigned": { + "type": "boolean" + }, "priority": { "type": "number" }, @@ -136,4 +139,4 @@ "foreignKey": "zoneFk" } } -} \ No newline at end of file +} diff --git a/modules/travel/back/methods/travel/extraCommunityEmail.js b/modules/travel/back/methods/travel/extraCommunityEmail.js index dd93ed905..f4b09b79d 100644 --- a/modules/travel/back/methods/travel/extraCommunityEmail.js +++ b/modules/travel/back/methods/travel/extraCommunityEmail.js @@ -1,5 +1,3 @@ -const {Email} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('extraCommunityEmail', { description: 'Sends the extra community email with an attached PDF', @@ -74,19 +72,5 @@ module.exports = Self => { } }); - Self.extraCommunityEmail = async ctx => { - const args = Object.assign({}, ctx.args); - const params = { - recipient: args.recipient, - lang: ctx.req.getLocale() - }; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const email = new Email('extra-community', params); - - return email.send(); - }; + Self.extraCommunityEmail = ctx => Self.sendTemplate(ctx, 'extra-community'); }; diff --git a/modules/travel/back/methods/travel/extraCommunityPdf.js b/modules/travel/back/methods/travel/extraCommunityPdf.js index a68e5cd09..676b98be2 100644 --- a/modules/travel/back/methods/travel/extraCommunityPdf.js +++ b/modules/travel/back/methods/travel/extraCommunityPdf.js @@ -1,5 +1,3 @@ -const {Report} = require('vn-print'); - module.exports = Self => { Self.remoteMethodCtx('extraCommunityPdf', { description: 'Returns the extra community pdf', @@ -11,6 +9,16 @@ module.exports = Self => { description: 'The recipient id', required: false }, + { + arg: 'filter', + type: 'object', + description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string' + }, + { + arg: 'search', + type: 'string', + description: 'Searchs the travel by id' + }, { arg: 'landedTo', type: 'date' @@ -73,17 +81,5 @@ module.exports = Self => { } }); - Self.extraCommunityPdf = async ctx => { - const args = Object.assign({}, ctx.args); - const params = {lang: ctx.req.getLocale()}; - - delete args.ctx; - for (const param in args) - params[param] = args[param]; - - const report = new Report('extra-community', params); - const stream = await report.toPdfStream(); - - return [stream, 'application/pdf', `filename="extra-community.pdf"`]; - }; + Self.extraCommunityPdf = ctx => Self.printReport(ctx, null, 'extra-community'); }; diff --git a/modules/travel/front/extra-community/index.html b/modules/travel/front/extra-community/index.html index 73e62b174..c888f97da 100644 --- a/modules/travel/front/extra-community/index.html +++ b/modules/travel/front/extra-community/index.html @@ -1,7 +1,7 @@ { + Self.remoteMethodCtx('getMailStates', { + description: 'Get the states of a month about time control mail', + accessType: 'READ', + accepts: [{ + arg: 'id', + type: 'number', + description: 'The worker id', + http: {source: 'path'} + }, + { + arg: 'month', + type: 'number', + description: 'The number of the month' + }, + { + arg: 'year', + type: 'number', + description: 'The number of the year' + }], + returns: [{ + type: ['object'], + root: true + }], + http: { + path: `/:id/getMailStates`, + verb: 'GET' + } + }); + + Self.getMailStates = async(ctx, workerId, options) => { + const models = Self.app.models; + const args = ctx.args; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const times = await models.Time.find({ + fields: ['week'], + where: { + month: args.month, + year: args.year + } + }, myOptions); + + const weeks = times.map(time => time.week); + const weekNumbersSet = new Set(weeks); + const weekNumbers = Array.from(weekNumbersSet); + + const workerTimeControlMails = await models.WorkerTimeControlMail.find({ + where: { + workerFk: workerId, + year: args.year, + week: {inq: weekNumbers} + } + }, myOptions); + + return workerTimeControlMails; + }; +}; diff --git a/modules/worker/back/methods/worker-time-control/specs/getMailStates.spec.js b/modules/worker/back/methods/worker-time-control/specs/getMailStates.spec.js new file mode 100644 index 000000000..cbad32323 --- /dev/null +++ b/modules/worker/back/methods/worker-time-control/specs/getMailStates.spec.js @@ -0,0 +1,29 @@ +const models = require('vn-loopback/server/server').models; + +describe('workerTimeControl getMailStates()', () => { + const workerId = 9; + const ctx = {args: { + month: 12, + year: 2000 + }}; + + it('should get the states of a month about time control mail', async() => { + const tx = await models.WorkerTimeControl.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const response = await models.WorkerTimeControl.getMailStates(ctx, workerId, options); + + expect(response[0].state).toEqual('REVISE'); + expect(response[1].state).toEqual('SENDED'); + expect(response[2].state).toEqual('CONFIRMED'); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); + diff --git a/modules/worker/back/methods/worker-time-control/updateWorkerTimeControlMail.js b/modules/worker/back/methods/worker-time-control/updateWorkerTimeControlMail.js index a8dc14bb1..642ff90d2 100644 --- a/modules/worker/back/methods/worker-time-control/updateWorkerTimeControlMail.js +++ b/modules/worker/back/methods/worker-time-control/updateWorkerTimeControlMail.js @@ -47,6 +47,10 @@ module.exports = Self => { if (typeof options == 'object') Object.assign(myOptions, options); + const isHimself = userId == args.workerId; + if (!isHimself) + throw new UserError(`You don't have enough privileges`); + const workerTimeControlMail = await models.WorkerTimeControlMail.findOne({ where: { workerFk: args.workerId, @@ -60,8 +64,6 @@ module.exports = Self => { const oldState = workerTimeControlMail.state; const oldReason = workerTimeControlMail.reason; - if (oldState == args.state) throw new UserError('Already has this status'); - await workerTimeControlMail.updateAttributes({ state: args.state, reason: args.reason || null diff --git a/modules/worker/back/models/time.json b/modules/worker/back/models/time.json index df9257540..e2f1161d7 100644 --- a/modules/worker/back/models/time.json +++ b/modules/worker/back/models/time.json @@ -14,6 +14,9 @@ "year": { "type": "number" }, + "month": { + "type": "number" + }, "week": { "type": "number" } diff --git a/modules/worker/back/models/worker-time-control.js b/modules/worker/back/models/worker-time-control.js index 7339f5d15..5b13e17f2 100644 --- a/modules/worker/back/models/worker-time-control.js +++ b/modules/worker/back/models/worker-time-control.js @@ -8,6 +8,7 @@ module.exports = Self => { require('../methods/worker-time-control/sendMail')(Self); require('../methods/worker-time-control/updateWorkerTimeControlMail')(Self); require('../methods/worker-time-control/weeklyHourRecordEmail')(Self); + require('../methods/worker-time-control/getMailStates')(Self); Self.rewriteDbError(function(err) { if (err.code === 'ER_DUP_ENTRY') diff --git a/modules/worker/front/calendar/index.html b/modules/worker/front/calendar/index.html index cff4d0bc9..c9eacbd82 100644 --- a/modules/worker/front/calendar/index.html +++ b/modules/worker/front/calendar/index.html @@ -25,11 +25,11 @@
{{'Contract' | translate}} #{{$ctrl.businessId}}
- {{'Used' | translate}} {{$ctrl.contractHolidays.holidaysEnjoyed || 0}} + {{'Used' | translate}} {{$ctrl.contractHolidays.holidaysEnjoyed || 0}} {{'of' | translate}} {{$ctrl.contractHolidays.totalHolidays || 0}} {{'days' | translate}}
- {{'Spent' | translate}} {{$ctrl.contractHolidays.hoursEnjoyed || 0}} + {{'Spent' | translate}} {{$ctrl.contractHolidays.hoursEnjoyed || 0}} {{'of' | translate}} {{$ctrl.contractHolidays.totalHours || 0}} {{'hours' | translate}}
@@ -40,11 +40,11 @@
{{'Year' | translate}} {{$ctrl.year}}
- {{'Used' | translate}} {{$ctrl.yearHolidays.holidaysEnjoyed || 0}} + {{'Used' | translate}} {{$ctrl.yearHolidays.holidaysEnjoyed || 0}} {{'of' | translate}} {{$ctrl.yearHolidays.totalHolidays || 0}} {{'days' | translate}}
- {{'Spent' | translate}} {{$ctrl.yearHolidays.hoursEnjoyed || 0}} + {{'Spent' | translate}} {{$ctrl.yearHolidays.hoursEnjoyed || 0}} {{'of' | translate}} {{$ctrl.yearHolidays.totalHours || 0}} {{'hours' | translate}}
@@ -66,7 +66,7 @@ order="businessFk DESC" limit="5"> -
#{{businessFk}}
+
#{{businessFk}}
{{started | date: 'dd/MM/yyyy'}} - {{ended ? (ended | date: 'dd/MM/yyyy') : 'Indef.'}}
@@ -87,17 +87,17 @@ - Festive + Festive - Current day + Current day
- diff --git a/modules/worker/front/create/index.js b/modules/worker/front/create/index.js index 7e837fe02..7966926b0 100644 --- a/modules/worker/front/create/index.js +++ b/modules/worker/front/create/index.js @@ -13,14 +13,20 @@ export default class Controller extends Section { }); } + get ibanCountry() { + if (!this.worker || !this.worker.iban) return false; + + let countryCode = this.worker.iban.substr(0, 2); + + return countryCode; + } + autofillBic() { if (!this.worker || !this.worker.iban) return; let bankEntityId = parseInt(this.worker.iban.substr(4, 4)); let filter = {where: {id: bankEntityId}}; - if (this.ibanCountry != 'ES') return; - this.$http.get(`BankEntities`, {filter}).then(response => { const hasData = response.data && response.data[0]; diff --git a/modules/worker/front/time-control/index.html b/modules/worker/front/time-control/index.html index 681d420d0..bd7e68b89 100644 --- a/modules/worker/front/time-control/index.html +++ b/modules/worker/front/time-control/index.html @@ -1,7 +1,7 @@ @@ -16,7 +16,7 @@ {{::weekday.dated | date: 'MMMM'}} - - + - - + - + + + + - +
Hours
- -
@@ -106,6 +122,8 @@ vn-id="calendar" class="vn-pt-md" ng-model="$ctrl.date" + format-week="$ctrl.formatWeek($element)" + on-move="$ctrl.getMailStates($date)" has-events="$ctrl.hasEvents($day)">
@@ -166,15 +184,31 @@ vn-id="reason" on-accept="$ctrl.isUnsatisfied()"> - - +
+ + +
- + - \ No newline at end of file + + + + + Are you sure you want to send it? + + + + + + diff --git a/modules/worker/front/time-control/index.js b/modules/worker/front/time-control/index.js index f7379fea0..9ed454d31 100644 --- a/modules/worker/front/time-control/index.js +++ b/modules/worker/front/time-control/index.js @@ -1,6 +1,7 @@ import ngModule from '../module'; import Section from 'salix/components/section'; import './style.scss'; +import UserError from 'core/lib/user-error'; class Controller extends Section { constructor($element, $, vnWeekDays) { @@ -24,12 +25,31 @@ class Controller extends Section { } this.date = initialDate; + + this.getMailStates(this.date); + } + + get isHr() { + return this.aclService.hasAny(['hr']); + } + + get isHimSelf() { + const userId = window.localStorage.currentUserWorkerId; + return userId == this.$params.id; } get worker() { return this._worker; } + get weekNumber() { + return this.getWeekNumber(this.date); + } + + set weekNumber(value) { + this._weekNumber = value; + } + set worker(value) { this._worker = value; } @@ -68,6 +88,27 @@ class Controller extends Section { } this.fetchHours(); + this.getWeekData(); + } + + getWeekData() { + const filter = { + where: { + workerFk: this.$params.id, + year: this._date.getFullYear(), + week: this.getWeekNumber(this._date) + } + }; + this.$http.get('WorkerTimeControlMails', {filter}) + .then(res => { + const workerTimeControlMail = res.data; + if (!workerTimeControlMail.length) { + this.state = null; + return; + } + this.state = workerTimeControlMail[0].state; + this.reason = workerTimeControlMail[0].reason; + }); } /** @@ -294,42 +335,56 @@ class Controller extends Section { this.$.editEntry.show($event); } - getWeekNumber(currentDate) { - const startDate = new Date(currentDate.getFullYear(), 0, 1); - let days = Math.floor((currentDate - startDate) / - (24 * 60 * 60 * 1000)); - return Math.ceil(days / 7); + getWeekNumber(date) { + const tempDate = new Date(date); + let dayOfWeek = tempDate.getDay(); + dayOfWeek = (dayOfWeek === 0) ? 7 : dayOfWeek; + const firstDayOfWeek = new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate() - (dayOfWeek - 1)); + const firstDayOfYear = new Date(tempDate.getFullYear(), 0, 1); + const differenceInMilliseconds = firstDayOfWeek.getTime() - firstDayOfYear.getTime(); + const weekNumber = Math.floor(differenceInMilliseconds / (1000 * 60 * 60 * 24 * 7)) + 1; + return weekNumber; } isSatisfied() { - const weekNumber = this.getWeekNumber(this.date); const params = { workerId: this.worker.id, year: this.date.getFullYear(), - week: weekNumber, + week: this.weekNumber, state: 'CONFIRMED' }; const query = `WorkerTimeControls/updateWorkerTimeControlMail`; this.$http.post(query, params).then(() => { + this.getMailStates(this.date); + this.getWeekData(); this.vnApp.showSuccess(this.$t('Data saved!')); }); } isUnsatisfied() { - const weekNumber = this.getWeekNumber(this.date); + if (!this.reason) throw new UserError(`You must indicate a reason`); + const params = { workerId: this.worker.id, year: this.date.getFullYear(), - week: weekNumber, + week: this.weekNumber, state: 'REVISE', reason: this.reason }; const query = `WorkerTimeControls/updateWorkerTimeControlMail`; this.$http.post(query, params).then(() => { + this.getMailStates(this.date); + this.getWeekData(); this.vnApp.showSuccess(this.$t('Data saved!')); }); } + changeState(state, reason) { + this.state = state; + this.reason = reason; + this.repaint(); + } + save() { try { const entry = this.selectedRow; @@ -345,6 +400,77 @@ class Controller extends Section { this.vnApp.showError(this.$t(e.message)); } } + + resendEmail() { + const timestamp = this.date.getTime() / 1000; + const url = `${window.location.origin}/#!/worker/${this.worker.id}/time-control?timestamp=${timestamp}`; + const params = { + recipient: this.worker.user.emailUser.email, + week: this.weekNumber, + year: this.date.getFullYear(), + url: url, + }; + this.$http.post(`WorkerTimeControls/weekly-hour-hecord-email`, params) + .then(() => { + this.vnApp.showSuccess(this.$t('Email sended')); + }); + } + + getTime(timeString) { + const [hours, minutes, seconds] = timeString.split(':'); + return [parseInt(hours), parseInt(minutes), parseInt(seconds)]; + } + + getMailStates(date) { + const params = { + month: date.getMonth() + 1, + year: date.getFullYear() + }; + const query = `WorkerTimeControls/${this.$params.id}/getMailStates`; + this.$http.get(query, {params}) + .then(res => { + this.workerTimeControlMails = res.data; + this.repaint(); + }); + } + + formatWeek($element) { + const weekNumberHTML = $element.firstElementChild; + const weekNumberValue = weekNumberHTML.innerHTML; + + if (!this.workerTimeControlMails) return; + const workerTimeControlMail = this.workerTimeControlMails.find( + workerTimeControlMail => workerTimeControlMail.week == weekNumberValue + ); + + if (!workerTimeControlMail) return; + const state = workerTimeControlMail.state; + + if (state == 'CONFIRMED') { + weekNumberHTML.classList.remove('revise'); + weekNumberHTML.classList.remove('sended'); + + weekNumberHTML.classList.add('confirmed'); + weekNumberHTML.setAttribute('title', 'Conforme'); + } + if (state == 'REVISE') { + weekNumberHTML.classList.remove('confirmed'); + weekNumberHTML.classList.remove('sended'); + + weekNumberHTML.classList.add('revise'); + weekNumberHTML.setAttribute('title', 'No conforme'); + } + if (state == 'SENDED') { + weekNumberHTML.classList.add('sended'); + weekNumberHTML.setAttribute('title', 'Pendiente'); + } + } + + repaint() { + let calendars = this.element.querySelectorAll('vn-calendar'); + for (let calendar of calendars) + calendar.$ctrl.repaint(); + } } Controller.$inject = ['$element', '$scope', 'vnWeekDays']; diff --git a/modules/worker/front/time-control/index.spec.js b/modules/worker/front/time-control/index.spec.js index b68162d39..0f9b48f6b 100644 --- a/modules/worker/front/time-control/index.spec.js +++ b/modules/worker/front/time-control/index.spec.js @@ -5,12 +5,14 @@ describe('Component vnWorkerTimeControl', () => { let $scope; let $element; let controller; + let $httpParamSerializer; beforeEach(ngModule('worker')); - beforeEach(inject(($componentController, $rootScope, $stateParams, _$httpBackend_) => { + beforeEach(inject(($componentController, $rootScope, $stateParams, _$httpBackend_, _$httpParamSerializer_) => { $stateParams.id = 1; $httpBackend = _$httpBackend_; + $httpParamSerializer = _$httpParamSerializer_; $scope = $rootScope.$new(); $element = angular.element(''); controller = $componentController('vnWorkerTimeControl', {$element, $scope}); @@ -82,6 +84,9 @@ describe('Component vnWorkerTimeControl', () => { $httpBackend.whenRoute('GET', 'Workers/:id/getWorkedHours') .respond(response); + $httpBackend.whenRoute('GET', 'WorkerTimeControlMails') + .respond([]); + today.setHours(0, 0, 0, 0); let weekOffset = today.getDay() - 1; @@ -97,7 +102,6 @@ describe('Component vnWorkerTimeControl', () => { controller.ended = ended; controller.getWorkedHours(controller.started, controller.ended); - $httpBackend.flush(); expect(controller.weekDays.length).toEqual(7); @@ -152,5 +156,120 @@ describe('Component vnWorkerTimeControl', () => { expect(controller.date.toDateString()).toEqual(date.toDateString()); }); }); + + describe('getWeekData() ', () => { + it(`should make a query an then update the state and reason`, () => { + const today = Date.vnNew(); + const response = [ + { + state: 'SENDED', + reason: null + } + ]; + + controller._date = today; + + $httpBackend.whenRoute('GET', 'WorkerTimeControlMails') + .respond(response); + + controller.getWeekData(); + $httpBackend.flush(); + + expect(controller.state).toBe('SENDED'); + expect(controller.reason).toBe(null); + }); + }); + + describe('isSatisfied() ', () => { + it(`should make a query an then call three methods`, () => { + const today = Date.vnNew(); + jest.spyOn(controller, 'getWeekData').mockReturnThis(); + jest.spyOn(controller, 'getMailStates').mockReturnThis(); + jest.spyOn(controller.vnApp, 'showSuccess'); + + controller.$.model = {applyFilter: jest.fn().mockReturnValue(Promise.resolve())}; + controller.worker = {id: 1}; + controller.date = today; + controller.weekNumber = 1; + + $httpBackend.expect('POST', 'WorkerTimeControls/updateWorkerTimeControlMail').respond(); + controller.isSatisfied(); + $httpBackend.flush(); + + expect(controller.getMailStates).toHaveBeenCalledWith(controller.date); + expect(controller.getWeekData).toHaveBeenCalled(); + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + }); + }); + + describe('isUnsatisfied() ', () => { + it(`should throw an error is reason is empty`, () => { + let error; + try { + controller.isUnsatisfied(); + } catch (e) { + error = e; + } + + expect(error).toBeDefined(); + expect(error.message).toBe(`You must indicate a reason`); + }); + + it(`should make a query an then call three methods`, () => { + const today = Date.vnNew(); + jest.spyOn(controller, 'getWeekData').mockReturnThis(); + jest.spyOn(controller, 'getMailStates').mockReturnThis(); + jest.spyOn(controller.vnApp, 'showSuccess'); + + controller.$.model = {applyFilter: jest.fn().mockReturnValue(Promise.resolve())}; + controller.worker = {id: 1}; + controller.date = today; + controller.weekNumber = 1; + controller.reason = 'reason'; + + $httpBackend.expect('POST', 'WorkerTimeControls/updateWorkerTimeControlMail').respond(); + controller.isSatisfied(); + $httpBackend.flush(); + + expect(controller.getMailStates).toHaveBeenCalledWith(controller.date); + expect(controller.getWeekData).toHaveBeenCalled(); + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + }); + }); + + describe('resendEmail() ', () => { + it(`should make a query an then call showSuccess method`, () => { + const today = Date.vnNew(); + jest.spyOn(controller, 'getWeekData').mockReturnThis(); + jest.spyOn(controller.vnApp, 'showSuccess'); + + controller.$.model = {applyFilter: jest.fn().mockReturnValue(Promise.resolve())}; + controller.worker = {id: 1}; + controller.worker = {user: {emailUser: {email: 'employee@verdnatura.es'}}}; + controller.date = today; + controller.weekNumber = 1; + + $httpBackend.expect('POST', 'WorkerTimeControls/weekly-hour-hecord-email').respond(); + controller.resendEmail(); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + }); + }); + + describe('getMailStates() ', () => { + it(`should make a query an then call showSuccess method`, () => { + const today = Date.vnNew(); + jest.spyOn(controller, 'repaint').mockReturnThis(); + + controller.$params = {id: 1}; + + $httpBackend.expect('GET', `WorkerTimeControls/1/getMailStates?month=1&year=2001`).respond(); + controller.getMailStates(today); + $httpBackend.flush(); + + expect(controller.repaint).toHaveBeenCalled(); + }); + }); }); }); diff --git a/modules/worker/front/time-control/locale/es.yml b/modules/worker/front/time-control/locale/es.yml index 2a3bffc00..091c01baa 100644 --- a/modules/worker/front/time-control/locale/es.yml +++ b/modules/worker/front/time-control/locale/es.yml @@ -13,4 +13,10 @@ Entry removed: Fichada borrada The entry type can't be empty: El tipo de fichada no puede quedar vacía Satisfied: Conforme Not satisfied: No conforme -Reason: Motivo \ No newline at end of file +Reason: Motivo +Resend: Reenviar +Email sended: Email enviado +You must indicate a reason: Debes indicar un motivo +Send time control email: Enviar email control horario +Are you sure you want to send it?: ¿Seguro que quieres enviarlo? +Resend email of this week to the user: Reenviar email de esta semana al usuario diff --git a/modules/worker/front/time-control/style.scss b/modules/worker/front/time-control/style.scss index 6a46921a7..9d7545aaf 100644 --- a/modules/worker/front/time-control/style.scss +++ b/modules/worker/front/time-control/style.scss @@ -14,7 +14,7 @@ vn-worker-time-control { align-items: center; justify-content: center; padding: 4px 0; - + & > vn-icon { color: $color-font-secondary; padding-right: 1px; @@ -24,8 +24,29 @@ vn-worker-time-control { .totalBox { max-width: none } + +} + +.reasonDialog{ + min-width: 500px; } .edit-time-entry { width: 200px -} \ No newline at end of file +} + +.right { + float: right; + } + +.confirmed { + color: #97B92F; +} + +.revise { + color: #f61e1e; +} + +.sended { + color: #d19b25; +} diff --git a/modules/zone/back/models/zone.json b/modules/zone/back/models/zone.json index 1e97c1bad..06ea5ca2b 100644 --- a/modules/zone/back/models/zone.json +++ b/modules/zone/back/models/zone.json @@ -38,6 +38,9 @@ "inflation": { "type": "number" }, + "m3Max": { + "type": "number" + }, "itemMaxSize": { "type": "number" } diff --git a/modules/zone/front/basic-data/index.html b/modules/zone/front/basic-data/index.html index 1836216a2..8d8fc6c56 100644 --- a/modules/zone/front/basic-data/index.html +++ b/modules/zone/front/basic-data/index.html @@ -30,14 +30,21 @@ rule> - + vn-one + label="Max m³" + ng-model="$ctrl.zone.itemMaxSize" + min="0" + vn-acl="deliveryBoss" + rule> + + + =0.10.0" } }, + "node_modules/array-parallel": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/array-parallel/-/array-parallel-0.1.3.tgz", + "integrity": "sha512-TDPTwSWW5E4oiFiKmz6RGJ/a80Y91GuLgUYuLd49+XBS75tYo8PNgaT2K/OxuQYqkoI852MDGBorg9OcUSTQ8w==" + }, + "node_modules/array-series": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/array-series/-/array-series-0.1.5.tgz", + "integrity": "sha512-L0XlBwfx9QetHOsbLDrE/vh2t018w9462HM3iaFfxRiK83aJjAt/Ja3NMkOW7FICwWTlQBa3ZbL5FKhuQWkDrg==" + }, "node_modules/array-slice": { "version": "1.1.0", "dev": true, @@ -9284,6 +9295,67 @@ "node": ">= 0.10" } }, + "node_modules/gm": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/gm/-/gm-1.25.0.tgz", + "integrity": "sha512-4kKdWXTtgQ4biIo7hZA396HT062nDVVHPjQcurNZ3o/voYN+o5FUC5kOwuORbpExp3XbTJ3SU7iRipiIhQtovw==", + "dependencies": { + "array-parallel": "~0.1.3", + "array-series": "~0.1.5", + "cross-spawn": "^4.0.0", + "debug": "^3.1.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gm/node_modules/cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA==", + "dependencies": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "node_modules/gm/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/gm/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/gm/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/gm/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/gm/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + }, "node_modules/google-auth-library": { "version": "3.1.2", "license": "Apache-2.0", @@ -28673,6 +28745,16 @@ } } }, + "array-parallel": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/array-parallel/-/array-parallel-0.1.3.tgz", + "integrity": "sha512-TDPTwSWW5E4oiFiKmz6RGJ/a80Y91GuLgUYuLd49+XBS75tYo8PNgaT2K/OxuQYqkoI852MDGBorg9OcUSTQ8w==" + }, + "array-series": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/array-series/-/array-series-0.1.5.tgz", + "integrity": "sha512-L0XlBwfx9QetHOsbLDrE/vh2t018w9462HM3iaFfxRiK83aJjAt/Ja3NMkOW7FICwWTlQBa3ZbL5FKhuQWkDrg==" + }, "array-slice": { "version": "1.1.0", "dev": true @@ -32611,6 +32693,63 @@ "sparkles": "^1.0.0" } }, + "gm": { + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/gm/-/gm-1.25.0.tgz", + "integrity": "sha512-4kKdWXTtgQ4biIo7hZA396HT062nDVVHPjQcurNZ3o/voYN+o5FUC5kOwuORbpExp3XbTJ3SU7iRipiIhQtovw==", + "requires": { + "array-parallel": "~0.1.3", + "array-series": "~0.1.5", + "cross-spawn": "^4.0.0", + "debug": "^3.1.0" + }, + "dependencies": { + "cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha512-yAXz/pA1tD8Gtg2S98Ekf/sewp3Lcp3YoFKJ4Hkp5h5yLWnKVTDU0kwjKJ8NDCYcfTLfyGkzTikst+jWypT1iA==", + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + } + } + }, "google-auth-library": { "version": "3.1.2", "requires": { diff --git a/package.json b/package.json index 30c8039ac..3d0fc4aed 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-back", - "version": "23.10.01", + "version": "23.12.01", "author": "Verdnatura Levante SL", "description": "Salix backend", "license": "GPL-3.0", @@ -20,6 +20,7 @@ "form-data": "^4.0.0", "fs-extra": "^5.0.0", "ftps": "^1.2.0", + "gm": "^1.25.0", "got": "^10.7.0", "helmet": "^3.21.2", "i18n": "^0.8.4", diff --git a/print/templates/reports/balance-compensation/balance-compensation.html b/print/templates/reports/balance-compensation/balance-compensation.html index d1a2788ed..c7448eeb9 100644 --- a/print/templates/reports/balance-compensation/balance-compensation.html +++ b/print/templates/reports/balance-compensation/balance-compensation.html @@ -17,8 +17,8 @@

{{$t('Agree') | uppercase}}

- {{$t('Date')}} {{client.payed | date('%d-%m-%Y')}} {{$t('Compensate')}} {{client.amountPaid}} € - {{$t('From client')}} {{client.name}} {{$t('Against the balance of')}}: {{client.invoiceFk}}. + {{$t('Date')}} {{formatDate(receipt.payed, '%d-%m-%Y')}} {{$t('Compensate')}} {{receipt.amountPaid}} € + {{$t('From client')}} {{client.name}} {{$t('Against the balance of')}}: {{receipt.description}}.

{{$t('Reception')}} administracion@verdnatura.es diff --git a/print/templates/reports/balance-compensation/balance-compensation.js b/print/templates/reports/balance-compensation/balance-compensation.js index bae7c5c3c..c2c2e9288 100644 --- a/print/templates/reports/balance-compensation/balance-compensation.js +++ b/print/templates/reports/balance-compensation/balance-compensation.js @@ -1,12 +1,31 @@ const vnReport = require('../../../core/mixins/vn-report.js'); +const app = require('vn-loopback/server/server'); module.exports = { name: 'balance-compensation', mixins: [vnReport], async serverPrefetch() { - this.client = await this.findOneFromDef('client', [this.id]); - this.checkMainEntity(this.client); - this.company = await this.findOneFromDef('company', [this.id]); + this.receipt = await app.models.Receipt.findOne({ + fields: ['amountPaid', 'payed', 'clientFk', 'companyFk', 'description'], + include: [ + { + relation: 'client', + scope: { + fields: ['name', 'street', 'fi', 'city'], + } + }, { + relation: 'supplier', + scope: { + fields: ['name', 'street', 'nif', 'city'], + } + } + ], + where: {id: this.id} + }); + this.client = this.receipt.client(); + this.company = this.receipt.supplier(); + + this.checkMainEntity(this.receipt); }, props: { id: { diff --git a/print/templates/reports/balance-compensation/sql/client.sql b/print/templates/reports/balance-compensation/sql/client.sql deleted file mode 100644 index c3679b68a..000000000 --- a/print/templates/reports/balance-compensation/sql/client.sql +++ /dev/null @@ -1,12 +0,0 @@ -SELECT - c.name, - c.socialName, - c.street, - c.fi, - c.city, - r.invoiceFk, - r.amountPaid, - r.payed - FROM client c - JOIN receipt r ON r.clientFk = c.id - WHERE r.id = ? diff --git a/print/templates/reports/balance-compensation/sql/company.sql b/print/templates/reports/balance-compensation/sql/company.sql deleted file mode 100644 index e61228a10..000000000 --- a/print/templates/reports/balance-compensation/sql/company.sql +++ /dev/null @@ -1,8 +0,0 @@ -SELECT - s.name, - s.nif, - s.street, - s.city - FROM supplier s - JOIN receipt r ON r.companyFk = s.id - WHERE r.id = ?; \ No newline at end of file diff --git a/print/templates/reports/extra-community/extra-community.html b/print/templates/reports/extra-community/extra-community.html index cd61a4f4d..3153b6b03 100644 --- a/print/templates/reports/extra-community/extra-community.html +++ b/print/templates/reports/extra-community/extra-community.html @@ -51,7 +51,7 @@ {{entry.supplierName}} {{entry.reference}} {{entry.volumeKg | number($i18n.locale)}} - {{entry.loadedKg | number($i18n.locale)}} + {{entry.loadedkg | number($i18n.locale)}} {{entry.stickers}} diff --git a/print/templates/reports/extra-community/extra-community.js b/print/templates/reports/extra-community/extra-community.js index 9941faa35..5d875d78f 100755 --- a/print/templates/reports/extra-community/extra-community.js +++ b/print/templates/reports/extra-community/extra-community.js @@ -1,11 +1,12 @@ const vnReport = require('../../../core/mixins/vn-report.js'); -const db = require(`vn-print/core/database`); +const app = require('vn-loopback/server/server'); module.exports = { name: 'extra-community', mixins: [vnReport], async serverPrefetch() { const args = { + search: this.search, landedTo: this.landedEnd, shippedFrom: this.shippedStart, continent: this.continent, @@ -17,76 +18,24 @@ module.exports = { ref: this.ref, cargoSupplierFk: this.cargoSupplierFk }; - - const travels = await this.fetchTravels(args); - this.checkMainEntity(travels); - const travelIds = travels.map(travel => travel.id); - const entries = await this.rawSqlFromDef('entries', [travelIds]); - - const map = new Map(); - for (let travel of travels) - map.set(travel.id, travel); - - for (let entry of entries) { - const travel = map.get(entry.travelFk); - if (!travel.entries) travel.entries = []; - travel.entries.push(entry); - } - - this.travels = travels; + const ctx = {args: args}; + this.travels = await app.models.Travel.extraCommunityFilter(ctx, this.filter); }, computed: { landedEnd: function() { if (!this.landedTo) return; - return formatDate(this.landedTo, '%Y-%m-%d'); + return this.formatDate(this.landedTo, '%Y-%m-%d'); }, shippedStart: function() { if (!this.shippedFrom) return; - return formatDate(this.shippedFrom, '%Y-%m-%d'); + return this.formatDate(this.shippedFrom, '%Y-%m-%d'); } }, methods: { - fetchTravels(args) { - const where = db.buildWhere(args, (key, value) => { - switch (key) { - case 'shippedFrom': - return `t.shipped >= ${value}`; - case 'landedTo': - return `t.landed <= ${value}`; - case 'continent': - return `cnt.code = ${value}`; - case 'ref': - return {'t.ref': {like: `%${value}%`}}; - case 'id': - return `t.id = ${value}`; - case 'agencyModeFk': - return `am.id = ${value}`; - case 'warehouseOutFk': - return `wo.id = ${value}`; - case 'warehouseInFk': - return `w.id = ${value}`; - case 'cargoSupplierFk': - return `s.id = ${value}`; - } - }); - - let query = this.getSqlFromDef('travels'); - query = db.merge(query, where); - query = db.merge(query, 'GROUP BY t.id'); - query = db.merge(query, ` - ORDER BY - shipped ASC, - landed ASC, - travelFk, - loadPriority, - agencyModeFk, - evaNotes - `); - - return this.rawSql(query); - }, }, props: [ + 'filter', + 'search', 'landedTo', 'shippedFrom', 'continent', diff --git a/print/templates/reports/extra-community/sql/entries.sql b/print/templates/reports/extra-community/sql/entries.sql deleted file mode 100644 index 84dc497c0..000000000 --- a/print/templates/reports/extra-community/sql/entries.sql +++ /dev/null @@ -1,18 +0,0 @@ -SELECT - e.id, - e.travelFk, - e.reference, - s.name AS supplierName, - SUM(b.stickers) AS stickers, - 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 - FROM travel t - JOIN entry e ON e.travelFk = t.id - JOIN buy b ON b.entryFk = e.id - JOIN packaging pkg ON pkg.id = b.packageFk - JOIN item i ON i.id = b.itemFk - JOIN itemType it ON it.id = i.typeFk - JOIN supplier s ON s.id = e.supplierFk - JOIN vn.volumeConfig vc - WHERE t.id IN(?) - GROUP BY e.id diff --git a/print/templates/reports/extra-community/sql/travels.sql b/print/templates/reports/extra-community/sql/travels.sql deleted file mode 100644 index b0987c330..000000000 --- a/print/templates/reports/extra-community/sql/travels.sql +++ /dev/null @@ -1,23 +0,0 @@ -SELECT - t.id, - t.ref, - t.shipped, - t.landed, - t.kg, - am.id AS agencyModeFk, - SUM(b.stickers) AS stickers, - 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 -FROM travel t - JOIN volumeConfig vc - 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 item i ON i.id = b.itemFk - LEFT JOIN itemType it ON it.id = i.typeFk - JOIN warehouse w ON w.id = t.warehouseInFk - JOIN warehouse wo ON wo.id = t.warehouseOutFk - JOIN country c ON c.id = wo.countryFk - LEFT JOIN continent cnt ON cnt.id = c.continentFk - JOIN agencyMode am ON am.id = t.agencyModeFk \ No newline at end of file