diff --git a/front/core/components/watcher/locale/es.yml b/front/core/components/watcher/locale/es.yml index 5d25752b4..83553d20d 100644 --- a/front/core/components/watcher/locale/es.yml +++ b/front/core/components/watcher/locale/es.yml @@ -1,4 +1,4 @@ Are you sure exit without saving?: ¿Seguro que quieres salir sin guardar? Unsaved changes will be lost: Los cambios que no hayas guardado se perderán No changes to save: No hay cambios que guardar -Some fields are invalid: Algunos campos no son válidos \ No newline at end of file +Some fields are invalid: Algunos campos no son válidos diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 2187371cd..9d7d2b75f 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -203,11 +203,19 @@ "keepPrice": "keepPrice", "Cannot past travels with entries": "Cannot past travels with entries", "It was not able to remove the next expeditions:": "It was not able to remove the next expeditions: {{expeditions}}", +<<<<<<< HEAD +======= + "Try again": "Try again", +>>>>>>> 5918c14d41259e9989b63c8a36f0e23612cf5d69 "Incorrect pin": "Incorrect pin.", "The notification subscription of this worker cant be modified": "The notification subscription of this worker cant be modified", "Name should be uppercase": "Name should be uppercase", "You cannot update these fields": "You cannot update these fields", +<<<<<<< HEAD "CountryFK cannot be empty": "Country cannot be empty", "You are not allowed to modify the alias": "You are not allowed to modify the alias", "You already have the mailAlias": "You already have the mailAlias" +======= + "CountryFK cannot be empty": "Country cannot be empty" +>>>>>>> 5918c14d41259e9989b63c8a36f0e23612cf5d69 } diff --git a/loopback/locale/es.json b/loopback/locale/es.json index aea0c311c..f4aca18d4 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -1,4 +1,8 @@ { +<<<<<<< HEAD +======= + "postalcode": "Código postal", +>>>>>>> 5918c14d41259e9989b63c8a36f0e23612cf5d69 "Phone format is invalid": "El formato del teléfono no es correcto", "You are not allowed to change the credit": "No tienes privilegios para modificar el crédito", "Unable to mark the equivalence surcharge": "No se puede marcar el recargo de equivalencia", @@ -139,7 +143,11 @@ "Claim state has changed to": "Se ha cambiado el estado de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}* a *{{newState}}*", "Client checked as validated despite of duplication": "Cliente comprobado a pesar de que existe el cliente id {{clientId}}", "ORDER_ROW_UNAVAILABLE": "No hay disponibilidad de este producto", +<<<<<<< HEAD "Distance must be lesser than 4000": "La distancia debe ser inferior a 4000", +======= + "Distance must be lesser than 1000": "La distancia debe ser inferior a 1000", +>>>>>>> 5918c14d41259e9989b63c8a36f0e23612cf5d69 "This ticket is deleted": "Este ticket está eliminado", "Unable to clone this travel": "No ha sido posible clonar este travel", "This thermograph id already exists": "La id del termógrafo ya existe", @@ -333,16 +341,28 @@ "It was not able to remove the next expeditions:": "No se pudo eliminar las siguientes expediciones: {{expeditions}}", "This claim has been updated": "La reclamación con Id: {{claimId}}, ha sido actualizada", "This user does not have an assigned tablet": "Este usuario no tiene tablet asignada", +<<<<<<< HEAD "Incorrect pin": "Pin incorrecto", "You already have the mailAlias": "Ya tienes este alias de correo", "The alias cant be modified": "Este alias de correo no puede ser modificado", +======= + "Field are invalid": "El campo '{{tag}}' no es válido", + "Incorrect pin": "Pin incorrecto.", + "You already have the mailAlias": "Ya tienes este alias de correo", + "The alias cant be modified": "Este alias de correo no puede ser modificado", + "No tickets to invoice": "No hay tickets para facturar", +>>>>>>> 5918c14d41259e9989b63c8a36f0e23612cf5d69 "This ticket already has a cmr saved": "Este ticket ya tiene un cmr guardado", "Name should be uppercase": "El nombre debe ir en mayúscula", "Bank entity must be specified": "La entidad bancaria es obligatoria", "An email is necessary": "Es necesario un email", "You cannot update these fields": "No puedes actualizar estos campos", "CountryFK cannot be empty": "El país no puede estar vacío", +<<<<<<< HEAD "Cmr file does not exist": "El archivo del cmr no existe", "You are not allowed to modify the alias": "No estás autorizado a modificar el alias", "No tickets to invoice": "No hay tickets para facturar" +======= + "Cmr file does not exist": "El archivo del cmr no existe" +>>>>>>> 5918c14d41259e9989b63c8a36f0e23612cf5d69 } diff --git a/loopback/server/middleware/error-handler.js b/loopback/server/middleware/error-handler.js index cc7b81618..dcce7d54a 100644 --- a/loopback/server/middleware/error-handler.js +++ b/loopback/server/middleware/error-handler.js @@ -1,7 +1,14 @@ -const SalixError = require('../../util/salixError'); -const UserError = require('../../util/user-error'); +const SalixError = require('vn-loopback/util/salixError'); +const UserError = require('vn-loopback/util/user-error'); const logToConsole = require('strong-error-handler/lib/logger'); - +const valueIsNot = require('./value-is-not'); +const valueInvalid = require('./value-invalid'); +const mapMethods = require('vn-loopback/util/map-methods'); +const validations = [ + valueIsNot, + valueInvalid, + mapMethods +]; module.exports = function() { return function(err, req, res, next) { // Thrown user errors @@ -11,7 +18,19 @@ module.exports = function() { } // Validation errors - if (err.statusCode == 422) { + if ([400, 422].includes(err.statusCode)) { + try { + validations.forEach(validation => { + if (validation.validation(err.message)) { + const error = validation.handleError(req, err); + if (error) + err.message = validation.message(error, req); + } + }); + + return next(err); + } catch (e) { + } try { let code; let {messages} = err.details; @@ -26,7 +45,6 @@ module.exports = function() { return next(new UserError(req.__(err.sqlMessage))); // Logs error to console - let env = process.env.NODE_ENV; let useCustomLogging = env && env != 'development' && (!err.statusCode || err.statusCode >= 500); diff --git a/loopback/server/middleware/specs/value-is-not.spec.js b/loopback/server/middleware/specs/value-is-not.spec.js new file mode 100644 index 000000000..4d65ddf56 --- /dev/null +++ b/loopback/server/middleware/specs/value-is-not.spec.js @@ -0,0 +1,59 @@ +const valueIsNot = require('../value-is-not'); + +fdescribe('Value is not', () => { + const i18n = require('i18n'); + + const userId = 9; + const ctx = { + req: { + + accessToken: {userId: userId}, + headers: {origin: 'http://localhost:5000'}, + } + }; + + fit('UpdateFiscalData endpoint', () => { + let messageError = 'Value is not number'; + const tag = 'provinceFk'; + try { + expect(valueIsNot.validation(messageError)).toBeTrue(); + const data = { + method: 'PATCH', + originalUrl: '/api/supplier/updateFiscalData', + body: { + [tag]: null + }, + __: i18n + }; + const result = valueIsNot.handleError(data); + + expect(result.tag).toEqual(tag); + expect(valueIsNot.message(result, i18n)).toEqual('El campo \'provincia\' no es válido'); + } catch (error) { + expect(error).toBeDefined(); + } + }); + + describe('OsTicket', () => { + let messageError = 'Value is not object'; + const tag = 'additionalData'; + it('sendToSupport endpoint', () => { + try { + expect(valueIsNot.validation(messageError)).toBeTrue(); + const data = { + method: 'POST', + originalUrl: '/api/OsTickets/send-to-support?access_token=DEFAULT_TOKEN', + body: { + [tag]: '{ \'foo\': 42 }' + } + }; + const result = valueIsNot.handleError(data); + + expect(result.tag).toEqual(tag); + expect(valueIsNot.message(tag, i18n)).toEqual(`El campo '${tag}' no es válido`); + } catch (error) { + expect(error).toBeDefined(); + } + }); + }); +}); diff --git a/loopback/server/middleware/value-invalid.js b/loopback/server/middleware/value-invalid.js new file mode 100644 index 000000000..962aa9e1f --- /dev/null +++ b/loopback/server/middleware/value-invalid.js @@ -0,0 +1,58 @@ +const SLASH = '/'; +const $t = require('i18n'); +const {models} = require('vn-loopback/server/server'); +const mapLocale = require('../../util/map-locales'); + + +module.exports = { + validation: message => String(message).includes('is not valid'), + message: ({tagValue}, {__: $t}) => + $t('Field are invalid', {tag: tagValue}), + handleError: ({method: verb, originalUrl, body}, err) => { + let tag = null; + let module = null; + let moduleOriginal = null; + let path = null; + try { + if (originalUrl.includes('?')) + originalUrl = originalUrl.split('?')[0]; + + originalUrl = originalUrl.split('api/')[1]; + [module, ...path] = originalUrl.split(SLASH); + + moduleOriginal = module; + let model = models[module]; + // Capitalize + if (!model) { + module = module.charAt(0).toUpperCase() + module.slice(1); + model = models[module]; + } + // Singular + if (!model) { + module = module.substring(0, module.length - 1); + model = models[module]; + } + if (!model) throw new Error('No matching model found'); + tag = Object.keys(err.details.codes)[0]; + if (tag) { + let tagValue = mapLocale[$t.getLocale()][tag]; + if (!tagValue) tagValue = tag; + return {tag, tagValue}; + } + } catch (error) { + throw new Error(error); + } + } + +}; + + + +function isJsonString(str) { + try { + let json = JSON.parse(str); + return (typeof json === 'object'); + } catch (e) { + return false; + } +} diff --git a/loopback/server/middleware/value-is-not.js b/loopback/server/middleware/value-is-not.js new file mode 100644 index 000000000..021af3fb9 --- /dev/null +++ b/loopback/server/middleware/value-is-not.js @@ -0,0 +1,106 @@ +const SLASH = '/'; +const {models} = require('vn-loopback/server/server'); +const $t = require('i18n'); +const mapLocale = require('../../util/map-locales'); + +module.exports = { + validation: message => String(message).startsWith('Value is not'), + message: ({tagValue}, {__: $t}) => + $t('Field are invalid', {tag: tagValue}), + handleError: ({method: verb, originalUrl, body}) => { + let tag = null; + let module = null; + let path = null; + let hasId = false; + try { + if (originalUrl.includes('?')) + originalUrl = originalUrl.split('?')[0]; + + originalUrl = originalUrl.split('api/')[1]; + [module, ...path] = originalUrl.split(SLASH); + hasId = path.length > 1; + + let model = models[module]; + // Capitalize + if (!model) { + module = module.charAt(0).toUpperCase() + module.slice(1); + model = models[module]; + } + // Singular + if (!model) { + module = module.substring(0, module.length - 1); + model = models[module]; + } + if (!model) throw new Error('No matching model found'); + const currentMethod = model.sharedClass.methods().find(method => { + const methodMatch = [method.http].flat().filter(el => el.verb === verb.toLowerCase()).find(el => { + let isValid = false; + if (hasId) + isValid = el.path.replace(':id', path[0]) === SLASH + path.join(SLASH); + else + isValid = el.path.endsWith(path[0]); + return isValid; + }); + if (methodMatch) + return method; + } + ); + if (!currentMethod) throw new Error('No matching currentMethod found'); + const {accepts} = currentMethod; + for (const [key, value] of Object.entries(body)) { + if (!value) { + tag = key; + break; + } + const accept = accepts.find(acc => acc.arg === key); + if (accept.type !== 'any') { + let isValid = false; + if (value) { + switch (accept.type) { + case 'object': + isValid = isJsonString(value); + break; + case 'number': + isValid = /^[0-9]*$/.test(value); + break; + + case 'boolean': + isValid = value instanceof Boolean; + break; + + case 'date': + isValid = (new Date(date) !== 'Invalid Date') && !isNaN(new Date(date)); + break; + + case 'string': + isValid = typeof value == 'string'; + break; + } + } + if (!isValid) { + tag = key; + break; + } + } + } + if (tag) { + let tagValue = mapLocale[$t.getLocale()][tag]; + if (!tagValue) tagValue = tag; + return {tag, tagValue}; + } + } catch (error) { + throw new Error(error); + } + } + +}; + + +function isJsonString(str) { + try { + let json = JSON.parse(str); + return (typeof json === 'object'); + } catch (e) { + return false; + } +} diff --git a/loopback/util/map-locales.js b/loopback/util/map-locales.js new file mode 100644 index 000000000..692de3f23 --- /dev/null +++ b/loopback/util/map-locales.js @@ -0,0 +1,23 @@ +const SLASH = '/'; +const MODULES = 'modules'; +const BACK = 'back'; +const path = require('path'); +const glob = require('glob'); +const modulesPath = `${MODULES}/**/${BACK}/locale/**/**.yml`; +const pathResolve = path.resolve(modulesPath); + +const modelsLocale = glob.sync(pathResolve, {}).reduce((acc, f) => { + const file = require(f); + const model = f.substring(f.indexOf(MODULES), f.indexOf(BACK) - 1).split(SLASH)[1]; + const locale = path.parse(f).base.split('.')[0]; + + if (!acc[locale]) acc[locale] = {}; + acc[locale] = Object.assign(acc[locale], file.columns); + return acc; +}, {} +); +function keyMap(model, local, connector = '_') { + return `${model}${connector}${local}`; +} +module.exports =modelsLocale; + diff --git a/loopback/util/map-methods.js b/loopback/util/map-methods.js new file mode 100644 index 000000000..2858cd61d --- /dev/null +++ b/loopback/util/map-methods.js @@ -0,0 +1,12 @@ +const SLASH = '/'; +const MODULES = 'modules'; +const BACK = 'back'; + +const app = require('vn-loopback/server/server'); + +const modelsMethods = app; +function keyMap(model, local, connector = '_') { + return `${model}${connector}${local}`; +} +module.exports = modelsMethods; + diff --git a/modules/client/back/methods/client/updateFiscalData.js b/modules/client/back/methods/client/updateFiscalData.js index 8fe92ecd0..f6ea80cfc 100644 --- a/modules/client/back/methods/client/updateFiscalData.js +++ b/modules/client/back/methods/client/updateFiscalData.js @@ -26,7 +26,7 @@ module.exports = Self => { }, { arg: 'street', - type: 'string' + type: 'any' }, { arg: 'postcode', @@ -94,7 +94,7 @@ module.exports = Self => { }, { arg: 'despiteOfClient', - type: 'number' + type: 'any' }, { arg: 'hasIncoterms', diff --git a/package.json b/package.json index 47b3a1564..88c6d69f8 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "form-data": "^4.0.0", "fs-extra": "^5.0.0", "ftps": "^1.2.0", + "glob": "^10.3.10", "gm": "^1.25.0", "got": "^10.7.0", "helmet": "^3.21.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 025be234e..d80825dcc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ dependencies: ftps: specifier: ^1.2.0 version: 1.2.0 + glob: + specifier: ^10.3.10 + version: 10.3.10 gm: specifier: ^1.25.0 version: 1.25.0 @@ -1625,7 +1628,6 @@ packages: strip-ansi-cjs: /strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: /wrap-ansi@7.0.0 - dev: true /@istanbuljs/load-nyc-config@1.1.0: resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} @@ -1913,7 +1915,6 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} requiresBuild: true - dev: true optional: true /@puppeteer/browsers@1.9.1: @@ -3027,7 +3028,6 @@ packages: /ansi-regex@6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} - dev: true /ansi-styles@2.2.1: resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} @@ -3049,7 +3049,6 @@ packages: /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - dev: true /ansi-wrap@0.1.0: resolution: {integrity: sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==} @@ -5207,7 +5206,6 @@ packages: /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - dev: true /ecc-jsbn@0.1.2: resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} @@ -5266,7 +5264,6 @@ packages: /emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - dev: true /emojis-list@3.0.0: resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} @@ -6102,7 +6099,6 @@ packages: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 - dev: true /forever-agent@0.6.1: resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} @@ -6496,7 +6492,6 @@ packages: minimatch: 9.0.3 minipass: 7.0.4 path-scurry: 1.10.1 - dev: true /glob@3.2.11: resolution: {integrity: sha512-hVb0zwEZwC1FXSKRPFTeOtN7AArJcJlI6ULGLtrstaswKNlrTJqAA+1lYlSUop4vjA423xlBzqfVS3iWGlqJ+g==} @@ -6511,7 +6506,7 @@ packages: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.0.8 + minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 dev: true @@ -8005,7 +8000,6 @@ packages: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 - dev: true /jade@0.26.3: resolution: {integrity: sha512-mkk3vzUHFjzKjpCXeu+IjXeZD+QOTjUUdubgmHtHTDwvAO2ZTkMTTVrapts5CWz3JvJryh/4KWZpjeZrCepZ3A==} @@ -9254,7 +9248,6 @@ packages: /lru-cache@10.2.0: resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} engines: {node: 14 || >=16.14} - dev: true /lru-cache@2.7.3: resolution: {integrity: sha512-WpibWJ60c3AgAz8a2iYErDrcT2C7OmKnsWhIcHOjkUHFjkXncJhtLxNSqUmxRxRunpb5I8Vprd7aNSd2NtksJQ==} @@ -9660,7 +9653,6 @@ packages: engines: {node: '>=16 || 14 >=14.17'} dependencies: brace-expansion: 2.0.1 - dev: true /minimist-options@4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} @@ -9750,7 +9742,6 @@ packages: /minipass@7.0.4: resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} engines: {node: '>=16 || 14 >=14.17'} - dev: true /minizlib@1.3.3: resolution: {integrity: sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==} @@ -10845,7 +10836,6 @@ packages: dependencies: lru-cache: 10.2.0 minipass: 7.0.4 - dev: true /path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} @@ -12176,7 +12166,6 @@ packages: /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - dev: true /simple-concat@1.0.1: resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} @@ -12633,7 +12622,6 @@ packages: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.1.0 - dev: true /string_decoder@0.10.31: resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==} @@ -12680,7 +12668,6 @@ packages: engines: {node: '>=12'} dependencies: ansi-regex: 6.0.1 - dev: true /strip-bom@2.0.0: resolution: {integrity: sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==} @@ -14183,7 +14170,6 @@ packages: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 - dev: true /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}