From ebfba823b05efd62f0f4bcfb1f742bbc1720350a Mon Sep 17 00:00:00 2001 From: carlossa Date: Fri, 21 Feb 2025 14:59:02 +0100 Subject: [PATCH 1/7] fix: refs #6553 workerBusiness, workerDescriptor --- .../11454-orangeDracena/00-firstScript.sql | 3 + .../back/methods/worker/getWorkerBusiness.js | 258 ++++++++++++++++++ modules/worker/back/models/worker.js | 1 + 3 files changed, 262 insertions(+) create mode 100644 db/versions/11454-orangeDracena/00-firstScript.sql create mode 100644 modules/worker/back/methods/worker/getWorkerBusiness.js diff --git a/db/versions/11454-orangeDracena/00-firstScript.sql b/db/versions/11454-orangeDracena/00-firstScript.sql new file mode 100644 index 0000000000..05b2a8c059 --- /dev/null +++ b/db/versions/11454-orangeDracena/00-firstScript.sql @@ -0,0 +1,3 @@ +-- Place your SQL code here +INSERT INTO salix.ACL (model,property,accessType,permission,principalType,principalId) + VALUES ('Worker','getWorkerBusiness','READ','ALLOW','ROLE','hr'); diff --git a/modules/worker/back/methods/worker/getWorkerBusiness.js b/modules/worker/back/methods/worker/getWorkerBusiness.js new file mode 100644 index 0000000000..1f65973cab --- /dev/null +++ b/modules/worker/back/methods/worker/getWorkerBusiness.js @@ -0,0 +1,258 @@ +const {filter} = require('jszip'); +const {ContextExclusionPlugin} = require('webpack'); + +const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; +const buildFilter = require('vn-loopback/util/filter').buildFilter; +const mergeFilters = require('vn-loopback/util/filter').mergeFilters; + +module.exports = Self => { + Self.remoteMethodCtx('getWorkerBusiness', { + description: 'Returns an array of business from an specified worker', + accessType: 'READ', + accepts: [{ + arg: 'id', + type: 'number', + required: true, + description: 'The worker id', + http: {source: 'path'} + }, + { + arg: 'filter', + type: 'object', + description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string' + }, + { + arg: 'started', + type: 'date', + description: 'started', + http: {source: 'query'} + }, + { + arg: 'ended', + type: 'date', + description: 'ended', + http: {source: 'query'} + }, + { + arg: 'companyCodeFk', + type: 'string', + description: 'companyCodeFk', + http: {source: 'query'} + }, + { + arg: 'reasonEndFk', + type: 'number', + description: 'reasonEndFk', + http: {source: 'query'} + }, + { + arg: 'reasondEnd', + type: 'string', + description: 'reasondEnd', + http: {source: 'query'} + }, + { + arg: 'departmentFk', + type: 'number', + description: 'departmentFk', + http: {source: 'query'} + }, + { + arg: 'department', + type: 'string', + description: 'department', + http: {source: 'query'} + }, + { + arg: 'workerBusinessProfessionalCategoryFk', + type: 'number', + description: 'workerBusinessProfessionalCategoryFk', + http: {source: 'query'} + }, + { + arg: 'workerBusinessProfessionalCategory', + type: 'string', + description: 'workerBusinessProfessionalCategory', + http: {source: 'query'} + }, + { + arg: 'calendarTypeFk', + type: 'number', + description: 'calendarTypeFk', + http: {source: 'query'} + }, + { + arg: 'calendarType', + type: 'string', + description: 'calendarType', + http: {source: 'query'} + }, + { + arg: 'workcenterFk', + type: 'number', + description: 'workcenterFk', + http: {source: 'query'} + }, + { + arg: 'workCenter', + type: 'string', + description: 'workCenter', + http: {source: 'query'} + }, + { + arg: 'workerBusinessCategoryFk', + type: 'number', + description: 'WorkerBusinessCategoryFk', + http: {source: 'query'} + }, + { + arg: 'workerBusinessCategory', + type: 'string', + description: 'workerBusinessCategory', + http: {source: 'query'} + }, + { + arg: 'occupationCodeFk', + type: 'number', + description: 'occupationCodeFk', + http: {source: 'query'} + }, + { + arg: 'occupationCode', + type: 'string', + description: 'occupationCode', + http: {source: 'query'} + }, + { + arg: 'rate', + type: 'number', + description: 'rate', + http: {source: 'query'} + }, + { + arg: 'workerBusinessTypeFk', + type: 'number', + description: 'workerBusinessTypeFk', + http: {source: 'query'} + }, + { + arg: 'workerBusinessType', + type: 'string', + description: 'workerBusinessType', + http: {source: 'query'} + }, + { + arg: 'amount', + type: 'number', + description: 'amount', + http: {source: 'query'} + }, + { + arg: 'basicSalary', + type: 'number', + description: 'amount', + http: {source: 'query'} + }, + { + arg: 'notes', + type: 'string', + description: 'notes', + http: {source: 'query'} + }], + returns: { + type: ['object'], + root: true + }, + http: { + path: '/:id/getWorkerBusiness', + verb: 'GET' + } + }); + + Self.getWorkerBusiness = async(ctx, id, filter, options) => { + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + let conn = Self.dataSource.connector; + let where = buildFilter(ctx.args, (param, value) => { + switch (param) { + case 'started': + case 'ended': + case 'companyCodeFk': + case 'reasonEndFk': + case 'reasondEnd': + case 'departmentFk': + case 'department': + case 'workerBusinessProfessionalCategoryFk': + case 'workerBusinessProfessionalCategory': + case 'calendarTypeFk': + case 'calendarType': + case 'workcenterFk': + case 'workCenter': + case 'workerBusinessCategoryFk': + case 'workerBusinessCategory': + case 'occupationCodeFk': + case 'occupationCode': + case 'rate': + case 'workerBusinessTypeFk': + case 'workerBusinessType': + case 'amount': + case 'basicSalary': + case 'notes': + } + }); + where = {...where, ...{workerFk: id}}; + filter = mergeFilters(filter, {where}); + + let stmts = []; + let stmt; + + stmt = new ParameterizedSQL( + `SELECT * FROM( + SELECT b.id, + b.workerFk, + b.started, + b.ended, + b.companyCodeFk, + b.reasonEndFk, + bre.reason, + b.departmentFk, + d.name departmentName, + b.occupationCodeFk, + oc.name occupationName, + b.workerBusinessProfessionalCategoryFk, + pf.description professionalDescription, + b.calendarTypeFk, + ct.description calendarTypeDescription, + b.workcenterFk, + wc.name workCenterName, + b.workerBusinessCategoryFk, + py.description payrollDescription, + b.workerBusinessTypeFk, + wt.name workerBusinessTypeName, + b.amount, + b.basicSalary, + b.notes + FROM business b + LEFT JOIN businessReasonEnd bre ON bre.id = b.reasonEndFk + LEFT JOIN occupationCode oc ON oc.code = b.occupationCodeFk + LEFT JOIN department d ON d.id = b.departmentFk + LEFT JOIN professionalCategory pf ON pf.id = b.workerBusinessProfessionalCategoryFk + JOIN calendarType ct ON ct.id = b.calendarTypeFk + LEFT JOIN workCenter wc ON wc.id = b.workcenterFk + LEFT JOIN payrollCategories py ON py.id = b.workerBusinessCategoryFk + LEFT JOIN workerBusinessType wt ON wt.id = b.workerBusinessTypeFk + ) sub + ` + ); + + stmt.merge(conn.makeSuffix(filter)); + stmts.push(stmt); + + let sql = ParameterizedSQL.join(stmts, ';'); + let result = await conn.executeStmt(sql, myOptions); + return result; + }; +}; diff --git a/modules/worker/back/models/worker.js b/modules/worker/back/models/worker.js index 4895a6107f..2f8c28ef4b 100644 --- a/modules/worker/back/models/worker.js +++ b/modules/worker/back/models/worker.js @@ -21,6 +21,7 @@ module.exports = Self => { require('../methods/worker/setPassword')(Self); require('../methods/worker/getAvailablePda')(Self); require('../methods/worker/myTeam')(Self); + require('../methods/worker/getWorkerBusiness')(Self); Self.canModifyAbsenceInPast = async(ctx, time) => { const hasPrivs = await Self.app.models.ACL.checkAccessAcl(ctx, 'Worker', 'canModifyAbsenceInPast', 'WRITE'); From 35cfce552b0155c2b10f08d68750e9ad2e6780aa Mon Sep 17 00:00:00 2001 From: carlossa Date: Fri, 21 Feb 2025 15:14:46 +0100 Subject: [PATCH 2/7] fix: refs #6553 getWorkerBusiness --- modules/worker/back/methods/worker/getWorkerBusiness.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules/worker/back/methods/worker/getWorkerBusiness.js b/modules/worker/back/methods/worker/getWorkerBusiness.js index 1f65973cab..cefde70085 100644 --- a/modules/worker/back/methods/worker/getWorkerBusiness.js +++ b/modules/worker/back/methods/worker/getWorkerBusiness.js @@ -1,6 +1,3 @@ -const {filter} = require('jszip'); -const {ContextExclusionPlugin} = require('webpack'); - const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; const buildFilter = require('vn-loopback/util/filter').buildFilter; const mergeFilters = require('vn-loopback/util/filter').mergeFilters; From 119ae0819b66e898c6da0d0cee3ccad36468e7f7 Mon Sep 17 00:00:00 2001 From: sergiodt Date: Mon, 24 Feb 2025 11:07:31 +0100 Subject: [PATCH 3/7] feat: refs#8624 addWorkerActions --- back/methods/workerActivity/add.js | 15 ++++++++++----- db/versions/11455-orangeTulip/00-firstScript.sql | 9 +++++++++ 2 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 db/versions/11455-orangeTulip/00-firstScript.sql diff --git a/back/methods/workerActivity/add.js b/back/methods/workerActivity/add.js index 89131491d1..b5b1bd65de 100644 --- a/back/methods/workerActivity/add.js +++ b/back/methods/workerActivity/add.js @@ -13,6 +13,11 @@ module.exports = Self => { type: 'string', description: 'Origin model from insert' }, + { + arg: 'description', + type: 'string', + description: 'Action description' + }, ], http: { @@ -21,7 +26,7 @@ module.exports = Self => { } }); - Self.add = async(ctx, code, model, options) => { + Self.add = async(ctx, code, model, description, options) => { const userId = ctx.req.accessToken.userId; const myOptions = {}; @@ -29,8 +34,8 @@ module.exports = Self => { Object.assign(myOptions, options); return await Self.rawSql(` - INSERT INTO workerActivity (workerFk, workerActivityTypeFk, model) - SELECT ?, ?, ? + INSERT INTO workerActivity (workerFk, workerActivityTypeFk, model, description) + SELECT ?, ?, ?, ? FROM workerTimeControlConfig wtcc LEFT JOIN ( SELECT wa.workerFk, @@ -43,8 +48,8 @@ module.exports = Self => { LIMIT 1 ) sub ON TRUE WHERE sub.workerFk IS NULL - OR sub.code <> ? + OR sub.code <> ? OR TIMESTAMPDIFF(SECOND, sub.created, util.VN_NOW()) > wtcc.dayBreak;` - , [userId, code, model, userId, code], myOptions); + , [userId, code, model, description, userId, code], myOptions); }; }; diff --git a/db/versions/11455-orangeTulip/00-firstScript.sql b/db/versions/11455-orangeTulip/00-firstScript.sql new file mode 100644 index 0000000000..d64c8e324e --- /dev/null +++ b/db/versions/11455-orangeTulip/00-firstScript.sql @@ -0,0 +1,9 @@ + + +USE vn; + +INSERT INTO vn.workerActivityType (code, description) +VALUES('SHELVING_CLEAN_START', 'SE INICIA LIMPIEZA CARRO'), + ('SHELVING_CLEAN_STOP', 'SE FINALIZA LIMPIEZA CARRO'); + + From 2941e5f80aa0f38870c9c8c777b82e68262cebb4 Mon Sep 17 00:00:00 2001 From: sergiodt Date: Mon, 24 Feb 2025 16:48:41 +0100 Subject: [PATCH 4/7] feat: refs #8624 refs#8624 addWorkerActions --- back/methods/workerActivity/specs/add.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/back/methods/workerActivity/specs/add.spec.js b/back/methods/workerActivity/specs/add.spec.js index 751cce009e..50d50eb4cb 100644 --- a/back/methods/workerActivity/specs/add.spec.js +++ b/back/methods/workerActivity/specs/add.spec.js @@ -3,7 +3,7 @@ const {models} = require('vn-loopback'); describe('workerActivity insert()', () => { const ctx = beforeAll.getCtx(1106); - it('should insert in workerActivity', async() => { + fit('should insert in workerActivity', async() => { const tx = await models.WorkerActivity.beginTransaction({}); let count = 0; const options = {transaction: tx}; @@ -13,7 +13,7 @@ describe('workerActivity insert()', () => { {'code': 'TEST', 'description': 'TEST'}, options ); - await models.WorkerActivity.add(ctx, 'TEST', 'APP', options); + await models.WorkerActivity.add(ctx, 'TEST', 'APP', 'description', options); count = await models.WorkerActivity.count( {'workerFK': 1106}, options From 08cd2d222181c0dbfe51c54bf77d078065c9adf1 Mon Sep 17 00:00:00 2001 From: sergiodt Date: Mon, 24 Feb 2025 16:50:35 +0100 Subject: [PATCH 5/7] feat: refs #8624 refs#8624 addWorkerActions --- back/methods/workerActivity/specs/add.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/back/methods/workerActivity/specs/add.spec.js b/back/methods/workerActivity/specs/add.spec.js index 50d50eb4cb..ad6cc7fa70 100644 --- a/back/methods/workerActivity/specs/add.spec.js +++ b/back/methods/workerActivity/specs/add.spec.js @@ -3,7 +3,7 @@ const {models} = require('vn-loopback'); describe('workerActivity insert()', () => { const ctx = beforeAll.getCtx(1106); - fit('should insert in workerActivity', async() => { + it('should insert in workerActivity', async() => { const tx = await models.WorkerActivity.beginTransaction({}); let count = 0; const options = {transaction: tx}; From fbdc715fa59af3beb06419aaf082b4f7415597b7 Mon Sep 17 00:00:00 2001 From: alexm Date: Tue, 25 Feb 2025 14:59:49 +0100 Subject: [PATCH 6/7] feat: enhance ticket closing process with email notifications and error handling --- .../ticket/back/methods/ticket/closeAll.js | 83 ++++++++++++------- 1 file changed, 55 insertions(+), 28 deletions(-) diff --git a/modules/ticket/back/methods/ticket/closeAll.js b/modules/ticket/back/methods/ticket/closeAll.js index b645c714c9..9559333c87 100644 --- a/modules/ticket/back/methods/ticket/closeAll.js +++ b/modules/ticket/back/methods/ticket/closeAll.js @@ -1,5 +1,6 @@ const smtp = require('vn-print/core/smtp'); const config = require('vn-print/core/config'); +const Email = require('vn-print/core/email'); module.exports = Self => { Self.remoteMethodCtx('closeAll', { @@ -44,8 +45,7 @@ module.exports = Self => { LIMIT 1`, [toDate, toDate], myOptions); await Self.rawSql(` - DROP TEMPORARY TABLE IF EXISTS tmp.ticket_close; - CREATE TEMPORARY TABLE tmp.ticket_close + CREATE OR REPLACE TEMPORARY TABLE tmp.ticket_close ENGINE = MEMORY WITH wTickets AS( SELECT t.id ticketFk @@ -63,11 +63,12 @@ module.exports = Self => { FROM wTicketsTracking wt JOIN ticketTracking tt ON tt.id = wt.maxTracking ) SELECT tls.ticketFk, - t.clientFk, + t.clientFk clientId, c.name clientName, c.email recipient, + c.isToBeMailed, eu.email salesPersonEmail, - t.addressFk, + t.addressFk addressId, c.hasDailyInvoice, c.hasToInvoiceByAddress, t.totalWithVat, @@ -79,7 +80,7 @@ module.exports = Self => { JOIN agencyMode am ON am.id = t.agencyModeFk JOIN client c ON c.id = t.clientFk LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk - WHERE (al.code = 'PACKED' OR (am.code = 'refund' AND al.code <> 'delivered')); + WHERE al.code = 'PACKED' OR (am.code = 'refund' AND al.code <> 'delivered'); CALL ticket_close(); `, [dateFrom, dateTo], myOptions); @@ -98,21 +99,27 @@ module.exports = Self => { AND tob.id IS NULL AND t.routeFk`, [dateFrom, dateTo], myOptions); - const [clients] = await Self.rawSql(` - SELECT clientFk clientId, - clientName, - recipient, - salesPersonEmail, - addressFk addressId, - companyFk, + const clients = await Self.rawSql(` + SELECT *, SUM(totalWithVat) total, 'quick' serialType FROM tmp.ticket_close WHERE hasDailyInvoice - GROUP BY IF (hasToInvoiceByAddress, addressFk, clientFk), companyFk - HAVING total > 0; + GROUP BY IF (hasToInvoiceByAddress, addressId, clientId), companyFk + HAVING total > 0 + `, [], myOptions); + + const [ticketsToMail] = await Self.rawSql(` + SELECT * + FROM tmp.ticket_close + WHERE NOT hasDailyInvoice + AND isToBeMailed + AND salesPersonEmail IS NOT NULL + AND salesPersonEmail <> '' + AND recipient IS NOT NULL + AND recipient <> ''; DROP TEMPORARY TABLE tmp.ticket_close; - `, [], myOptions); + `, [], myOptions); if (tx) await tx.commit(); @@ -130,21 +137,23 @@ module.exports = Self => { if (id) await Self.app.models.InvoiceOut.makePdfAndNotify(ctx, id, null, nestedTransaction); } catch (error) { - await Self.rawSql(` - INSERT INTO util.debug (variable, value) - VALUES ('invoicingTicketError', ?) - `, [client.clientId + ' - ' + error]); + await handleInvoicingError(error, client, failedClients); + } + } - if (error.responseCode == 450) { - await invalidEmail(client); - continue; - } + for (const ticket of ticketsToMail) { + const args = { + id: ticket.ticketFk, + recipientId: ticket.clientId, + recipient: ticket.recipient, + replyTo: ticket.salesPersonEmail, + }; - failedClients.push({ - id: client.clientId, - address: client.addressId, - error - }); + try { + const email = new Email('delivery-note-link', args); + await email.send(); + } catch (error) { + await handleInvoicingError(error, ticket, failedClients); } } @@ -188,4 +197,22 @@ module.exports = Self => { html: body, }).catch(err => console.error(err)); } + + async function handleInvoicingError(error, entity, failedClients) { + await Self.rawSql(` + INSERT INTO util.debug (variable, value) + VALUES ('invoicingTicketError', ?) + `, [entity.clientId + ' - ' + error]); + + if (error.responseCode == 450) { + await invalidEmail(entity); + return; + } + + failedClients.push({ + id: entity.clientId, + address: entity.addressId, + error + }); + } }; From 2914dfff2e456f052e8753afd55c20dbaa81caad Mon Sep 17 00:00:00 2001 From: alexm Date: Tue, 25 Feb 2025 14:59:54 +0100 Subject: [PATCH 7/7] feat: add Catalan localization for email templates and footer --- .../core/components/email-footer/locale/ca.yml | 18 ++++++++++++++++++ .../email/delivery-note-link/locale/ca.yml | 11 +++++++++++ 2 files changed, 29 insertions(+) create mode 100644 print/core/components/email-footer/locale/ca.yml create mode 100644 print/templates/email/delivery-note-link/locale/ca.yml diff --git a/print/core/components/email-footer/locale/ca.yml b/print/core/components/email-footer/locale/ca.yml new file mode 100644 index 0000000000..b0e3fee865 --- /dev/null +++ b/print/core/components/email-footer/locale/ca.yml @@ -0,0 +1,18 @@ +buttons: + webAcccess: Visita la nostra Web + info: Ajuda'ns a millorar +fiscalAddress: VERDNATURA LEVANTE SL, B97367486 C/ Fenollar, 2. 46680 ALGEMESÍ + · verdnatura.es · clientes@verdnatura.es +disclaimer: '- AVÍS - Aquest missatge és privat i confidencial, i ha de ser utilitzat + exclusivament per la persona destinatària del mateix. Si has rebut aquest missatge + per error, et preguem que ho comuniquis al remitent i esborres aquest missatge + i qualsevol document adjunt que pugui contenir. Verdnatura Levante SL no renuncia + a la confidencialitat ni a cap privilegi per causa de transmissió errònia o mal + funcionament. Igualment, no es fa responsable dels canvis, alteracions, errors + o omissions que es puguin fer al missatge un cop enviat.' +privacy: En compliment del que disposa la Llei Orgànica 15/1999, de Protecció de + Dades de Caràcter Personal, et comuniquem que les dades personals que facilitis + s'inclouran en fitxers automatitzats de VERDNATURA LEVANTE S.L., podent en tot + moment exercir els drets d'accés, rectificació, cancel·lació i oposició, + comunicant-ho per escrit al domicili social de l'entitat. La finalitat del + fitxer és la gestió administrativa, comptabilitat i facturació. diff --git a/print/templates/email/delivery-note-link/locale/ca.yml b/print/templates/email/delivery-note-link/locale/ca.yml new file mode 100644 index 0000000000..41f0c128c7 --- /dev/null +++ b/print/templates/email/delivery-note-link/locale/ca.yml @@ -0,0 +1,11 @@ +subject: El teu albarà +title: El teu albarà +dear: Estimat client +description: Ja està disponible l'albarà corresponent a la comanda {0}.
+ Pots veure'l fent clic en aquest enllaç. +copyLink: 'Com a alternativa, pots copiar el següent enllaç al teu navegador:' +poll: Si ho desitges, pots respondre a la nostra enquesta de satisfacció per + ajudar-nos a oferir un millor servei. La teva opinió és molt important per a nosaltres! +help: Qualsevol dubte que tinguis, no dubtis a consultar-nos, estem aquí + per atendre't! +conclusion: Gràcies per la teva atenció!