From b6c8ca20ee9e2eb7b932499300522913f0338175 Mon Sep 17 00:00:00 2001 From: sergiodt Date: Mon, 19 Jun 2023 08:58:20 +0200 Subject: [PATCH 01/22] refs #5339 data --- modules/worker/back/models/operator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/worker/back/models/operator.js b/modules/worker/back/models/operator.js index e9b590fa6..63b5154ed 100644 --- a/modules/worker/back/models/operator.js +++ b/modules/worker/back/models/operator.js @@ -1,6 +1,6 @@ module.exports = function(Self) { Self.observe('after save', async function(ctx) { - const instance = ctx.instance; + const instance = ctx.data; const models = Self.app.models; const options = ctx.options; From cea6fae64949f828aaf202e06ee82963e357cc07 Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 20 Jun 2023 10:40:57 +0200 Subject: [PATCH 02/22] fix: eliminada variable que no estaba definida --- modules/ticket/back/methods/ticket/setDeleted.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ticket/back/methods/ticket/setDeleted.js b/modules/ticket/back/methods/ticket/setDeleted.js index 7cc300547..40f3db8c5 100644 --- a/modules/ticket/back/methods/ticket/setDeleted.js +++ b/modules/ticket/back/methods/ticket/setDeleted.js @@ -81,7 +81,7 @@ module.exports = Self => { throw new UserError('You must delete all the buy requests first'); // removes item shelvings - if (hasItemShelvingSales && isSalesAssistant) { + if (hasItemShelvingSales && canDeleteTicketWithPartPrepared) { const promises = []; for (let sale of sales) { if (sale.itemShelvingSale()) { From e99627c0b238c4019fdd894ee154d38616f839dc Mon Sep 17 00:00:00 2001 From: vicent Date: Tue, 20 Jun 2023 11:28:25 +0200 Subject: [PATCH 03/22] codigo innecesario --- modules/ticket/back/methods/ticket/setDeleted.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/modules/ticket/back/methods/ticket/setDeleted.js b/modules/ticket/back/methods/ticket/setDeleted.js index 40f3db8c5..4d7fe73d4 100644 --- a/modules/ticket/back/methods/ticket/setDeleted.js +++ b/modules/ticket/back/methods/ticket/setDeleted.js @@ -81,17 +81,15 @@ module.exports = Self => { throw new UserError('You must delete all the buy requests first'); // removes item shelvings - if (hasItemShelvingSales && canDeleteTicketWithPartPrepared) { - const promises = []; - for (let sale of sales) { - if (sale.itemShelvingSale()) { - const itemShelvingSale = sale.itemShelvingSale(); - const destroyedShelving = models.ItemShelvingSale.destroyById(itemShelvingSale.id, myOptions); - promises.push(destroyedShelving); - } + const promises = []; + for (let sale of sales) { + if (sale.itemShelvingSale()) { + const itemShelvingSale = sale.itemShelvingSale(); + const destroyedShelving = models.ItemShelvingSale.destroyById(itemShelvingSale.id, myOptions); + promises.push(destroyedShelving); } - await Promise.all(promises); } + await Promise.all(promises); // Remove ticket greuges const ticketGreuges = await models.Greuge.find({where: {ticketFk: id}}, myOptions); From 18a810646136d890f9a38035d20c12720e84f079 Mon Sep 17 00:00:00 2001 From: alexm Date: Thu, 22 Jun 2023 08:56:27 +0200 Subject: [PATCH 04/22] refs #5339 fix after save --- modules/worker/back/models/operator.js | 4 ++-- modules/worker/back/models/operator.json | 10 ++-------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/modules/worker/back/models/operator.js b/modules/worker/back/models/operator.js index 63b5154ed..db1ac7e49 100644 --- a/modules/worker/back/models/operator.js +++ b/modules/worker/back/models/operator.js @@ -1,10 +1,10 @@ module.exports = function(Self) { Self.observe('after save', async function(ctx) { - const instance = ctx.data; + const instance = ctx.data || ctx.instance; const models = Self.app.models; const options = ctx.options; - if (!instance.sectorFk || !instance.labelerFk) return; + if (!instance?.sectorFk || !instance?.labelerFk) return; const sector = await models.Sector.findById(instance.sectorFk, { fields: ['mainPrinterFk'] diff --git a/modules/worker/back/models/operator.json b/modules/worker/back/models/operator.json index 9433a0fd5..2417078e1 100644 --- a/modules/worker/back/models/operator.json +++ b/modules/worker/back/models/operator.json @@ -16,18 +16,12 @@ "type": "number" }, "trainFk": { - "type": "number", - "required": true + "type": "number" }, "itemPackingTypeFk": { - "type": "string", - "required": true + "type": "string" }, "warehouseFk": { - "type": "number", - "required": true - }, - "sectorFk": { "type": "number" }, "labelerFk": { From c9f11830a202dc5d15423fa43cd79dc3bad971a1 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Fri, 23 Jun 2023 20:03:56 +0200 Subject: [PATCH 05/22] refs #5772 smtp.send() error throw flow fix --- print/core/smtp.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/print/core/smtp.js b/print/core/smtp.js index 04bede5b5..7ff9ca41d 100644 --- a/print/core/smtp.js +++ b/print/core/smtp.js @@ -20,14 +20,18 @@ module.exports = { options.to = config.app.senderEmail; } + let res; let error; - return this.transporter.sendMail(options).catch(err => { + try { + res = await this.transporter.sendMail(options); + } catch (err) { error = err; - throw err; - }).finally(async() => { + } finally { await this.mailLog(options, error); - }); + } + + return res; }, async mailLog(options, error) { From 665339c8d25252c9fc0eced9b0fd8dea1e8de239 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Fri, 23 Jun 2023 22:02:33 +0200 Subject: [PATCH 06/22] refs #5772 Fix for unhandled ENOENT error --- .../back/methods/invoiceOut/download.js | 68 +++++++++---------- print/core/email.js | 4 +- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/modules/invoiceOut/back/methods/invoiceOut/download.js b/modules/invoiceOut/back/methods/invoiceOut/download.js index 49bba046b..ce80ecffd 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/download.js +++ b/modules/invoiceOut/back/methods/invoiceOut/download.js @@ -42,42 +42,42 @@ module.exports = Self => { if (typeof options == 'object') Object.assign(myOptions, options); + const invoiceOut = await models.InvoiceOut.findById(id, { + fields: ['ref', 'issued'] + }, myOptions); + + const issued = invoiceOut.issued; + const year = issued.getFullYear().toString(); + const month = (issued.getMonth() + 1).toString(); + const day = issued.getDate().toString(); + + const container = await models.InvoiceContainer.container(year); + const rootPath = container.client.root; + const src = path.join(rootPath, year, month, day); + const fileName = `${year}${invoiceOut.ref}.pdf`; + const fileSrc = path.join(src, fileName); + + const file = { + path: fileSrc, + contentType: 'application/pdf', + name: fileName + }; + try { - const invoiceOut = await models.InvoiceOut.findById(id, { - fields: ['ref', 'issued'] - }, myOptions); - - const issued = invoiceOut.issued; - const year = issued.getFullYear().toString(); - const month = (issued.getMonth() + 1).toString(); - const day = issued.getDate().toString(); - - const container = await models.InvoiceContainer.container(year); - const rootPath = container.client.root; - const src = path.join(rootPath, year, month, day); - const fileName = `${year}${invoiceOut.ref}.pdf`; - const fileSrc = path.join(src, fileName); - - const file = { - path: fileSrc, - contentType: 'application/pdf', - name: fileName - }; - - try { - await fs.access(file.path); - } catch (error) { - await Self.createPdf(ctx, id, myOptions); - } - - const stream = fs.createReadStream(file.path); - - return [stream, file.contentType, `filename="${file.name}"`]; + await fs.access(file.path); } catch (error) { - if (error.code === 'ENOENT') - throw new UserError('The PDF document does not exist'); - - throw error; + await Self.createPdf(ctx, id, myOptions); } + + const stream = await fs.createReadStream(file.path); + // XXX: To prevent unhandled ENOENT error + // https://stackoverflow.com/questions/17136536/is-enoent-from-fs-createreadstream-uncatchable + stream.on('error', err => { + const e = new Error(err.message); + err.stack = e.stack; + console.error(err); + }); + + return [stream, file.contentType, `filename="${file.name}"`]; }; }; diff --git a/print/core/email.js b/print/core/email.js index 6e96f5c2e..43e92e619 100644 --- a/print/core/email.js +++ b/print/core/email.js @@ -63,12 +63,12 @@ class Email extends Component { await getAttachments(componentPath, component.attachments); if (component.components) - await getSubcomponentAttachments(component) + await getSubcomponentAttachments(component); } } } - await getSubcomponentAttachments(instance) + await getSubcomponentAttachments(instance); if (this.attachments) await getAttachments(this.path, this.attachments); From 7660d7685061241206e6c5324113fdbdc29b1959 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Fri, 23 Jun 2023 22:52:03 +0200 Subject: [PATCH 07/22] refs #5772 Test environment fix --- .../back/methods/invoiceOut/createPdf.js | 17 +++----- .../back/methods/invoiceOut/download.js | 39 ++++++++----------- .../back/methods/invoiceOut/invoiceClient.js | 1 - .../back/methods/invoiceOut/invoiceEmail.js | 16 +++----- modules/invoiceOut/back/models/invoice-out.js | 30 +++++++++----- 5 files changed, 49 insertions(+), 54 deletions(-) diff --git a/modules/invoiceOut/back/methods/invoiceOut/createPdf.js b/modules/invoiceOut/back/methods/invoiceOut/createPdf.js index 8cc584c94..c4d50a4ee 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/createPdf.js +++ b/modules/invoiceOut/back/methods/invoiceOut/createPdf.js @@ -24,20 +24,15 @@ module.exports = Self => { Self.createPdf = async function(ctx, id, options) { const models = Self.app.models; + options = typeof options == 'object' + ? Object.assign({}, options) : {}; let tx; - const myOptions = {}; - - if (typeof options == 'object') - Object.assign(myOptions, options); - - if (!myOptions.transaction) { - tx = await Self.beginTransaction({}); - myOptions.transaction = tx; - } + if (!options.transaction) + tx = options.transaction = await Self.beginTransaction({}); try { - const invoiceOut = await Self.findById(id, {fields: ['hasPdf']}, myOptions); + const invoiceOut = await Self.findById(id, {fields: ['hasPdf']}, options); if (invoiceOut.hasPdf) { const canCreatePdf = await models.ACL.checkAccessAcl(ctx, 'InvoiceOut', 'canCreatePdf', 'WRITE'); @@ -45,7 +40,7 @@ module.exports = Self => { throw new UserError(`You don't have enough privileges`); } - await Self.makePdf(id, myOptions); + await Self.makePdf(id, options); if (tx) await tx.commit(); } catch (err) { diff --git a/modules/invoiceOut/back/methods/invoiceOut/download.js b/modules/invoiceOut/back/methods/invoiceOut/download.js index ce80ecffd..4c76f7c07 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/download.js +++ b/modules/invoiceOut/back/methods/invoiceOut/download.js @@ -1,6 +1,5 @@ const fs = require('fs-extra'); const path = require('path'); -const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethodCtx('download', { @@ -37,39 +36,27 @@ module.exports = Self => { Self.download = async function(ctx, id, options) { const models = Self.app.models; - const myOptions = {}; + options = typeof options == 'object' + ? Object.assign({}, options) : {}; - if (typeof options == 'object') - Object.assign(myOptions, options); + const pdfFile = await Self.filePath(id, options); - const invoiceOut = await models.InvoiceOut.findById(id, { - fields: ['ref', 'issued'] - }, myOptions); - - const issued = invoiceOut.issued; - const year = issued.getFullYear().toString(); - const month = (issued.getMonth() + 1).toString(); - const day = issued.getDate().toString(); - - const container = await models.InvoiceContainer.container(year); + const container = await models.InvoiceContainer.container(pdfFile.year); const rootPath = container.client.root; - const src = path.join(rootPath, year, month, day); - const fileName = `${year}${invoiceOut.ref}.pdf`; - const fileSrc = path.join(src, fileName); const file = { - path: fileSrc, + path: path.join(rootPath, pdfFile.path, pdfFile.name), contentType: 'application/pdf', - name: fileName + name: pdfFile.name }; try { await fs.access(file.path); } catch (error) { - await Self.createPdf(ctx, id, myOptions); + await Self.createPdf(ctx, id, options); } - const stream = await fs.createReadStream(file.path); + let stream = await fs.createReadStream(file.path); // XXX: To prevent unhandled ENOENT error // https://stackoverflow.com/questions/17136536/is-enoent-from-fs-createreadstream-uncatchable stream.on('error', err => { @@ -78,6 +65,14 @@ module.exports = Self => { console.error(err); }); - return [stream, file.contentType, `filename="${file.name}"`]; + if (process.env.NODE_ENV == 'test') { + try { + await fs.access(file.path); + } catch (error) { + stream = null; + } + } + + return [stream, file.contentType, `filename="${pdfFile.name}"`]; }; }; diff --git a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js index ab076b46f..ddd008942 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js +++ b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js @@ -45,7 +45,6 @@ module.exports = Self => { Self.invoiceClient = async(ctx, options) => { const args = ctx.args; const models = Self.app.models; - options = typeof options == 'object' ? Object.assign({}, options) : {}; options.userId = ctx.req.accessToken.userId; diff --git a/modules/invoiceOut/back/methods/invoiceOut/invoiceEmail.js b/modules/invoiceOut/back/methods/invoiceOut/invoiceEmail.js index 113526484..400328fef 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/invoiceEmail.js +++ b/modules/invoiceOut/back/methods/invoiceOut/invoiceEmail.js @@ -11,20 +11,17 @@ module.exports = Self => { type: 'string', required: true, http: {source: 'path'} - }, - { + }, { arg: 'recipient', type: 'string', description: 'The recipient email', required: true, - }, - { + }, { arg: 'replyTo', type: 'string', description: 'The sender email to reply to', required: false - }, - { + }, { arg: 'recipientId', type: 'number', description: 'The recipient id to send to the recipient preferred language', @@ -43,16 +40,13 @@ module.exports = Self => { Self.invoiceEmail = async(ctx, reference) => { const args = Object.assign({}, ctx.args); - const {InvoiceOut} = Self.app.models; const params = { recipient: args.recipient, lang: ctx.req.getLocale() }; - const invoiceOut = await InvoiceOut.findOne({ - where: { - ref: reference - } + const invoiceOut = await Self.findOne({ + where: {ref: reference} }); delete args.ctx; diff --git a/modules/invoiceOut/back/models/invoice-out.js b/modules/invoiceOut/back/models/invoice-out.js index 9baa1e7e6..d3aaf3b3d 100644 --- a/modules/invoiceOut/back/models/invoice-out.js +++ b/modules/invoiceOut/back/models/invoice-out.js @@ -1,4 +1,5 @@ const print = require('vn-print'); +const path = require('path'); module.exports = Self => { require('../methods/invoiceOut/filter')(Self); @@ -23,20 +24,31 @@ module.exports = Self => { require('../methods/invoiceOut/negativeBases')(Self); require('../methods/invoiceOut/negativeBasesCsv')(Self); - Self.makePdf = async function(id, options) { - const fields = ['id', 'hasPdf', 'ref', 'issued']; + Self.filePath = async function(id, options) { + const fields = ['ref', 'issued']; const invoiceOut = await Self.findById(id, {fields}, options); - const invoiceReport = new print.Report('invoice', { - reference: invoiceOut.ref - }); - const buffer = await invoiceReport.toPdfStream(); const issued = invoiceOut.issued; const year = issued.getFullYear().toString(); const month = (issued.getMonth() + 1).toString(); const day = issued.getDate().toString(); - const fileName = `${year}${invoiceOut.ref}.pdf`; + return { + path: path.join(year, month, day), + name: `${year}${invoiceOut.ref}.pdf`, + year + }; + }; + + Self.makePdf = async function(id, options) { + const fields = ['id', 'hasPdf', 'ref']; + const invoiceOut = await Self.findById(id, {fields}, options); + const invoiceReport = new print.Report('invoice', { + reference: invoiceOut.ref + }); + const buffer = await invoiceReport.toPdfStream(); + + const pdfFile = await Self.filePath(id, options); // Store invoice @@ -47,8 +59,8 @@ module.exports = Self => { if (process.env.NODE_ENV !== 'test') { await print.storage.write(buffer, { type: 'invoice', - path: `${year}/${month}/${day}`, - fileName: fileName + path: pdfFile.path, + fileName: pdfFile.name }); } }; From f70df714df81b75d675f37a4751c5670b101114e Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Sun, 25 Jun 2023 16:31:24 +0200 Subject: [PATCH 08/22] refs #5772, #5900 InvoiceOut back test removed, logs improved --- e2e/helpers/selectors.js | 12 - front/salix/components/log/index.html | 199 +++++++----- front/salix/components/log/index.js | 242 ++++++++++---- front/salix/components/log/locale/es.yml | 1 + front/salix/components/log/style.scss | 296 +++++++++++------- loopback/common/methods/log/pitInstance.js | 56 ++++ loopback/common/models/log.js | 1 + .../methods/invoiceOut/specs/download.spec.js | 28 -- 8 files changed, 545 insertions(+), 290 deletions(-) create mode 100644 loopback/common/methods/log/pitInstance.js delete mode 100644 modules/invoiceOut/back/methods/invoiceOut/specs/download.spec.js diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index b8eaa99a1..d92cb129b 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -479,9 +479,6 @@ export default { fourthBalance: 'vn-item-diary vn-tbody > vn-tr:nth-child(4) > vn-td.balance > span', firstBalance: 'vn-item-diary vn-tbody > vn-tr:nth-child(1) > vn-td.balance' }, - itemLog: { - anyLineCreated: 'vn-item-log > vn-log vn-tbody > vn-tr', - }, ticketSummary: { header: 'vn-ticket-summary > vn-card > h5', state: 'vn-ticket-summary vn-label-value[label="State"] > section > span', @@ -667,15 +664,6 @@ export default { thirdRemoveRequestButton: 'vn-ticket-request-index vn-tr:nth-child(3) vn-icon[icon="delete"]', thirdRequestQuantity: 'vn-ticket-request-index vn-table vn-tr:nth-child(3) > vn-td:nth-child(6) vn-input-number', saveButton: 'vn-ticket-request-create button[type=submit]', - - }, - ticketLog: { - firstTD: 'vn-ticket-log vn-table vn-td:nth-child(1)', - logButton: 'vn-left-menu a[ui-sref="ticket.card.log"]', - user: 'vn-ticket-log vn-tbody vn-tr vn-td:nth-child(2)', - action: 'vn-ticket-log vn-tbody vn-tr vn-td:nth-child(4)', - changes: 'vn-ticket-log vn-data-viewer vn-tbody vn-tr table tr:nth-child(2) td.after', - id: 'vn-ticket-log vn-tr:nth-child(1) table tr:nth-child(1) td.before' }, ticketService: { addServiceButton: 'vn-ticket-service vn-icon-button[vn-tooltip="Add service"] > button', diff --git a/front/salix/components/log/index.html b/front/salix/components/log/index.html index 8b33c811b..ac4e5cb6e 100644 --- a/front/salix/components/log/index.html +++ b/front/salix/components/log/index.html @@ -2,8 +2,6 @@ vn-id="model" url="{{$ctrl.url}}" filter="$ctrl.filter" - link="{originFk: $ctrl.originId}" - where="{changedModel: $ctrl.changedModel, changedModelId: $ctrl.changedModelId}" data="$ctrl.logs" order="creationDate DESC, id DESC" limit="20"> @@ -17,90 +15,107 @@ -
-
- - - - -
+
+
+
+ {{$ctrl.modelI18n}} #{{::originLog.originFk}} +
- -
-
- - {{::log.changedModelI18n}} - -
-
- {{::$ctrl.relativeDate(log.creationDate)}} - - -
+
+
+ + + + +
+
-
- #{{::log.changedModelId}} - - - {{::log.changedModelValue}} -
-
- - - - - - {{::prop.nameI18n}}: +
+
+
+ + + + {{::modelLog.modelI18n}} - , - -
-
- - {{::prop.nameI18n}}: - - - - ← - -
+ #{{::modelLog.id}} + {{::modelLog.showValue}}
- - - {{::log.description}} - - + +
+
+ {{::$ctrl.relativeDate(log.creationDate)}} +
+
+ + + + +
+
+
+ + + + + + {{::prop.nameI18n}}: + + , + +
+
+ + {{::prop.nameI18n}}: + + + + ← + +
+
+
+ + {{::log.description}} + + +
+
+
- + + + + + +
+
+ {{$ctrl.instance.modelLog.modelI18n}} #{{$ctrl.instance.modelLog.id}} +
+
+
+ + {{::prop.nameI18n}}: + + +
+
+ No data +
+
+
+
+
diff --git a/front/salix/components/log/index.js b/front/salix/components/log/index.js index 8aea255a2..ecbeaefe7 100644 --- a/front/salix/components/log/index.js +++ b/front/salix/components/log/index.js @@ -3,7 +3,10 @@ import Section from '../section'; import {hashToColor} from 'core/lib/string'; import './style.scss'; -const validDate = /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$/; +const validDate = new RegExp( + /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])/.source + + /T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$/.source +); export default class Controller extends Section { constructor($element, $) { @@ -28,6 +31,20 @@ export default class Controller extends Section { select: 'visibility' }; this.filter = { + fields: [ + 'id', + 'originFk', + 'userFk', + 'action', + 'changedModel', + 'oldInstance', + 'newInstance', + 'creationDate', + 'changedModel', + 'changedModelId', + 'changedModelValue', + 'description' + ], include: [{ relation: 'user', scope: { @@ -48,6 +65,12 @@ export default class Controller extends Section { this.today.setHours(0, 0, 0, 0); } + $onInit() { + const match = this.url?.match(/(.*)Logs$/); + this.model = match && match[1]; + this.modelI18n = this.translateModel(this.model); + } + $postLink() { this.resetFilter(); this.$.$watch( @@ -63,47 +86,80 @@ export default class Controller extends Section { set logs(value) { this._logs = value; + this.logTree = []; if (!value) return; const empty = {}; const validations = window.validations; - const castJsonValue = this.castJsonValue; - for (const log of value) { + let originLog; + let userLog; + let modelLog; + let nLogs; + + for (let i = 0; i < value.length; i++) { + const log = value[i]; + const prevLog = i > 0 ? value[i - 1] : null; + const locale = validations[log.changedModel]?.locale || empty; + + // Origin + + const originChanged = !prevLog + || log.originFk != prevLog.originFk; + if (originChanged) { + this.logTree.push(originLog = { + originFk: log.originFk, + logs: [] + }); + } + + // User + + const day = 86400 * 1000; + const time = new Date(log.creationDate).getTime(); + const prevTime = prevLog && new Date(prevLog.creationDate).getTime(); + const userChanged = !prevLog + || log.userFk != prevLog.userFk + || time < prevTime - day * 2 + || nLogs >= 10; + + if (userChanged) { + originLog.logs.push(userLog = { + user: log.user, + userFk: log.userFk, + logs: [] + }); + nLogs = 0; + } + nLogs++; + + // Model + + const modelChanged = userChanged + || log.changedModel != prevLog.changedModel + || log.changedModelId != prevLog.changedModelId; + if (modelChanged) { + userLog.logs.push(modelLog = { + model: log.changedModel, + modelI18n: firstUpper(locale.name) || log.changedModel, + id: log.changedModelId, + showValue: log.changedModelValue, + logs: [] + }); + } + + modelLog.logs.push(log); + + // Changes + const notDelete = log.action != 'delete'; const olds = (notDelete ? log.oldInstance : null) || empty; const vals = (notDelete ? log.newInstance : log.oldInstance) || empty; - const locale = validations[log.changedModel]?.locale || empty; - log.changedModelI18n = firstUpper(locale.name) || log.changedModel; - let props = Object.keys(olds).concat(Object.keys(vals)); - props = [...new Set(props)]; + let propNames = Object.keys(olds).concat(Object.keys(vals)); + propNames = [...new Set(propNames)]; - log.props = []; - for (const prop of props) { - if (prop.endsWith('$')) continue; - log.props.push({ - name: prop, - nameI18n: firstUpper(locale.columns?.[prop]) || prop, - old: getVal(olds, prop), - val: getVal(vals, prop) - }); - } - log.props.sort( - (a, b) => a.nameI18n.localeCompare(b.nameI18n)); - } - - function getVal(vals, prop) { - let val, id; - const showProp = `${prop}$`; - - if (vals[showProp] != null) { - val = vals[showProp]; - id = vals[prop]; - } else - val = vals[prop]; - - return {val: castJsonValue(val), id}; + log.props = this.parseProps(propNames, locale, vals, olds); } } @@ -114,17 +170,76 @@ export default class Controller extends Section { set models(value) { this._models = value; if (!value) return; - for (const model of value) { - const name = model.changedModel; - model.changedModelI18n = - firstUpper(window.validations[name]?.locale?.name) || name; - } + for (const model of value) + model.changedModelI18n = this.translateModel(model.changedModel); } get showModelName() { return !(this.changedModel && this.changedModelId); } + parseProps(propNames, locale, vals, olds) { + const castJsonValue = this.castJsonValue; + const props = []; + + for (const prop of propNames) { + if (prop.endsWith('$')) continue; + props.push({ + name: prop, + nameI18n: firstUpper(locale.columns?.[prop]) || prop, + old: getVal(olds, prop), + val: getVal(vals, prop) + }); + } + props.sort( + (a, b) => a.nameI18n.localeCompare(b.nameI18n)); + + function getVal(vals, prop) { + let val; let id; + const showProp = `${prop}$`; + + if (vals[showProp] != null) { + val = vals[showProp]; + id = vals[prop]; + } else + val = vals[prop]; + + return {val: castJsonValue(val), id}; + } + + return props; + } + + viewPitInstance(event, id, modelLog) { + if (this.instance?.canceler) + this.instance.canceler.resolve(); + + const canceler = this.$q.defer(); + this.instance = { + modelLog, + canceler + }; + const options = {timeout: canceler.promise}; + + this.$http.get(`${this.url}/${id}/pitInstance`, options) + .then(res => { + const instance = res.data; + const propNames = Object.keys(instance); + const locale = window.validations[modelLog.model]?.locale || {}; + this.instance.props = this.parseProps(propNames, locale, instance, {}); + }) + .finally(() => { + this.instance.canceler = null; + this.$.$applyAsync(() => this.$.instancePopover.relocate()); + }); + + this.$.instancePopover.show(event); + } + + translateModel(name) { + return firstUpper(window.validations[name]?.locale?.name) || name; + } + castJsonValue(value) { return typeof value === 'string' && validDate.test(value) ? new Date(value) @@ -160,12 +275,11 @@ export default class Controller extends Section { applyFilter() { const filter = this.$.filter; - function getParam(prop, value) { + const getParam = (prop, value) => { if (value == null || value == '') return null; switch (prop) { case 'search': - const or = []; - if (/^\s*[0-9]+\s*$/.test(value)) + if (/^\s*[0-9]+\s*$/.test(value) || this.byEntity) return {changedModelId: value.trim()}; else return {changedModelValue: {like: `%${value}%`}}; @@ -177,72 +291,86 @@ export default class Controller extends Section { ]}; case 'who': switch (value) { - case 'all': - return null; case 'user': return {userFk: {neq: null}}; case 'system': return {userFk: null}; + case 'all': + default: + return null; } - case 'actions': + case 'actions': { const inq = []; for (const action in value) { if (value[action]) inq.push(action); } return inq.length ? {action: {inq}} : null; + } case 'from': - if (filter.to) { + if (filter.to) return {creationDate: {gte: value}}; - } else { + else { const to = new Date(value); to.setHours(23, 59, 59, 999); return {creationDate: {between: [value, to]}}; } - case 'to': + case 'to': { const to = new Date(value); to.setHours(23, 59, 59, 999); return {creationDate: {lte: to}}; + } case 'userFk': return filter.who != 'system' ? {[prop]: value} : null; default: return {[prop]: value}; } - } + }; + this.hasFilter = false; const and = []; + + if (!filter.search || !filter.changedModel) + this.byEntity = false; + if (!this.byEntity) + and.push({originFk: this.originId}); + for (const prop in filter) { const param = getParam(prop, filter[prop]); - if (param) and.push(param); + if (param) { + and.push(param); + this.hasFilter = true; + } } const lbFilter = and.length ? {where: {and}} : null; return this.$.model.applyFilter(lbFilter); } - filterByEntity(log) { + filterByEntity(modelLog) { + this.byEntity = true; this.$.filter = { who: 'all', - search: log.changedModelId, - changedModel: log.changedModel + search: modelLog.id, + changedModel: modelLog.model }; } searchUser(search) { - if (/^[0-9]+$/.test(search)) { + if (/^[0-9]+$/.test(search)) return {id: search}; - } else { + else { return {or: [ {name: search}, {nickname: {like: `%${search}%`}} - ]} + ]}; } } - showWorkerDescriptor(event, log) { - if (log.user?.worker) - this.$.workerDescriptor.show(event.target, log.userFk); + showWorkerDescriptor(event, userLog) { + if (userLog.user?.worker) + this.$.workerDescriptor.show(event.target, userLog.userFk); } } diff --git a/front/salix/components/log/locale/es.yml b/front/salix/components/log/locale/es.yml index 90a42911b..09154f04e 100644 --- a/front/salix/components/log/locale/es.yml +++ b/front/salix/components/log/locale/es.yml @@ -24,4 +24,5 @@ Changes: Cambios today: hoy yesterday: ayer Show all record changes: Mostrar todos los cambios realizados en el registro +View record at this point in time: Ver el registro en este punto Quit filter: Quitar filtro diff --git a/front/salix/components/log/style.scss b/front/salix/components/log/style.scss index de294972d..8c39559fc 100644 --- a/front/salix/components/log/style.scss +++ b/front/salix/components/log/style.scss @@ -1,13 +1,49 @@ @import "util"; vn-log { - .change { + .origin-log { + & > .origin-info { + display: flex; + text-align: right; + justify-content: space-between; + align-items: center; + column-gap: 4px; + padding-left: 48px; + margin-bottom: 10px; + + & > .line { + flex-grow: 1; + background-color: $color-font-secondary; + height: 2px; + } + & > .origin-id { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + font-size: 1.1rem; + color: $color-font-secondary; + } + } + } + .user-log { display: flex; - & > .left { + & > .timeline { position: relative; padding-right: 10px; + width: 38px; + min-width: 38px; + flex-grow: auto; + & > .arrow { + height: 8px; + width: 8px; + position: absolute; + transform: rotateY(0deg) rotate(45deg); + top: 14px; + right: -4px; + z-index: 1; + } & > vn-avatar { cursor: pointer; @@ -15,15 +51,6 @@ vn-log { background-color: $color-main !important; } } - & > .arrow { - height: 8px; - width: 8px; - position: absolute; - transform: rotateY(0deg) rotate(45deg); - top: 18px; - right: -4px; - z-index: 1; - } & > .line { position: absolute; background-color: $color-main; @@ -34,134 +61,173 @@ vn-log { bottom: -8px; } } - &:last-child > .left > .line { + &:last-child > .timeline > .line { display: none; } - .detail { - position: relative; + & > .user-changes { flex-grow: 1; - width: 100%; - border-radius: 2px; overflow: hidden; + } + } + .model-log { + & > .model-info { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + margin-top: 6px; - & > .header { - display: flex; - justify-content: space-between; - align-items: center; - overflow: hidden; + & > .model-name { + display: inline-block; + padding: 2px 5px; + color: $color-font-dark; + border-radius: 8px; + vertical-align: middle; + } + & > .model-value { + font-style: italic; + } + & > .model-id { + color: $color-font-secondary; + font-size: .9rem; + } + & > vn-icon[icon="filter_alt"] { + @extend %clickable-light; + vertical-align: middle; + font-size: 18px; + color: $color-font-secondary; + float: right; + display: none; - & > .action-model { - display: inline-flex; - overflow: hidden; - - & > .model-name { - display: inline-block; - padding: 2px 5px; - color: $color-font-dark; - border-radius: 8px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - } - & > .action-date { - white-space: nowrap; - - & > .action { - display: inline-flex; - align-items: center; - justify-content: center; - color: $color-font-bg; - vertical-align: middle; - border-radius: 50%; - width: 24px; - height: 24px; - font-size: 18px; - - &.notice { - background-color: $color-notice-medium - } - &.success { - background-color: $color-success-medium; - } - &.warning { - background-color: $color-main-medium; - } - &.alert { - background-color: lighten($color-alert, 5%); - } - } + @include mobile { + display: initial; } } - & > .model { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - max-height: 18px; + } + &:hover > .model-info > vn-icon[icon="filter_alt"] { + display: initial; + } + } + .changes-log { + position: relative; + max-width: 100%; + width: 100%; + border-radius: 2px; + overflow: hidden; - & > vn-icon { + & > .change-info { + display: flex; + justify-content: space-between; + align-items: center; + overflow: hidden; + + & > .date { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + & > div { + white-space: nowrap; + + & > vn-icon.pit { @extend %clickable-light; vertical-align: middle; - padding: 2px; - margin: -2px; font-size: 18px; color: $color-font-secondary; - float: right; display: none; @include mobile { - display: initial; + display: inline-block; } } - & > .model-value { - font-style: italic; - } - & > .model-id { - color: $color-font-secondary; - font-size: .9rem; - } - } - &:hover > .model > vn-icon { - display: initial; - } - } - } - .changes { - overflow: hidden; - background-color: rgba(255, 255, 255, .05); - color: $color-font-light; - position: relative; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - min-height: 34px; - box-sizing: border-box; + & > .action { + display: inline-flex; + align-items: center; + justify-content: center; + color: $color-font-bg; + vertical-align: middle; + border-radius: 50%; + width: 24px; + height: 24px; + font-size: 18px; - & > vn-icon { - @extend %clickable; - float: right; - position: relative; - transition-property: transform, background-color; - transition-duration: 150ms; - margin: -5px; - margin-left: 4px; - padding: 1px; - border-radius: 50%; + &.notice { + background-color: $color-notice-medium + } + &.success { + background-color: $color-success-medium; + } + &.warning { + background-color: $color-main-medium; + } + &.alert { + background-color: lighten($color-alert, 5%); + } + } + } + &:hover vn-icon.pit { + display: inline-block; + } } - &.expanded { - text-overflow: initial; - white-space: initial; + & > .change-detail { + overflow: hidden; + background-color: rgba(255, 255, 255, .05); + color: $color-font-light; + position: relative; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + min-height: 34px; + box-sizing: border-box; & > vn-icon { - transform: rotate(180deg); + @extend %clickable; + float: right; + position: relative; + transition-property: transform, background-color; + transition-duration: 150ms; + margin: -5px; + margin-left: 4px; + padding: 1px; + border-radius: 50%; + } + &.expanded { + text-overflow: initial; + white-space: initial; + + & > vn-icon { + transform: rotate(180deg); + } + } + & > .no-changes { + font-style: italic; } } - & > .no-changes { - font-style: italic; + } + .id-value { + font-size: .9rem; + color: $color-font-secondary; + } +} +.vn-log-instance { + display: block; + + & > .loading { + display: flex; + justify-content: center; + } + & > .instance { + min-width: 180px; + max-width: 400px; + + & > .header { + background-color: $color-main; + color: $color-font-dark; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + & > .change-detail { + color: $color-font-light; } } } -vn-log-value > .id-value { - font-size: .9rem; - color: $color-font-secondary; -} diff --git a/loopback/common/methods/log/pitInstance.js b/loopback/common/methods/log/pitInstance.js new file mode 100644 index 000000000..3cbe013b3 --- /dev/null +++ b/loopback/common/methods/log/pitInstance.js @@ -0,0 +1,56 @@ +module.exports = Self => { + Self.remoteMethod('pitInstance', { + description: 'Gets the status of instance at specific point in time', + accepts: [ + { + arg: 'id', + type: 'integer', + description: 'The log id', + required: true + } + ], + returns: { + type: [Self], + root: true + }, + http: { + path: `/:id/pitInstance`, + verb: 'GET' + } + }); + + Self.pitInstance = async function(id) { + const log = await Self.findById(id, { + fields: ['changedModel', 'changedModelId'] + }); + + const where = { + changedModel: log.changedModel, + changedModelId: log.changedModelId + }; + + const createdWhere = {action: 'insert'}; + const createdLog = await Self.findOne({ + fields: ['id'], + where: Object.assign(createdWhere, where) + }); + + const logsWhere = { + id: {between: [ + Math.min(id, createdLog.id), + Math.max(id, createdLog.id) + ]} + }; + const logs = await Self.find({ + fields: ['newInstance'], + where: Object.assign(logsWhere, where), + order: 'creationDate' + }); + + const instance = {}; + for (const log of logs) + Object.assign(instance, log.newInstance); + + return instance; + }; +}; diff --git a/loopback/common/models/log.js b/loopback/common/models/log.js index 0622431a6..b0d3cfc00 100644 --- a/loopback/common/models/log.js +++ b/loopback/common/models/log.js @@ -5,6 +5,7 @@ module.exports = function(Self) { Self.super_.setup.call(this); require('../methods/log/editors')(this); require('../methods/log/models')(this); + require('../methods/log/pitInstance')(this); } }); }; diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/download.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/download.spec.js deleted file mode 100644 index 4029bfbcf..000000000 --- a/modules/invoiceOut/back/methods/invoiceOut/specs/download.spec.js +++ /dev/null @@ -1,28 +0,0 @@ -const models = require('vn-loopback/server/server').models; -const fs = require('fs-extra'); - -describe('InvoiceOut download()', () => { - const userId = 9; - const invoiceId = 1; - const ctx = { - req: { - - accessToken: {userId: userId}, - headers: {origin: 'http://localhost:5000'}, - } - }; - - it('should return the downloaded file name', async() => { - spyOn(models.InvoiceContainer, 'container').and.returnValue({ - client: {root: '/path'} - }); - spyOn(fs, 'createReadStream').and.returnValue(new Promise(resolve => resolve('streamObject'))); - spyOn(fs, 'access').and.returnValue(true); - spyOn(models.InvoiceOut, 'createPdf').and.returnValue(new Promise(resolve => resolve(true))); - - const result = await models.InvoiceOut.download(ctx, invoiceId); - - expect(result[1]).toEqual('application/pdf'); - expect(result[2]).toMatch(/filename="\d{4}T1111111.pdf"/); - }); -}); From 98301f8e709158e8431d93e583b7bcb41ee6d641 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Sun, 25 Jun 2023 17:06:38 +0200 Subject: [PATCH 09/22] refs #5900 Small fixes & code clean --- .../02-client/07_edit_web_access.spec.js | 4 +-- front/salix/components/log/index.html | 8 +++--- front/salix/components/log/index.js | 13 ++++----- front/salix/components/log/style.scss | 5 ++-- loopback/common/methods/log/pitInstance.js | 28 ++++++++++++++++--- 5 files changed, 39 insertions(+), 19 deletions(-) diff --git a/e2e/paths/02-client/07_edit_web_access.spec.js b/e2e/paths/02-client/07_edit_web_access.spec.js index 5386d12bd..3847ec7cd 100644 --- a/e2e/paths/02-client/07_edit_web_access.spec.js +++ b/e2e/paths/02-client/07_edit_web_access.spec.js @@ -5,8 +5,8 @@ const $ = { userName: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.name"]', email: 'vn-client-web-access vn-textfield[ng-model="$ctrl.account.email"]', saveButton: 'vn-client-web-access button[type=submit]', - nameValue: 'vn-client-log .change:nth-child(1) .basic-json:nth-child(2) vn-json-value', - activeValue: 'vn-client-log .change:nth-child(2) .basic-json:nth-child(1) vn-json-value' + nameValue: 'vn-client-log .changes-log:nth-child(2) .basic-json:nth-child(2) vn-json-value', + activeValue: 'vn-client-log .changes-log:nth-child(3) .basic-json:nth-child(1) vn-json-value' }; describe('Client web access path', () => { diff --git a/front/salix/components/log/index.html b/front/salix/components/log/index.html index ac4e5cb6e..e0465b840 100644 --- a/front/salix/components/log/index.html +++ b/front/salix/components/log/index.html @@ -33,16 +33,16 @@ ng-src="/api/Images/user/160x160/{{::userLog.userFk}}/download?access_token={{::$ctrl.vnToken.token}}"> -
+
-
+
+ ng-click="$ctrl.filterByRecord(modelLog)"> diff --git a/front/salix/components/log/index.js b/front/salix/components/log/index.js index ecbeaefe7..822a31801 100644 --- a/front/salix/components/log/index.js +++ b/front/salix/components/log/index.js @@ -67,8 +67,7 @@ export default class Controller extends Section { $onInit() { const match = this.url?.match(/(.*)Logs$/); - this.model = match && match[1]; - this.modelI18n = this.translateModel(this.model); + this.modelI18n = this.translateModel(match && match[1]); } $postLink() { @@ -279,7 +278,7 @@ export default class Controller extends Section { if (value == null || value == '') return null; switch (prop) { case 'search': - if (/^\s*[0-9]+\s*$/.test(value) || this.byEntity) + if (/^\s*[0-9]+\s*$/.test(value) || this.byRecord) return {changedModelId: value.trim()}; else return {changedModelValue: {like: `%${value}%`}}; @@ -332,8 +331,8 @@ export default class Controller extends Section { const and = []; if (!filter.search || !filter.changedModel) - this.byEntity = false; - if (!this.byEntity) + this.byRecord = false; + if (!this.byRecord) and.push({originFk: this.originId}); for (const prop in filter) { @@ -348,8 +347,8 @@ export default class Controller extends Section { return this.$.model.applyFilter(lbFilter); } - filterByEntity(modelLog) { - this.byEntity = true; + filterByRecord(modelLog) { + this.byRecord = true; this.$.filter = { who: 'all', search: modelLog.id, diff --git a/front/salix/components/log/style.scss b/front/salix/components/log/style.scss index 8c39559fc..f11fc2f25 100644 --- a/front/salix/components/log/style.scss +++ b/front/salix/components/log/style.scss @@ -74,7 +74,8 @@ vn-log { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; - margin-top: 6px; + margin-top: 5px; + min-height: 22px; & > .model-name { display: inline-block; @@ -131,7 +132,7 @@ vn-log { & > vn-icon.pit { @extend %clickable-light; vertical-align: middle; - font-size: 18px; + font-size: 20px; color: $color-font-secondary; display: none; diff --git a/loopback/common/methods/log/pitInstance.js b/loopback/common/methods/log/pitInstance.js index 3cbe013b3..1c054c53e 100644 --- a/loopback/common/methods/log/pitInstance.js +++ b/loopback/common/methods/log/pitInstance.js @@ -1,3 +1,5 @@ +const NotFoundError = require('vn-loopback/util/not-found-error'); + module.exports = Self => { Self.remoteMethod('pitInstance', { description: 'Gets the status of instance at specific point in time', @@ -21,24 +23,40 @@ module.exports = Self => { Self.pitInstance = async function(id) { const log = await Self.findById(id, { - fields: ['changedModel', 'changedModelId'] + fields: [ + 'changedModel', + 'changedModelId', + 'creationDate' + ] }); + if (!log) + throw new NotFoundError(); const where = { changedModel: log.changedModel, changedModelId: log.changedModelId }; - const createdWhere = {action: 'insert'}; + const createdWhere = { + action: 'insert', + creationDate: {lte: log.creationDate} + }; const createdLog = await Self.findOne({ - fields: ['id'], - where: Object.assign(createdWhere, where) + fields: ['id', 'creationDate'], + where: Object.assign(createdWhere, where), + order: 'creationDate DESC' }); + if (!createdLog) + throw new NotFoundError('Cannot find creation log'); const logsWhere = { id: {between: [ Math.min(id, createdLog.id), Math.max(id, createdLog.id) + ]}, + creationDate: {between: [ + createdLog.creationDate, + log.creationDate ]} }; const logs = await Self.find({ @@ -46,6 +64,8 @@ module.exports = Self => { where: Object.assign(logsWhere, where), order: 'creationDate' }); + if (!logs.length) + throw new NotFoundError('No logs found for record'); const instance = {}; for (const log of logs) From 21988fba8c2939da1e0e2eb0d3e6497931654c59 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Sun, 25 Jun 2023 17:11:01 +0200 Subject: [PATCH 10/22] refs #5900 Hide PIT button on insert actions --- front/salix/components/log/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/front/salix/components/log/index.html b/front/salix/components/log/index.html index e0465b840..2a6bd9abe 100644 --- a/front/salix/components/log/index.html +++ b/front/salix/components/log/index.html @@ -65,6 +65,7 @@ class="pit vn-ml-xs" icon="preview" translate-attr="::{title: 'View record at this point in time'}" + ng-show="::log.action != 'insert'" ng-click="$ctrl.viewPitInstance($event, log.id, modelLog)"> Date: Sun, 25 Jun 2023 17:14:41 +0200 Subject: [PATCH 11/22] refs #5900 Optimization: remove unnecessary watcher --- front/salix/components/log/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/salix/components/log/index.html b/front/salix/components/log/index.html index 2a6bd9abe..892268a64 100644 --- a/front/salix/components/log/index.html +++ b/front/salix/components/log/index.html @@ -19,7 +19,7 @@
- {{$ctrl.modelI18n}} #{{::originLog.originFk}} + {{::$ctrl.modelI18n}} #{{::originLog.originFk}}
From 57277ceb2e2b406a8d4b0ec212c11b6dba886a42 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Sun, 25 Jun 2023 17:17:04 +0200 Subject: [PATCH 12/22] refs #5900 Show user every 6 changes, model info margin fix --- front/salix/components/log/index.html | 2 +- front/salix/components/log/index.js | 2 +- front/salix/components/log/style.scss | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/front/salix/components/log/index.html b/front/salix/components/log/index.html index 892268a64..7c7c5b068 100644 --- a/front/salix/components/log/index.html +++ b/front/salix/components/log/index.html @@ -38,7 +38,7 @@
-
+
= 10; + || nLogs >= 6; if (userChanged) { originLog.logs.push(userLog = { diff --git a/front/salix/components/log/style.scss b/front/salix/components/log/style.scss index f11fc2f25..481a7fbb3 100644 --- a/front/salix/components/log/style.scss +++ b/front/salix/components/log/style.scss @@ -74,7 +74,6 @@ vn-log { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; - margin-top: 5px; min-height: 22px; & > .model-name { From d31d1e5a133f5a978253692d1da651ebe70f6170 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Sun, 25 Jun 2023 17:25:11 +0200 Subject: [PATCH 13/22] refs #5900 User avatar margin fix --- front/salix/components/log/style.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/front/salix/components/log/style.scss b/front/salix/components/log/style.scss index 481a7fbb3..53d13b7f9 100644 --- a/front/salix/components/log/style.scss +++ b/front/salix/components/log/style.scss @@ -46,6 +46,7 @@ vn-log { } & > vn-avatar { cursor: pointer; + margin-top: 4px; &.system { background-color: $color-main !important; From fd69d69ff0bb2a76cc0f02c9511916223305e57f Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Sun, 25 Jun 2023 17:40:09 +0200 Subject: [PATCH 14/22] refs #5900 Style: margin adjustments --- front/core/components/avatar/style.scss | 4 ++-- front/salix/components/log/style.scss | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/front/core/components/avatar/style.scss b/front/core/components/avatar/style.scss index 272930821..b3a80db55 100644 --- a/front/core/components/avatar/style.scss +++ b/front/core/components/avatar/style.scss @@ -4,8 +4,8 @@ vn-avatar { display: block; border-radius: 50%; overflow: hidden; - height: 36px; - width: 36px; + height: 38px; + width: 38px; font-size: 22px; background-color: $color-main; position: relative; diff --git a/front/salix/components/log/style.scss b/front/salix/components/log/style.scss index 53d13b7f9..050e3f9d9 100644 --- a/front/salix/components/log/style.scss +++ b/front/salix/components/log/style.scss @@ -40,13 +40,12 @@ vn-log { width: 8px; position: absolute; transform: rotateY(0deg) rotate(45deg); - top: 14px; + top: 15px; right: -4px; z-index: 1; } & > vn-avatar { cursor: pointer; - margin-top: 4px; &.system { background-color: $color-main !important; @@ -56,10 +55,10 @@ vn-log { position: absolute; background-color: $color-main; width: 2px; - left: 17px; + left: 18px; z-index: -1; top: 44px; - bottom: -8px; + bottom: -2px; } } &:last-child > .timeline > .line { @@ -115,6 +114,9 @@ vn-log { border-radius: 2px; overflow: hidden; + &:last-child { + margin-bottom: 0; + } & > .change-info { display: flex; justify-content: space-between; From 631c0e312139bdecf1ca44ba21e0ada85f0cb808 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Sun, 25 Jun 2023 18:09:28 +0200 Subject: [PATCH 15/22] refs #5900 Log grouping fix --- front/salix/components/log/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/salix/components/log/index.js b/front/salix/components/log/index.js index b144b6446..ad5a3b355 100644 --- a/front/salix/components/log/index.js +++ b/front/salix/components/log/index.js @@ -117,7 +117,7 @@ export default class Controller extends Section { const day = 86400 * 1000; const time = new Date(log.creationDate).getTime(); const prevTime = prevLog && new Date(prevLog.creationDate).getTime(); - const userChanged = !prevLog + const userChanged = originChanged || log.userFk != prevLog.userFk || time < prevTime - day * 2 || nLogs >= 6; From c15d9a4753e271fe54b528e4b470425464e99b0d Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Sun, 25 Jun 2023 18:49:38 +0200 Subject: [PATCH 16/22] refs #5900 Log order fix --- front/salix/components/log/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/salix/components/log/index.html b/front/salix/components/log/index.html index 7c7c5b068..5c10aa205 100644 --- a/front/salix/components/log/index.html +++ b/front/salix/components/log/index.html @@ -3,7 +3,7 @@ url="{{$ctrl.url}}" filter="$ctrl.filter" data="$ctrl.logs" - order="creationDate DESC, id DESC" + order="creationDate DESC, originFk DESC, id DESC" limit="20"> Date: Sun, 25 Jun 2023 19:01:46 +0200 Subject: [PATCH 17/22] refs #5900 Removed code to split user changes based on date --- front/salix/components/log/index.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/front/salix/components/log/index.js b/front/salix/components/log/index.js index ad5a3b355..b5dac196e 100644 --- a/front/salix/components/log/index.js +++ b/front/salix/components/log/index.js @@ -114,14 +114,9 @@ export default class Controller extends Section { // User - const day = 86400 * 1000; - const time = new Date(log.creationDate).getTime(); - const prevTime = prevLog && new Date(prevLog.creationDate).getTime(); const userChanged = originChanged || log.userFk != prevLog.userFk - || time < prevTime - day * 2 || nLogs >= 6; - if (userChanged) { originLog.logs.push(userLog = { user: log.user, From d69cbe0ad4c3dd5451c7fafb9fc82722ed1e054d Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Sun, 25 Jun 2023 19:39:27 +0200 Subject: [PATCH 18/22] refs #5900 Origin separator style fix --- front/salix/components/log/index.html | 9 +++------ front/salix/components/log/style.scss | 27 +++++++-------------------- 2 files changed, 10 insertions(+), 26 deletions(-) diff --git a/front/salix/components/log/index.html b/front/salix/components/log/index.html index 5c10aa205..68c828772 100644 --- a/front/salix/components/log/index.html +++ b/front/salix/components/log/index.html @@ -16,12 +16,9 @@ model="model" class="vn-w-sm vn-px-sm vn-pb-xl">
-
-
-
- {{::$ctrl.modelI18n}} #{{::originLog.originFk}} -
-
+
+ {{::$ctrl.modelI18n}} #{{::originLog.originFk}} +
.origin-info { + margin-top: 0; + } & > .origin-info { - display: flex; - text-align: right; - justify-content: space-between; - align-items: center; - column-gap: 4px; - padding-left: 48px; - margin-bottom: 10px; - - & > .line { - flex-grow: 1; - background-color: $color-font-secondary; - height: 2px; - } - & > .origin-id { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - font-size: 1.1rem; - color: $color-font-secondary; - } + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + color: $color-font-secondary; } } .user-log { From 544445c4ae6f6c976db61e09702d6f0c75440d23 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Sun, 25 Jun 2023 19:52:14 +0200 Subject: [PATCH 19/22] refs #5800 Origin separator style more clear --- front/salix/components/log/index.html | 9 ++++++--- front/salix/components/log/style.scss | 21 +++++++++++++++++---- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/front/salix/components/log/index.html b/front/salix/components/log/index.html index 68c828772..084bf9834 100644 --- a/front/salix/components/log/index.html +++ b/front/salix/components/log/index.html @@ -16,9 +16,12 @@ model="model" class="vn-w-sm vn-px-sm vn-pb-xl">
-
- {{::$ctrl.modelI18n}} #{{::originLog.originFk}} -
+
+
+ {{::$ctrl.modelI18n}} #{{::originLog.originFk}} +
+
+
.origin-info { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - color: $color-font-secondary; + display: flex; + align-items: center; + margin-top: 28px; + gap: 6px; + + & > .origin-id { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + color: $color-font-secondary; + margin: 0; + } + & > .line { + flex-grow: 1; + background-color: $color-font-secondary; + height: 2px; + } } } .user-log { From c42b38c5ccdd296e8130db159d849891afdb05a1 Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Mon, 26 Jun 2023 10:23:34 +0200 Subject: [PATCH 20/22] refs #5900 Code clean, refactor & accurated --- front/salix/components/log/index.html | 12 +++--- front/salix/components/log/index.js | 10 ++--- front/salix/components/log/style.scss | 1 + loopback/common/methods/log/pitInstance.js | 47 ++++++++++++++-------- 4 files changed, 43 insertions(+), 27 deletions(-) diff --git a/front/salix/components/log/index.html b/front/salix/components/log/index.html index 084bf9834..16342783d 100644 --- a/front/salix/components/log/index.html +++ b/front/salix/components/log/index.html @@ -3,7 +3,7 @@ url="{{$ctrl.url}}" filter="$ctrl.filter" data="$ctrl.logs" - order="creationDate DESC, originFk DESC, id DESC" + order="creationDate DESC, id DESC" limit="20"> - -
-
+
{{$ctrl.instance.modelLog.modelI18n}} #{{$ctrl.instance.modelLog.id}} -
+
@@ -258,3 +255,6 @@
+ + diff --git a/front/salix/components/log/index.js b/front/salix/components/log/index.js index b5dac196e..3df367cae 100644 --- a/front/salix/components/log/index.js +++ b/front/salix/components/log/index.js @@ -67,7 +67,7 @@ export default class Controller extends Section { $onInit() { const match = this.url?.match(/(.*)Logs$/); - this.modelI18n = this.translateModel(match && match[1]); + this.modelI18n = match && this.translateModel(match[1]); } $postLink() { @@ -116,7 +116,7 @@ export default class Controller extends Section { const userChanged = originChanged || log.userFk != prevLog.userFk - || nLogs >= 6; + || nLogs >= 5; if (userChanged) { originLog.logs.push(userLog = { user: log.user, @@ -181,8 +181,8 @@ export default class Controller extends Section { props.push({ name: prop, nameI18n: firstUpper(locale.columns?.[prop]) || prop, - old: getVal(olds, prop), - val: getVal(vals, prop) + val: getVal(vals, prop), + old: olds && getVal(olds, prop) }); } props.sort( @@ -220,7 +220,7 @@ export default class Controller extends Section { const instance = res.data; const propNames = Object.keys(instance); const locale = window.validations[modelLog.model]?.locale || {}; - this.instance.props = this.parseProps(propNames, locale, instance, {}); + this.instance.props = this.parseProps(propNames, locale, instance); }) .finally(() => { this.instance.canceler = null; diff --git a/front/salix/components/log/style.scss b/front/salix/components/log/style.scss index 3ecbbb609..d729d09a3 100644 --- a/front/salix/components/log/style.scss +++ b/front/salix/components/log/style.scss @@ -228,6 +228,7 @@ vn-log { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; + margin: 0; } & > .change-detail { color: $color-font-light; diff --git a/loopback/common/methods/log/pitInstance.js b/loopback/common/methods/log/pitInstance.js index 1c054c53e..cb671cb7e 100644 --- a/loopback/common/methods/log/pitInstance.js +++ b/loopback/common/methods/log/pitInstance.js @@ -37,37 +37,52 @@ module.exports = Self => { changedModelId: log.changedModelId }; + // Fetch creation and all update logs for record up to requested log + const createdWhere = { action: 'insert', creationDate: {lte: log.creationDate} }; const createdLog = await Self.findOne({ - fields: ['id', 'creationDate'], + fields: ['id', 'creationDate', 'newInstance'], where: Object.assign(createdWhere, where), - order: 'creationDate DESC' + order: 'creationDate DESC, id DESC' }); - if (!createdLog) - throw new NotFoundError('Cannot find creation log'); - const logsWhere = { - id: {between: [ - Math.min(id, createdLog.id), - Math.max(id, createdLog.id) - ]}, - creationDate: {between: [ - createdLog.creationDate, - log.creationDate - ]} + const instance = {}; + + let logsWhere = { + action: 'update' }; + if (createdLog) { + Object.assign(instance, createdLog.newInstance); + Object.assign(logsWhere, { + creationDate: {between: [ + createdLog.creationDate, + log.creationDate + ]}, + id: {between: [ + Math.min(id, createdLog.id), + Math.max(id, createdLog.id) + ]} + }); + } else { + Object.assign(logsWhere, { + creationDate: {lte: log.creationDate}, + id: {lte: id} + }); + } + const logs = await Self.find({ fields: ['newInstance'], where: Object.assign(logsWhere, where), - order: 'creationDate' + order: 'creationDate, id' }); - if (!logs.length) + if (!logs.length && !createdLog) throw new NotFoundError('No logs found for record'); - const instance = {}; + // Merge all logs in order into one instance + for (const log of logs) Object.assign(instance, log.newInstance); From e980717d149466c5b2c87a7ff4b25f331e02e19a Mon Sep 17 00:00:00 2001 From: Juan Ferrer Toribio Date: Mon, 26 Jun 2023 10:56:53 +0200 Subject: [PATCH 21/22] refs #5900, #5772 Changelog updated --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa2ebcd62..47eeb47a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,11 +9,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - (Entradas -> Correo) Al cambiar el tipo de cambio enviará un correo a las personas designadas +- (General -> Históricos) Botón para ver el estado del registro en cada punto +- (General -> Históricos) Al filtar por registro se muestra todo el histórial desde que fue creado ### Changed +- (General -> Históricos) Los registros se muestran agrupados por usuario y entidad +- (Facturas -> Facturación global) Optimizada, generación de PDFs y notificaciones en paralelo ### Fixed -- +- (General -> Históricos) Duplicidades eliminadas +- (Facturas -> Facturación global) Solucionados fallos que paran el proceso ## [2324.01] - 2023-06-15 From ef8e1c4169b1008098615c34ba2429623a0b8b0a Mon Sep 17 00:00:00 2001 From: alexm Date: Mon, 26 Jun 2023 10:57:12 +0200 Subject: [PATCH 22/22] refs #5531 refs #5753 fix travelConfig sql --- db/changes/232402/00-hotFix_travelConfig.sql | 22 ++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 db/changes/232402/00-hotFix_travelConfig.sql diff --git a/db/changes/232402/00-hotFix_travelConfig.sql b/db/changes/232402/00-hotFix_travelConfig.sql new file mode 100644 index 000000000..65450a74d --- /dev/null +++ b/db/changes/232402/00-hotFix_travelConfig.sql @@ -0,0 +1,22 @@ +CREATE TABLE `vn`.`travelConfig` ( + `id` int(11) unsigned NOT NULL AUTO_INCREMENT, + `warehouseInFk` smallint(6) unsigned NOT NULL DEFAULT 8 COMMENT 'Warehouse de origen', + `warehouseOutFk` smallint(6) unsigned NOT NULL DEFAULT 60 COMMENT 'Warehouse destino', + `agencyFk` int(11) NOT NULL DEFAULT 1378 COMMENT 'Agencia por defecto', + `companyFk` int(10) unsigned NOT NULL DEFAULT 442 COMMENT 'Compañía por defecto', + PRIMARY KEY (`id`), + KEY `travelConfig_FK` (`warehouseInFk`), + KEY `travelConfig_FK_1` (`warehouseOutFk`), + KEY `travelConfig_FK_2` (`agencyFk`), + KEY `travelConfig_FK_3` (`companyFk`), + CONSTRAINT `travelConfig_FK` FOREIGN KEY (`warehouseInFk`) REFERENCES `warehouse` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `travelConfig_FK_1` FOREIGN KEY (`warehouseOutFk`) REFERENCES `warehouse` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `travelConfig_FK_2` FOREIGN KEY (`agencyFk`) REFERENCES `agencyMode` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `travelConfig_FK_3` FOREIGN KEY (`companyFk`) REFERENCES `company` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; + +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('Entry', 'addFromPackaging', 'WRITE', 'ALLOW', 'ROLE', 'production'), + ('Entry', 'addFromBuy', 'WRITE', 'ALLOW', 'ROLE', 'production'), + ('Supplier', 'getItemsPackaging', 'READ', 'ALLOW', 'ROLE', 'production');