diff --git a/.eslintrc.yml b/.eslintrc.yml index 13fc2b140..ee20324ff 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -1,6 +1,6 @@ extends: [eslint:recommended, google, plugin:jasmine/recommended] parserOptions: - ecmaVersion: 2018 + ecmaVersion: 2020 sourceType: "module" plugins: - jasmine @@ -35,4 +35,4 @@ rules: space-in-parens: ["error", "never"] jasmine/no-focused-tests: 0 jasmine/prefer-toHaveBeenCalledWith: 0 - arrow-spacing: ["error", { "before": true, "after": true }] \ No newline at end of file + arrow-spacing: ["error", { "before": true, "after": true }] diff --git a/CHANGELOG.md b/CHANGELOG.md index ca342abbd..fa2ebcd62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,20 +8,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [2326.01] - 2023-06-29 ### Added +- (Entradas -> Correo) Al cambiar el tipo de cambio enviará un correo a las personas designadas ### Changed ### Fixed - -## [2324.01] - 2023-06-08 +## [2324.01] - 2023-06-15 ### Added - (Tickets -> Abono) Al abonar permite crear el ticket abono con almacén o sin almmacén - (General -> Desplegables) Mejorada eficiencia de carga de datos +- (General -> Históricos) Ahora, ademas de los ids, se muestra la descripión de los atributos +- (General -> Históricos) Botón para hacer más ágil mostrar sólo los cambios en un registro +- (General -> Históricos) Filtro por cambios ### Changed - (General -> Permisos) Mejorada seguridad +- (General -> Históricos) Elementos de la interfaz reorganizados para hacerla más ágil e intuitiva ### Fixed - @@ -37,7 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - (Trabajadores -> Nuevo trabajador) Los clientes se crean sin 'TR' pero se añade tipo de negocio 'Trabajador' - +- (Tickets -> Expediciones) Interfaz mejorada y contador añadido ### Fixed - (Tickets -> Líneas) Se permite hacer split de líneas al mismo ticket - (Tickets -> Cambiar estado) Ahora muestra la lista completa de todos los estados diff --git a/README.md b/README.md index f73a8551b..59f5dbcf7 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Salix is also the scientific name of a beautifull tree! :) Required applications. -* Node.js = 14.x LTS +* Node.js >= 16.x LTS * Docker * Git @@ -71,7 +71,7 @@ $ npm run test:e2e Open Visual Studio Code, press Ctrl+P and paste the following commands. -In Visual Studio Code we use the ESLint extension. +In Visual Studio Code we use the ESLint extension. ``` ext install dbaeumer.vscode-eslint ``` diff --git a/back/methods/collection/newCollection.js b/back/methods/collection/newCollection.js index 31e419b67..2be9f8b0e 100644 --- a/back/methods/collection/newCollection.js +++ b/back/methods/collection/newCollection.js @@ -30,11 +30,11 @@ module.exports = Self => { Self.newCollection = async(ctx, collectionFk, sectorFk, vWagons) => { let query = ''; + const userId = ctx.req.accessToken.userId; if (!collectionFk) { - const userId = ctx.req.accessToken.userId; query = `CALL vn.collectionTrain_newBeta(?,?,?)`; - const [result] = await Self.rawSql(query, [sectorFk, vWagons, userId]); + const [result] = await Self.rawSql(query, [sectorFk, vWagons, userId], {userId}); if (result.length == 0) throw new Error(`No collections for today`); @@ -42,16 +42,16 @@ module.exports = Self => { } query = `CALL vn.collectionTicket_get(?)`; - const [tickets] = await Self.rawSql(query, [collectionFk]); + const [tickets] = await Self.rawSql(query, [collectionFk], {userId}); query = `CALL vn.collectionSale_get(?)`; - const [sales] = await Self.rawSql(query, [collectionFk]); + const [sales] = await Self.rawSql(query, [collectionFk], {userId}); query = `CALL vn.collectionPlacement_get(?)`; - const [placements] = await Self.rawSql(query, [collectionFk]); + const [placements] = await Self.rawSql(query, [collectionFk], {userId}); query = `CALL vn.collectionSticker_print(?,?)`; - await Self.rawSql(query, [collectionFk, sectorFk]); + await Self.rawSql(query, [collectionFk, sectorFk], {userId}); return makeCollection(tickets, sales, placements, collectionFk); }; diff --git a/back/methods/docuware/specs/upload.spec.js b/back/methods/docuware/specs/upload.spec.js index 7ac873e95..3b8c55a50 100644 --- a/back/methods/docuware/specs/upload.spec.js +++ b/back/methods/docuware/specs/upload.spec.js @@ -2,8 +2,9 @@ const models = require('vn-loopback/server/server').models; describe('docuware upload()', () => { const userId = 9; - const ticketId = 10; + const ticketIds = [10]; const ctx = { + args: {ticketIds}, req: { getLocale: () => { return 'en'; @@ -27,7 +28,7 @@ describe('docuware upload()', () => { let error; try { - await models.Docuware.upload(ctx, ticketId, fileCabinetName); + await models.Docuware.upload(ctx, ticketIds, fileCabinetName); } catch (e) { error = e.message; } diff --git a/back/methods/docuware/upload.js b/back/methods/docuware/upload.js index ea9ee3622..096301e56 100644 --- a/back/methods/docuware/upload.js +++ b/back/methods/docuware/upload.js @@ -3,34 +3,34 @@ const axios = require('axios'); module.exports = Self => { Self.remoteMethodCtx('upload', { - description: 'Upload an docuware PDF', + description: 'Upload docuware PDFs', accessType: 'WRITE', accepts: [ { - arg: 'id', - type: 'number', - description: 'The ticket id', - http: {source: 'path'} + arg: 'ticketIds', + type: ['number'], + description: 'The ticket ids', + required: true }, { arg: 'fileCabinet', type: 'string', - description: 'The file cabinet' - }, - { - arg: 'dialog', - type: 'string', - description: 'The dialog' + description: 'The file cabinet', + required: true } ], - returns: [], + returns: { + type: 'object', + root: true + }, http: { - path: `/:id/upload`, + path: `/upload`, verb: 'POST' } }); - Self.upload = async function(ctx, id, fileCabinet) { + Self.upload = async function(ctx, ticketIds, fileCabinet) { + delete ctx.args.ticketIds; const models = Self.app.models; const action = 'store'; @@ -38,104 +38,114 @@ module.exports = Self => { const fileCabinetId = await Self.getFileCabinet(fileCabinet); const dialogId = await Self.getDialog(fileCabinet, action, fileCabinetId); - // get delivery note - const deliveryNote = await models.Ticket.deliveryNotePdf(ctx, { - id, - type: 'deliveryNote' - }); - - // get ticket data - const ticket = await models.Ticket.findById(id, { - include: [{ - relation: 'client', - scope: { - fields: ['id', 'socialName', 'fi'] - } - }] - }); - - // upload file - const templateJson = { - 'Fields': [ - { - 'FieldName': 'N__ALBAR_N', - 'ItemElementName': 'string', - 'Item': id, - }, - { - 'FieldName': 'CIF_PROVEEDOR', - 'ItemElementName': 'string', - 'Item': ticket.client().fi, - }, - { - 'FieldName': 'CODIGO_PROVEEDOR', - 'ItemElementName': 'string', - 'Item': ticket.client().id, - }, - { - 'FieldName': 'NOMBRE_PROVEEDOR', - 'ItemElementName': 'string', - 'Item': ticket.client().socialName, - }, - { - 'FieldName': 'FECHA_FACTURA', - 'ItemElementName': 'date', - 'Item': ticket.shipped, - }, - { - 'FieldName': 'TOTAL_FACTURA', - 'ItemElementName': 'Decimal', - 'Item': ticket.totalWithVat, - }, - { - 'FieldName': 'ESTADO', - 'ItemElementName': 'string', - 'Item': 'Pendiente procesar', - }, - { - 'FieldName': 'FIRMA_', - 'ItemElementName': 'string', - 'Item': 'Si', - }, - { - 'FieldName': 'FILTRO_TABLET', - 'ItemElementName': 'string', - 'Item': 'Tablet1', - } - ] - }; - - if (process.env.NODE_ENV != 'production') - throw new UserError('Action not allowed on the test environment'); - - // delete old - const docuwareFile = await models.Docuware.checkFile(ctx, id, fileCabinet, false); - if (docuwareFile) { - const deleteJson = { - 'Field': [{'FieldName': 'ESTADO', 'Item': 'Pendiente eliminar', 'ItemElementName': 'String'}] - }; - const deleteUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents/${docuwareFile.id}/Fields`; - await axios.put(deleteUri, deleteJson, options.headers); - } - - const uploadUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents?StoreDialogId=${dialogId}`; - const FormData = require('form-data'); - const data = new FormData(); - - data.append('document', JSON.stringify(templateJson), 'schema.json'); - data.append('file[]', deliveryNote[0], 'file.pdf'); - const uploadOptions = { - headers: { - 'Content-Type': 'multipart/form-data', - 'X-File-ModifiedDate': Date.vnNew(), - 'Cookie': options.headers.headers.Cookie, - ...data.getHeaders() - }, - }; - - return await axios.post(uploadUri, data, uploadOptions) - .catch(() => { - throw new UserError('Failed to upload file'); + const uploaded = []; + for (id of ticketIds) { + // get delivery note + ctx.args.id = id; + const deliveryNote = await models.Ticket.deliveryNotePdf(ctx, { + id, + type: 'deliveryNote' }); + // get ticket data + const ticket = await models.Ticket.findById(id, { + include: [{ + relation: 'client', + scope: { + fields: ['id', 'name', 'fi'] + } + }] + }); + + // upload file + const templateJson = { + 'Fields': [ + { + 'FieldName': 'N__ALBAR_N', + 'ItemElementName': 'string', + 'Item': id, + }, + { + 'FieldName': 'CIF_PROVEEDOR', + 'ItemElementName': 'string', + 'Item': ticket.client().fi, + }, + { + 'FieldName': 'CODIGO_PROVEEDOR', + 'ItemElementName': 'string', + 'Item': ticket.client().id, + }, + { + 'FieldName': 'NOMBRE_PROVEEDOR', + 'ItemElementName': 'string', + 'Item': ticket.client().name + ' - ' + id, + }, + { + 'FieldName': 'FECHA_FACTURA', + 'ItemElementName': 'date', + 'Item': ticket.shipped, + }, + { + 'FieldName': 'TOTAL_FACTURA', + 'ItemElementName': 'Decimal', + 'Item': ticket.totalWithVat, + }, + { + 'FieldName': 'ESTADO', + 'ItemElementName': 'string', + 'Item': 'Pendiente procesar', + }, + { + 'FieldName': 'FIRMA_', + 'ItemElementName': 'string', + 'Item': 'Si', + }, + { + 'FieldName': 'FILTRO_TABLET', + 'ItemElementName': 'string', + 'Item': 'Tablet1', + } + ] + }; + + if (process.env.NODE_ENV != 'production') + throw new UserError('Action not allowed on the test environment'); + + // delete old + const docuwareFile = await models.Docuware.checkFile(ctx, id, fileCabinet, false); + if (docuwareFile) { + const deleteJson = { + 'Field': [{'FieldName': 'ESTADO', 'Item': 'Pendiente eliminar', 'ItemElementName': 'String'}] + }; + const deleteUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents/${docuwareFile.id}/Fields`; + await axios.put(deleteUri, deleteJson, options.headers); + } + + const uploadUri = `${options.url}/FileCabinets/${fileCabinetId}/Documents?StoreDialogId=${dialogId}`; + const FormData = require('form-data'); + const data = new FormData(); + + data.append('document', JSON.stringify(templateJson), 'schema.json'); + data.append('file[]', deliveryNote[0], 'file.pdf'); + const uploadOptions = { + headers: { + 'Content-Type': 'multipart/form-data', + 'X-File-ModifiedDate': Date.vnNew(), + 'Cookie': options.headers.headers.Cookie, + ...data.getHeaders() + }, + }; + + try { + await axios.post(uploadUri, data, uploadOptions); + } catch (err) { + const $t = ctx.req.__; + const message = $t('Failed to upload delivery note', {id}); + if (uploaded.length) + await models.TicketTracking.setDelivered(ctx, uploaded); + throw new UserError(message); + } + uploaded.push(id); + } + return models.TicketTracking.setDelivered(ctx, ticketIds); }; }; diff --git a/back/methods/edi/updateData.js b/back/methods/edi/updateData.js index 1057be39b..10c81a795 100644 --- a/back/methods/edi/updateData.js +++ b/back/methods/edi/updateData.js @@ -3,237 +3,237 @@ const path = require('path'); const fs = require('fs-extra'); module.exports = Self => { - Self.remoteMethodCtx('updateData', { - description: 'Updates schema data from external provider', - accessType: 'WRITE', - returns: { - type: 'object', - root: true - }, - http: { - path: `/updateData`, - verb: 'POST' - } - }); + Self.remoteMethodCtx('updateData', { + description: 'Updates schema data from external provider', + accessType: 'WRITE', + returns: { + type: 'object', + root: true + }, + http: { + path: `/updateData`, + verb: 'POST' + } + }); - Self.updateData = async() => { - const models = Self.app.models; + Self.updateData = async ctx => { + const models = Self.app.models; - // Get files checksum - const tx = await Self.beginTransaction({}); + // Get files checksum + const tx = await Self.beginTransaction({}); - try { - const options = {transaction: tx}; - const files = await Self.rawSql('SELECT name, checksum, keyValue FROM edi.fileConfig', null, options); + try { + const options = {transaction: tx, userId: ctx.req.accessToken.userId}; + const files = await Self.rawSql('SELECT name, checksum, keyValue FROM edi.fileConfig', null, options); - const updatableFiles = []; - for (const file of files) { - const fileChecksum = await getChecksum(file); + const updatableFiles = []; + for (const file of files) { + const fileChecksum = await getChecksum(file); - if (file.checksum != fileChecksum) { - updatableFiles.push({ - name: file.name, - checksum: fileChecksum - }); - } else - console.debug(`File already updated, skipping...`); - } + if (file.checksum != fileChecksum) { + updatableFiles.push({ + name: file.name, + checksum: fileChecksum + }); + } else + console.debug(`File already updated, skipping...`); + } - if (updatableFiles.length === 0) - return false; + if (updatableFiles.length === 0) + return false; - // Download files - const container = await models.TempContainer.container('edi'); - const tempPath = path.join(container.client.root, container.name); + // Download files + const container = await models.TempContainer.container('edi'); + const tempPath = path.join(container.client.root, container.name); - let remoteFile; - let tempDir; - let tempFile; + let remoteFile; + let tempDir; + let tempFile; - const fileNames = updatableFiles.map(file => file.name); + const fileNames = updatableFiles.map(file => file.name); - const tables = await Self.rawSql(` + const tables = await Self.rawSql(` SELECT fileName, toTable, file FROM edi.tableConfig WHERE file IN (?)`, [fileNames], options); - for (const table of tables) { - const fileName = table.file; + for (const table of tables) { + const fileName = table.file; - remoteFile = `codes/${fileName}.ZIP`; - tempDir = `${tempPath}/${fileName}`; - tempFile = `${tempPath}/${fileName}.zip`; + remoteFile = `codes/${fileName}.ZIP`; + tempDir = `${tempPath}/${fileName}`; + tempFile = `${tempPath}/${fileName}.zip`; - try { - await fs.readFile(tempFile); - } catch (error) { - if (error.code === 'ENOENT') { - console.debug(`Downloading file ${fileName}...`); - const downloadOutput = await downloadFile(remoteFile, tempFile); - if (downloadOutput.error) - continue; - } - } + try { + await fs.readFile(tempFile); + } catch (error) { + if (error.code === 'ENOENT') { + console.debug(`Downloading file ${fileName}...`); + const downloadOutput = await downloadFile(remoteFile, tempFile); + if (downloadOutput.error) + continue; + } + } - await extractFile(fileName, tempFile, tempDir); + await extractFile(fileName, tempFile, tempDir); - console.debug(`Updating table ${table.toTable}...`); - await dumpData(tempDir, table, options); - } + console.debug(`Updating table ${table.toTable}...`); + await dumpData(tempDir, table, options); + } - // Update files checksum - for (const file of updatableFiles) { - console.log(`Updating file ${file.name} checksum...`); - await Self.rawSql(` + // Update files checksum + for (const file of updatableFiles) { + console.log(`Updating file ${file.name} checksum...`); + await Self.rawSql(` UPDATE edi.fileConfig SET checksum = ? WHERE name = ?`, - [file.checksum, file.name], options); - } + [file.checksum, file.name], options); + } - await tx.commit(); + await tx.commit(); - // Clean files - try { - console.debug(`Cleaning files...`); - await fs.remove(tempPath); - } catch (error) { - if (error.code !== 'ENOENT') - throw e; - } + // Clean files + try { + console.debug(`Cleaning files...`); + await fs.remove(tempPath); + } catch (error) { + if (error.code !== 'ENOENT') + throw e; + } - return true; - } catch (error) { - await tx.rollback(); - throw error; - } - }; + return true; + } catch (error) { + await tx.rollback(); + throw error; + } + }; - let ftpClient; - async function getFtpClient() { - if (!ftpClient) { - const [ftpConfig] = await Self.rawSql('SELECT host, user, password FROM edi.ftpConfig'); - console.debug(`Openning FTP connection to ${ftpConfig.host}...\n`); + let ftpClient; + async function getFtpClient() { + if (!ftpClient) { + const [ftpConfig] = await Self.rawSql('SELECT host, user, password FROM edi.ftpConfig'); + console.debug(`Openning FTP connection to ${ftpConfig.host}...\n`); - const FtpClient = require('ftps'); + const FtpClient = require('ftps'); - ftpClient = new FtpClient({ - host: ftpConfig.host, - username: ftpConfig.user, - password: ftpConfig.password, - procotol: 'ftp', - additionalLftpCommands: 'set ssl:verify-certificate no' - }); - } + ftpClient = new FtpClient({ + host: ftpConfig.host, + username: ftpConfig.user, + password: ftpConfig.password, + procotol: 'ftp', + additionalLftpCommands: 'set ssl:verify-certificate no' + }); + } - return ftpClient; - } + return ftpClient; + } - async function getChecksum(file) { - const ftpClient = await getFtpClient(); - console.debug(`Checking checksum for file ${file.name}...`); + async function getChecksum(file) { + const ftpClient = await getFtpClient(); + console.debug(`Checking checksum for file ${file.name}...`); - ftpClient.cat(`codes/${file.name}.TXT`); + ftpClient.cat(`codes/${file.name}.TXT`); - const response = await new Promise((resolve, reject) => { - ftpClient.exec((err, response) => { - if (err || response.error) { - console.debug(`Error downloading checksum file... ${response.error}`); - return reject(err); - } + const response = await new Promise((resolve, reject) => { + ftpClient.exec((err, response) => { + if (err || response.error) { + console.debug(`Error downloading checksum file... ${response.error}`); + return reject(err); + } - resolve(response); - }); - }); + resolve(response); + }); + }); - if (response && response.data) { - const fileContents = response.data; - const rows = fileContents.split('\n'); - const row = rows[4]; - const columns = row.split(/\s+/); + if (response && response.data) { + const fileContents = response.data; + const rows = fileContents.split('\n'); + const row = rows[4]; + const columns = row.split(/\s+/); - let fileChecksum; - if (file.keyValue) - fileChecksum = columns[1]; + let fileChecksum; + if (file.keyValue) + fileChecksum = columns[1]; - if (!file.keyValue) - fileChecksum = columns[0]; + if (!file.keyValue) + fileChecksum = columns[0]; - return fileChecksum; - } - } + return fileChecksum; + } + } - async function downloadFile(remoteFile, tempFile) { - const ftpClient = await getFtpClient(); + async function downloadFile(remoteFile, tempFile) { + const ftpClient = await getFtpClient(); - ftpClient.get(remoteFile, tempFile); + ftpClient.get(remoteFile, tempFile); - return new Promise((resolve, reject) => { - ftpClient.exec((err, response) => { - if (err || response.error) { - console.debug(`Error downloading file... ${response.error}`); - return reject(err); - } + return new Promise((resolve, reject) => { + ftpClient.exec((err, response) => { + if (err || response.error) { + console.debug(`Error downloading file... ${response.error}`); + return reject(err); + } - resolve(response); - }); - }); - } + resolve(response); + }); + }); + } - async function extractFile(fileName, tempFile, tempDir) { - const JSZip = require('jszip'); + async function extractFile(fileName, tempFile, tempDir) { + const JSZip = require('jszip'); - try { - await fs.mkdir(tempDir); - console.debug(`Extracting file ${fileName}...`); - } catch (error) { - if (error.code !== 'EEXIST') - throw e; - } + try { + await fs.mkdir(tempDir); + console.debug(`Extracting file ${fileName}...`); + } catch (error) { + if (error.code !== 'EEXIST') + throw e; + } - const fileStream = await fs.readFile(tempFile); - if (fileStream) { - const zip = new JSZip(); - const zipContents = await zip.loadAsync(fileStream); + const fileStream = await fs.readFile(tempFile); + if (fileStream) { + const zip = new JSZip(); + const zipContents = await zip.loadAsync(fileStream); - if (!zipContents) return; + if (!zipContents) return; - const fileNames = Object.keys(zipContents.files); + const fileNames = Object.keys(zipContents.files); - for (const fileName of fileNames) { - const fileContent = await zip.file(fileName).async('nodebuffer'); - const dest = path.join(tempDir, fileName); - await fs.writeFile(dest, fileContent); - } - } - } + for (const fileName of fileNames) { + const fileContent = await zip.file(fileName).async('nodebuffer'); + const dest = path.join(tempDir, fileName); + await fs.writeFile(dest, fileContent); + } + } + } - async function dumpData(tempDir, table, options) { - const toTable = table.toTable; - const baseName = table.fileName; + async function dumpData(tempDir, table, options) { + const toTable = table.toTable; + const baseName = table.fileName; - console.log(`Emptying table ${toTable}...`); - const tableName = `edi.${toTable}`; - await Self.rawSql(`DELETE FROM ??`, [tableName]); + console.log(`Emptying table ${toTable}...`); + const tableName = `edi.${toTable}`; + await Self.rawSql(`DELETE FROM ??`, [tableName]); - const dirFiles = await fs.readdir(tempDir); - const files = dirFiles.filter(file => file.startsWith(baseName)); + const dirFiles = await fs.readdir(tempDir); + const files = dirFiles.filter(file => file.startsWith(baseName)); - for (const file of files) { - console.log(`Dumping data from file ${file}...`); + for (const file of files) { + console.log(`Dumping data from file ${file}...`); - const templatePath = path.join(__dirname, `./sql/${toTable}.sql`); - const sqlTemplate = await fs.readFile(templatePath, 'utf8'); - const filePath = path.join(tempDir, file); + const templatePath = path.join(__dirname, `./sql/${toTable}.sql`); + const sqlTemplate = await fs.readFile(templatePath, 'utf8'); + const filePath = path.join(tempDir, file); - await Self.rawSql(sqlTemplate, [filePath], options); - await Self.rawSql(` + await Self.rawSql(sqlTemplate, [filePath], options); + await Self.rawSql(` UPDATE edi.tableConfig SET updated = ? WHERE fileName = ? `, [Date.vnNew(), baseName], options); - } + } - console.log(`Updated table ${toTable}\n`); - } + console.log(`Updated table ${toTable}\n`); + } }; diff --git a/back/methods/vn-user/recover-password.js b/back/methods/vn-user/recover-password.js index 34f5dd545..b87bb14d4 100644 --- a/back/methods/vn-user/recover-password.js +++ b/back/methods/vn-user/recover-password.js @@ -24,6 +24,7 @@ module.exports = Self => { fields: ['email'], where: {name: user} }); + if (!account) return; user = account.email; } diff --git a/back/methods/vn-user/renew-token.js b/back/methods/vn-user/renew-token.js new file mode 100644 index 000000000..1f3532bd6 --- /dev/null +++ b/back/methods/vn-user/renew-token.js @@ -0,0 +1,38 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('renewToken', { + description: 'Checks if the token has more than renewPeriod seconds to live and if so, renews it', + accepts: [], + returns: { + type: 'Object', + root: true + }, + http: { + path: `/renewToken`, + verb: 'POST' + } + }); + + Self.renewToken = async function(ctx) { + const models = Self.app.models; + const userId = ctx.req.accessToken.userId; + const created = ctx.req.accessToken.created; + const tokenId = ctx.req.accessToken.id; + + const now = new Date(); + const differenceMilliseconds = now - created; + const differenceSeconds = Math.floor(differenceMilliseconds / 1000); + + const accessTokenConfig = await models.AccessTokenConfig.findOne({fields: ['renewPeriod']}); + + if (differenceSeconds <= accessTokenConfig.renewPeriod) + throw new UserError(`The renew period has not been exceeded`); + + await Self.logout(tokenId); + const user = await Self.findById(userId); + const accessToken = await user.createAccessToken(); + + return {token: accessToken.id, created: accessToken.created}; + }; +}; diff --git a/back/methods/vn-user/signIn.js b/back/methods/vn-user/signIn.js index da3172ae4..c98f1da54 100644 --- a/back/methods/vn-user/signIn.js +++ b/back/methods/vn-user/signIn.js @@ -27,33 +27,46 @@ module.exports = Self => { }); Self.signIn = async function(user, password) { - let models = Self.app.models; + const models = Self.app.models; + const usesEmail = user.indexOf('@') !== -1; let token; - let usesEmail = user.indexOf('@') !== -1; - let userInfo = usesEmail + const userInfo = usesEmail ? {email: user} : {username: user}; - let instance = await Self.findOne({ + const instance = await Self.findOne({ fields: ['username', 'password'], where: userInfo }); - let where = usesEmail + const where = usesEmail ? {email: user} : {name: user}; - let vnUser = await Self.findOne({ - fields: ['active'], + const vnUser = await Self.findOne({ + fields: ['id', 'active', 'passExpired'], where }); - let validCredentials = instance + const today = Date.vnNew(); + today.setHours(0, 0, 0, 0); + + const validCredentials = instance && await instance.hasPassword(password); if (validCredentials) { if (!vnUser.active) throw new UserError('User disabled'); + if (vnUser.passExpired && vnUser.passExpired.getTime() <= today.getTime()) { + const changePasswordToken = await models.AccessToken.create({ + scopes: ['change-password'], + userId: vnUser.id + }); + const err = new UserError('Pass expired', 'passExpired'); + err.details = {token: changePasswordToken}; + throw err; + } + try { await models.Account.sync(instance.username, password); } catch (err) { @@ -63,6 +76,6 @@ module.exports = Self => { let loginInfo = Object.assign({password}, userInfo); token = await Self.login(loginInfo, 'user'); - return {token: token.id}; + return {token: token.id, created: token.created}; }; }; diff --git a/back/methods/vn-user/specs/signIn.js b/back/methods/vn-user/specs/signIn.spec.js similarity index 91% rename from back/methods/vn-user/specs/signIn.js rename to back/methods/vn-user/specs/signIn.spec.js index b46c645d6..c3f4630c6 100644 --- a/back/methods/vn-user/specs/signIn.js +++ b/back/methods/vn-user/specs/signIn.spec.js @@ -9,7 +9,7 @@ describe('VnUser signIn()', () => { expect(login.token).toBeDefined(); - await models.VnUser.signOut(ctx); + await models.VnUser.logout(ctx.req.accessToken.id); }); it('should return the token if the user doesnt exist but the client does', async() => { @@ -19,7 +19,7 @@ describe('VnUser signIn()', () => { expect(login.token).toBeDefined(); - await models.VnUser.signOut(ctx); + await models.VnUser.logout(ctx.req.accessToken.id); }); }); diff --git a/back/methods/vn-user/specs/signOut.js b/back/methods/vn-user/specs/signOut.js deleted file mode 100644 index c84e86f05..000000000 --- a/back/methods/vn-user/specs/signOut.js +++ /dev/null @@ -1,42 +0,0 @@ -const {models} = require('vn-loopback/server/server'); - -describe('VnUser signOut()', () => { - it('should logout and remove token after valid login', async() => { - let loginResponse = await models.VnUser.signOut('buyer', 'nightmare'); - let accessToken = await models.AccessToken.findById(loginResponse.token); - let ctx = {req: {accessToken: accessToken}}; - - let logoutResponse = await models.VnUser.signOut(ctx); - let tokenAfterLogout = await models.AccessToken.findById(loginResponse.token); - - expect(logoutResponse).toBeTrue(); - expect(tokenAfterLogout).toBeNull(); - }); - - it('should throw a 401 error when token is invalid', async() => { - let error; - let ctx = {req: {accessToken: {id: 'invalidToken'}}}; - - try { - response = await models.VnUser.signOut(ctx); - } catch (e) { - error = e; - } - - expect(error).toBeDefined(); - expect(error.statusCode).toBe(401); - }); - - it('should throw an error when no token is passed', async() => { - let error; - let ctx = {req: {accessToken: null}}; - - try { - response = await models.VnUser.signOut(ctx); - } catch (e) { - error = e; - } - - expect(error).toBeDefined(); - }); -}); diff --git a/back/model-config.json b/back/model-config.json index ff2bf5850..d945f3250 100644 --- a/back/model-config.json +++ b/back/model-config.json @@ -2,6 +2,14 @@ "AccountingType": { "dataSource": "vn" }, + "AccessTokenConfig": { + "dataSource": "vn", + "options": { + "mysql": { + "table": "salix.accessTokenConfig" + } + } + }, "Bank": { "dataSource": "vn" }, diff --git a/back/models/access-token-config.json b/back/models/access-token-config.json new file mode 100644 index 000000000..6d90a0f4d --- /dev/null +++ b/back/models/access-token-config.json @@ -0,0 +1,30 @@ +{ + "name": "AccessTokenConfig", + "base": "VnModel", + "options": { + "mysql": { + "table": "accessTokenConfig" + } + }, + "properties": { + "id": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "renewPeriod": { + "type": "number", + "required": true + }, + "renewInterval": { + "type": "number", + "required": true + } + }, + "acls": [{ + "accessType": "READ", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + }] +} diff --git a/back/models/vn-user.js b/back/models/vn-user.js index 84ba11794..b58395acc 100644 --- a/back/models/vn-user.js +++ b/back/models/vn-user.js @@ -10,6 +10,9 @@ module.exports = function(Self) { require('../methods/vn-user/recover-password')(Self); require('../methods/vn-user/validate-token')(Self); require('../methods/vn-user/privileges')(Self); + require('../methods/vn-user/renew-token')(Self); + + Self.definition.settings.acls = Self.definition.settings.acls.filter(acl => acl.property !== 'create'); // Validations @@ -107,4 +110,81 @@ module.exports = function(Self) { return email.send(); }); + + const _setPassword = Self.prototype.setPassword; + Self.prototype.setPassword = async function(newPassword, options, cb) { + if (cb === undefined && typeof options === 'function') { + cb = options; + options = undefined; + } + + const myOptions = {}; + let tx; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + options = myOptions; + + try { + await Self.rawSql(`CALL account.user_checkPassword(?)`, [newPassword], options); + await _setPassword.call(this, newPassword, options); + await this.updateAttribute('passExpired', null, options); + await Self.app.models.Account.sync(this.name, newPassword, null, options); + tx && await tx.commit(); + cb && cb(); + } catch (err) { + tx && await tx.rollback(); + if (cb) cb(err); else throw err; + } + }; + + Self.sharedClass._methods.find(method => method.name == 'changePassword') + .accessScopes = ['change-password']; + + // FIXME: https://redmine.verdnatura.es/issues/5761 + // Self.afterRemote('prototype.patchAttributes', async(ctx, instance) => { + // if (!ctx.args || !ctx.args.data.email) return; + + // const loopBackContext = LoopBackContext.getCurrentContext(); + // const httpCtx = {req: loopBackContext.active}; + // const httpRequest = httpCtx.req.http.req; + // const headers = httpRequest.headers; + // const origin = headers.origin; + // const url = origin.split(':'); + + // class Mailer { + // async send(verifyOptions, cb) { + // const params = { + // url: verifyOptions.verifyHref, + // recipient: verifyOptions.to, + // lang: ctx.req.getLocale() + // }; + + // const email = new Email('email-verify', params); + // email.send(); + + // cb(null, verifyOptions.to); + // } + // } + + // const options = { + // type: 'email', + // to: instance.email, + // from: {}, + // redirect: `${origin}/#!/account/${instance.id}/basic-data?emailConfirmed`, + // template: false, + // mailer: new Mailer, + // host: url[1].split('/')[2], + // port: url[2], + // protocol: url[0], + // user: Self + // }; + + // await instance.verify(options); + // }); }; diff --git a/back/models/vn-user.json b/back/models/vn-user.json index 17efc8ce6..61e42f77a 100644 --- a/back/models/vn-user.json +++ b/back/models/vn-user.json @@ -25,10 +25,7 @@ }, "password": { "type": "string", - "required": true, - "mysql": { - "columnName": "bcryptPassword" - } + "required": true }, "roleFk": { "type": "number", @@ -42,9 +39,6 @@ "lang": { "type": "string" }, - "bcryptPassword": { - "type": "string" - }, "active": { "type": "boolean" }, @@ -62,7 +56,10 @@ }, "hasGrant": { "type": "boolean" - } + }, + "passExpired": { + "type": "date" + } }, "relations": { "role": { @@ -121,5 +118,24 @@ "principalId": "$authenticated", "permission": "ALLOW" } - ] + ], + "scopes": { + "preview": { + "fields": [ + "id", + "name", + "username", + "roleFk", + "nickname", + "lang", + "active", + "created", + "updated", + "image", + "hasGrant", + "realm", + "email" + ] + } + } } diff --git a/back/process.yml b/back/process.yml index 38d2b9eaf..a29323240 100644 --- a/back/process.yml +++ b/back/process.yml @@ -4,4 +4,4 @@ apps: instances: 1 max_restarts: 3 restart_delay: 15000 - node_args: --tls-min-v1.0 + node_args: --tls-min-v1.0 --openssl-legacy-provider diff --git a/db/Dockerfile b/db/Dockerfile index 053fbcee6..448b8b03a 100644 --- a/db/Dockerfile +++ b/db/Dockerfile @@ -1,26 +1,18 @@ -FROM mariadb:10.7.5 +FROM mariadb:10.7.7 ENV MYSQL_ROOT_PASSWORD root ENV TZ Europe/Madrid ARG MOCKDATE=2001-01-01 11:00:00 ARG DEBIAN_FRONTEND=noninteractive -RUN apt-get update \ - && apt-get install -y --no-install-recommends curl ca-certificates \ - && curl -sL https://apt.verdnatura.es/conf/verdnatura.gpg | apt-key add - \ - && echo "deb http://apt.verdnatura.es/ jessie main" > /etc/apt/sources.list.d/vn.list \ - && apt-get update \ - && apt-get install -y vn-mariadb \ - && apt-get purge -y --auto-remove curl ca-certificates \ - && rm -rf /var/lib/apt/lists/* - COPY docker/docker.cnf /etc/mysql/conf.d/ COPY \ + docker/docker-start.sh \ docker/docker-init.sh \ - docker/docker-temp-start.sh \ docker/docker-temp-stop.sh \ docker/docker-dump.sh \ - docker/docker-start.sh \ + docker/docker-structure.sh \ + docker/docker-fixtures.sh \ /usr/local/bin/ RUN mkdir /mysql-data \ @@ -31,26 +23,16 @@ WORKDIR /docker-boot COPY \ import-changes.sh \ config.ini \ - dump/mysqlPlugins.sql \ dump/structure.sql \ dump/mockDate.sql \ dump/dumpedFixtures.sql \ ./ -RUN gosu mysql docker-init.sh \ - && docker-dump.sh mysqlPlugins \ - && docker-dump.sh structure \ - && sed -i -e 's/@mockDate/'"$MOCKDATE"'/g' mockDate.sql \ - && docker-dump.sh mockDate \ - && docker-dump.sh dumpedFixtures \ - && gosu mysql docker-temp-stop.sh - +RUN sed -i -e 's/@mockDate/'"$MOCKDATE"'/g' mockDate.sql \ + && gosu mysql docker-structure.sh COPY changes ./changes COPY dump/fixtures.sql ./ ARG STAMP=unknown -RUN gosu mysql docker-temp-start.sh \ - && ./import-changes.sh \ - && docker-dump.sh fixtures \ - && gosu mysql docker-temp-stop.sh +RUN gosu mysql docker-fixtures.sh RUN echo "[INFO] -> Import finished" \ && rm -rf /docker-boot diff --git a/db/changes/231801/00-userAcl.sql b/db/changes/231801/00-userAcl.sql index 64803bf18..9eb3ebf28 100644 --- a/db/changes/231801/00-userAcl.sql +++ b/db/changes/231801/00-userAcl.sql @@ -1,6 +1,5 @@ INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) VALUES - ('VnUser', '*', '*', 'ALLOW', 'ROLE', 'employee'), ('VnUser','acl','READ','ALLOW','ROLE','account'), ('VnUser','getCurrentUserData','READ','ALLOW','ROLE','account'), ('VnUser','changePassword', 'WRITE', 'ALLOW', 'ROLE', 'account'), diff --git a/db/changes/232401/00-userPassExpired.sql b/db/changes/232401/00-userPassExpired.sql new file mode 100644 index 000000000..2d4b45937 --- /dev/null +++ b/db/changes/232401/00-userPassExpired.sql @@ -0,0 +1,76 @@ +ALTER TABLE `account`.`user` ADD passExpired DATE DEFAULT NULL; + +DROP PROCEDURE `account`.`myUser_changePassword`; +DROP PROCEDURE `account`.`myUser_restorePassword`; +DROP PROCEDURE `account`.`user_changePassword`; +DROP PROCEDURE `account`.`user_restorePassword`; +DROP PROCEDURE `account`.`user_setPassword`; + +ALTER TABLE account.`user` CHANGE password password__ char(64) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NOT NULL COMMENT 'Deprecated'; +ALTER TABLE account.`user` CHANGE bcryptPassword password varchar(512) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci DEFAULT NULL NULL; + +DELIMITER $$ +$$ +CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `account`.`user_beforeUpdate` + BEFORE UPDATE ON `user` + FOR EACH ROW +BEGIN + SET NEW.editorFk = account.myUser_getId(); + + IF !(NEW.`name` <=> OLD.`name`) THEN + CALL user_checkName (NEW.`name`); + END IF; + + IF !(NEW.`password` <=> OLD.`password`) THEN + SET NEW.lastPassChange = util.VN_NOW(); + END IF; +END$$ +DELIMITER ; + +CREATE OR REPLACE DEFINER=`root`@`localhost` + SQL SECURITY DEFINER + VIEW `account`.`accountDovecot` AS +select + `u`.`name` AS `name`, + `u`.`password` AS `password` +from + (`account`.`user` `u` +join `account`.`account` `a` on + (`a`.`id` = `u`.`id`)) +where + `u`.`active` <> 0; + +CREATE OR REPLACE DEFINER=`root`@`localhost` + SQL SECURITY DEFINER + VIEW `salix`.`User` AS +select + `account`.`user`.`id` AS `id`, + `account`.`user`.`realm` AS `realm`, + `account`.`user`.`name` AS `username`, + `account`.`user`.`password` AS `password`, + `account`.`user`.`email` AS `email`, + `account`.`user`.`emailVerified` AS `emailVerified`, + `account`.`user`.`verificationToken` AS `verificationToken` +from + `account`.`user`; + +CREATE OR REPLACE DEFINER=`root`@`localhost` + SQL SECURITY DEFINER + VIEW `vn`.`workerTimeControlUserInfo` AS +select + `u`.`id` AS `userFk`, + `w`.`firstName` AS `name`, + `w`.`lastName` AS `surname`, + `u`.`name` AS `user`, + `u`.`password` AS `password`, + `wd`.`departmentFk` AS `departmentFk`, + left(`c`.`fi`, + 8) AS `dni` +from + (((`account`.`user` `u` +join `vn`.`worker` `w` on + (`w`.`userFk` = `u`.`id`)) +join `vn`.`client` `c` on + (`c`.`id` = `u`.`id`)) +left join `vn`.`workerDepartment` `wd` on + (`wd`.`workerFk` = `w`.`id`)); diff --git a/db/changes/232601/.gitkeep b/db/changes/232601/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/db/changes/232601/00-aclAccount.sql b/db/changes/232601/00-aclAccount.sql new file mode 100644 index 000000000..bf8106b98 --- /dev/null +++ b/db/changes/232601/00-aclAccount.sql @@ -0,0 +1,8 @@ +DELETE + FROM `salix`.`ACL` + WHERE model='Account' AND property='*' AND accessType='*'; + +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) + VALUES + ('Account', '*', 'WRITE', 'ALLOW', 'ROLE', 'sysadmin'), + ('Account', '*', 'READ', 'ALLOW', 'ROLE', 'employee'); diff --git a/db/changes/232601/00-aclMailAliasAccount.sql b/db/changes/232601/00-aclMailAliasAccount.sql new file mode 100644 index 000000000..619e9bb6e --- /dev/null +++ b/db/changes/232601/00-aclMailAliasAccount.sql @@ -0,0 +1,5 @@ +DELETE FROM `salix`.`ACL` WHERE model = 'MailAliasAccount'; +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('MailAliasAccount', '*', 'READ', 'ALLOW', 'ROLE', 'employee'), + ('MailAliasAccount', '*', 'WRITE', 'ALLOW', 'ROLE', 'itManagement'); diff --git a/db/changes/232601/00-aclMailForward.sql b/db/changes/232601/00-aclMailForward.sql new file mode 100644 index 000000000..afe2acec8 --- /dev/null +++ b/db/changes/232601/00-aclMailForward.sql @@ -0,0 +1,5 @@ +DELETE FROM `salix`.`ACL` WHERE model = 'MailForward'; +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('MailForward', '*', 'READ', 'ALLOW', 'ROLE', 'employee'), + ('MailForward', '*', 'WRITE', 'ALLOW', 'ROLE', 'itManagement'); diff --git a/db/changes/232601/00-aclRole.sql b/db/changes/232601/00-aclRole.sql new file mode 100644 index 000000000..e16f052be --- /dev/null +++ b/db/changes/232601/00-aclRole.sql @@ -0,0 +1,5 @@ +DELETE FROM `salix`.`ACL` WHERE model = 'Role'; +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('Role', '*', 'READ', 'ALLOW', 'ROLE', 'employee'), + ('Role', '*', 'WRITE', 'ALLOW', 'ROLE', 'it'); diff --git a/db/changes/232601/00-aclVnUser.sql b/db/changes/232601/00-aclVnUser.sql new file mode 100644 index 000000000..39fa2cb14 --- /dev/null +++ b/db/changes/232601/00-aclVnUser.sql @@ -0,0 +1,10 @@ +DELETE + FROM `salix`.`ACL` + WHERE model = 'VnUser' AND property = '*' AND principalId = 'employee'; + +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) + VALUES + ('VnUser', '*', '*', 'ALLOW', 'ROLE', 'itManagement'), + ('VnUser', '__get__preview', 'READ', 'ALLOW', 'ROLE', 'employee'), + ('VnUser', 'preview', '*', 'ALLOW', 'ROLE', 'employee'), + ('VnUser', 'create', '*', 'ALLOW', 'ROLE', 'itManagement'); diff --git a/db/changes/232601/00-entry_updateComission.sql b/db/changes/232601/00-entry_updateComission.sql new file mode 100644 index 000000000..5a25d72e8 --- /dev/null +++ b/db/changes/232601/00-entry_updateComission.sql @@ -0,0 +1,40 @@ +DELIMITER $$ +$$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`entry_updateComission`(vCurrency INT) +BEGIN +/** + * Actualiza la comision de las entradas de hoy a futuro y las recalcula + * + * @param vCurrency id del tipo de moneda(SAR,EUR,USD,GBP,JPY) + */ + DECLARE vCurrencyName VARCHAR(25); + DECLARE vComission INT; + + CREATE OR REPLACE TEMPORARY TABLE tmp.recalcEntryCommision + SELECT e.id + FROM vn.entry e + JOIN vn.travel t ON t.id = e.travelFk + JOIN vn.warehouse w ON w.id = t.warehouseInFk + WHERE t.shipped >= util.VN_CURDATE() + AND e.currencyFk = vCurrency; + + SET vComission = currency_getCommission(vCurrency); + + UPDATE vn.entry e + JOIN tmp.recalcEntryCommision tmp ON tmp.id = e.id + SET e.commission = vComission; + + SELECT `name` INTO vCurrencyName + FROM currency + WHERE id = vCurrency; + + CALL entry_recalc(); + SELECT util.notification_send( + 'entry-update-comission', + JSON_OBJECT('currencyName', vCurrencyName, 'referenceCurrent', vComission), + account.myUser_getId() + ); + + DROP TEMPORARY TABLE tmp.recalcEntryCommision; +END$$ +DELIMITER ; \ No newline at end of file diff --git a/db/changes/232601/00-packingSiteAdvanced.sql b/db/changes/232601/00-packingSiteAdvanced.sql new file mode 100644 index 000000000..0e33744cd --- /dev/null +++ b/db/changes/232601/00-packingSiteAdvanced.sql @@ -0,0 +1,71 @@ +CREATE TABLE `vn`.`packingSiteAdvanced` ( + `ticketFk` int(11), + `workerFk` int(10) unsigned, + PRIMARY KEY (`ticketFk`), + KEY `packingSiteAdvanced_FK_1` (`workerFk`), + CONSTRAINT `packingSiteAdvanced_FK` FOREIGN KEY (`ticketFk`) REFERENCES `ticket` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `packingSiteAdvanced_FK_1` FOREIGN KEY (`workerFk`) REFERENCES `worker` (`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 + ('PackingSiteAdvanced', '*', '*', 'ALLOW', 'ROLE', 'production'); + + DROP PROCEDURE IF EXISTS `vn`.`packingSite_startCollection`; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`packingSite_startCollection`(vSelf INT, vTicketFk INT) +proc: BEGIN +/** + * @param vSelf packingSite id + * @param vTicketFk A ticket id from the collection to start + */ + DECLARE vExists BOOL; + DECLARE vIsAdvanced BOOL; + DECLARE vNewCollectionFk INT; + DECLARE vOldCollectionFk INT; + DECLARE vIsPackingByOther BOOL; + + SELECT id, collectionFk + INTO vExists, vOldCollectionFk + FROM packingSite + WHERE id = vSelf; + + IF NOT vExists THEN + CALL util.throw('packingSiteNotExists'); + END IF; + + SELECT COUNT(*) > 0 + INTO vIsAdvanced + FROM packingSiteAdvanced + WHERE ticketFk = vTicketFk; + + IF vIsAdvanced THEN + LEAVE proc; + END IF; + + SELECT collectionFk INTO vNewCollectionFk + FROM ticketCollection WHERE ticketFk = vTicketFk; + + IF vOldCollectionFk IS NOT NULL + AND vOldCollectionFk <> vNewCollectionFk THEN + SELECT COUNT(*) > 0 + INTO vIsPackingByOther + FROM packingSite + WHERE id <> vSelf + AND collectionFk = vOldCollectionFk; + + IF NOT vIsPackingByOther AND NOT collection_isPacked(vOldCollectionFk) THEN + CALL util.throw('cannotChangeCollection'); + END IF; + END IF; + + UPDATE packingSite SET collectionFk = vNewCollectionFk + WHERE id = vSelf; +END$$ +DELIMITER ; + + + diff --git a/db/changes/232601/00-salix.sql b/db/changes/232601/00-salix.sql new file mode 100644 index 000000000..dc1ed69be --- /dev/null +++ b/db/changes/232601/00-salix.sql @@ -0,0 +1,10 @@ +CREATE TABLE `salix`.`accessTokenConfig` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `renewPeriod` int(10) unsigned DEFAULT NULL, + `renewInterval` int(10) unsigned DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; + +INSERT IGNORE INTO `salix`.`accessTokenConfig` (`id`, `renewPeriod`, `renewInterval`) + VALUES + (1, 21600, 300); diff --git a/db/docker/docker-temp-start.sh b/db/docker/docker-fixtures.sh similarity index 62% rename from db/docker/docker-temp-start.sh rename to db/docker/docker-fixtures.sh index fc067102f..0de6114d9 100755 --- a/db/docker/docker-temp-start.sh +++ b/db/docker/docker-fixtures.sh @@ -5,3 +5,6 @@ CMD=mysqld docker_setup_env "$CMD" docker_temp_server_start "$CMD" +bash import-changes.sh +docker-dump.sh fixtures +docker_temp_server_stop diff --git a/db/docker/docker-structure.sh b/db/docker/docker-structure.sh new file mode 100755 index 000000000..75a1c24f3 --- /dev/null +++ b/db/docker/docker-structure.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +. docker-init.sh +docker-dump.sh structure +docker-dump.sh mockDate +docker-dump.sh dumpedFixtures +. docker-temp-stop.sh diff --git a/db/docker/docker-temp-stop.sh b/db/docker/docker-temp-stop.sh old mode 100755 new mode 100644 diff --git a/db/dump/dumpedFixtures.sql b/db/dump/dumpedFixtures.sql index abb5516f7..2513972db 100644 --- a/db/dump/dumpedFixtures.sql +++ b/db/dump/dumpedFixtures.sql @@ -782,6 +782,38 @@ USE `sage`; -- Dumping data for table `TiposIva` -- +LOCK TABLES `taxType` WRITE; +/*!40000 ALTER TABLE `taxType` DISABLE KEYS */; +INSERT INTO `sage`.`taxType` (id, code, isIntracommunity) VALUES + (2, NULL, 0), + (4, 'national4', 0), + (5, NULL, 0), + (6, NULL, 1), + (7, NULL, 1), + (8, NULL, 1), + (10, 'national10', 0), + (11, NULL, 0), + (16, 'CEEServices21', 1), + (18, NULL, 0), + (20, 'national0', 0), + (21, 'national21', 0), + (22, 'import10', 0), + (26, NULL, 0), + (90, 'import21', 0), + (91, NULL, 0), + (92, NULL, 0), + (93, NULL, 0), + (94, NULL, 0), + (100, NULL, 0), + (108, NULL, 0), + (109, NULL, 0), + (110, NULL, 1), + (111, NULL, 0), + (112, NULL, 0), + (113, 'ISP21', 0), + (114, NULL, 0), + (115, 'import4', 0); + LOCK TABLES `TiposIva` WRITE; /*!40000 ALTER TABLE `TiposIva` DISABLE KEYS */; INSERT INTO `TiposIva` VALUES (2,0,'Operaciones no sujetas',0.0000000000,0.0000000000,0.0000000000,'','4770000020','','','','','','','95B21A93-5910-489D-83BB-C32788C9B19D','','','','','','','','','',0,0),(4,0,'I.V.A. 4%',0.0000000000,4.0000000000,0.0000000000,'4720000004','4770000004','','6310000000','','','','','9E6160D5-984E-4643-ACBC-1EBC3BF73360','','','','','','','','','',0,0),(5,0,'I.V.A. 4% y R.E. 0.5%',0.0000000000,4.0000000000,0.5000000000,'','4770000504','4770000405','','','','','','DBEFA562-63FB-4FFC-8171-64F0C6F065FF','','','','','','','','','',0,0),(6,0,'H.P. IVA 4% CEE',0.0000000000,4.0000000000,0.0000000000,'4721000004','4771000004','','','','','','','DD0ECBA8-2EF5-425E-911B-623580BADA77','','','','','','','','','',0,1),(7,0,'H.P. IVA 10% CEE',0.0000000000,10.0000000000,0.0000000000,'4721000011','4771000010','','','','','','','593208CD-6F28-4489-B6EC-907AD689EAC9','','','','','','','','','',0,1),(8,0,'H.P. IVA 21% CEE',0.0000000000,21.0000000000,0.0000000000,'4721000021','4771000021','','','','','','','27061852-9BC1-4C4F-9B6E-69970E208F23','','','','','','','','','',0,1),(10,0,'I.V.A. 10% Nacional',0.0000000000,10.0000000000,0.0000000000,'4720000011','4770000010','','6290000553','','','','','828A9D6F-5C01-4C3A-918A-B2E4482830D3','','','','','','','','','',0,0),(11,0,'I.V.A. 10% y R.E. 1,4%',0.0000000000,10.0000000000,1.4000000000,'','4770000101','4770000110','','','','','','C1F2D910-83A1-4191-A76C-8B3D7AB98348','','','','','','','','','',0,0),(16,0,'I.V.A. Adqui. servicios CEE',0.0000000000,21.0000000000,0.0000000000,'4721000015','4771000016','','','','','','','E3EDE961-CE8F-41D4-9E6C-D8BCD32275A1','','','','','','','','','',0,1),(18,0,'H.P. Iva Importación 0% ISP',0.0000000000,0.0000000000,0.0000000000,'4720000005','4770000005','','','','','','','27AD4158-2349-49C2-B53A-A4E0EFAC5D09','','','','','','','','','',0,0),(20,0,'I.V.A 0% Nacional',0.0000000000,0.0000000000,0.0000000000,'4720000000','','','','','','','','B90B0FBD-E513-4F04-9721-C873504E08DF','','','','','','','','','',0,0),(21,0,'I.V.A. 21%',0.0000000000,21.0000000000,0.0000000000,'4720000021','4770000021','4770000000','','','','','','BA8C4E28-DCFA-4F7B-AE4F-CA044626B55E','','','','','','','','','',0,0),(22,0,'IVA 10% importaciones',0.0000000000,10.0000000000,0.0000000000,'4722000010','','','','','','','','540450A8-4B41-4607-96D1-E7F296FB6933','','','','','','','','','',0,0),(26,0,'I.V.A. 21% y R.E. 5,2%',0.0000000000,21.0000000000,5.2000000000,'4720000021','4770000215','4770000521','631000000','','','','','2BC0765F-7739-49AE-A5F0-28B648B81677','','','','','','','','','',0,0),(90,0,'IVA 21% importaciones',0.0000000000,21.0000000000,0.0000000000,'4722000021','','','','','','','','EB675F91-5FF2-4E26-A31E-EEB674125945','','','','','','','','','',0,0),(91,0,'IVA 0% importaciones',0.0000000000,0.0000000000,0.0000000000,'4723000000','','','','','','','','5E5EFA56-2A99-4D54-A16B-5D818274CA18','','','','','','','','','',0,0),(92,0,'8.5% comp. ganadera o pesquera',0.0000000000,8.5000000000,0.0000000000,'4720000000','4770000000','477000000','631000000','','','','','','','','','','','','','','',0,0),(93,0,'12% com. agrícola o forestal',0.0000000000,12.0000000000,0.0000000000,'4720000012','','','','','','','','267B1DDB-247F-4A71-AB95-3349FEFC5F92','','','','','','','','','',0,0),(94,0,'10,5% com. ganadera o pesquera',0.0000000000,10.5000000000,0.0000000000,'4770000000','4720000000','631000000','477000000','','','','','','','','','','','','','','',0,0),(100,0,'HP IVA SOPORTADO 5%',0.0000000000,5.0000000000,0.0000000000,'4720000055','','','','','','','','3AD36CB2-4172-4CC9-9F87-2BF2B56AAC80','','','','','','','','','',0,0),(108,0,'I.V.A. 8%',0.0000000000,8.0000000000,0.0000000000,'4720000000','4770000000','477000000','631000000','','','','','','','','','','','','','','',0,0),(109,0,'I.V.A. 8% y R.E. 1%',0.0000000000,8.0000000000,1.0000000000,'4720000000','4770000000','477000000','631000000','','','','','','','','','','','','','','',0,0),(110,0,'HP IVA Devengado Exento CEE',0.0000000000,0.0000000000,0.0000000000,'','4771000000','','','','','','','C605BC32-E161-42FD-83F3-3A66B1FBE399','','','','','','','','','',0,1),(111,0,'H.P. Iva Devengado Exento Ser',0.0000000000,0.0000000000,0.0000000000,'','4771000001','','','','','','','F1AEC4DC-AFE5-498E-A713-2648FFB6DA32','','','','','','','','','',0,0),(112,0,'H.P. IVA Devengado en exportac',0.0000000000,0.0000000000,0.0000000000,'','4770000002','','','','','','','F980AE74-BF75-4F4C-927F-0CCCE0DB8D15','','','','','','','','','',0,0),(113,0,'HP DEVENGADO 21 ISP ',0.0000000000,21.0000000000,0.0000000000,'4720000006','4770000006','','','','','','','728D7A76-E936-438C-AF05-3CA38FE16EA5','','','','','','','','','',0,0),(114,0,'HP.IVA NO DEDUCIBLE 10%',0.0000000000,0.0000000000,0.0000000000,'4720000026','','','','','','','','','','','','','','','','','',0,0),(115,0,'H.P. IVA Soportado Impor 4% ',0.0000000000,4.0000000000,0.0000000000,'4722000004','','','','','','','','','','','','','','','','','',0,0); diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 3476bfe48..9c930222f 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -71,8 +71,8 @@ INSERT INTO `account`.`roleConfig`(`id`, `mysqlPassword`, `rolePrefix`, `userPre CALL `account`.`role_sync`; -INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `password`,`role`,`active`,`email`, `lang`, `image`, `bcryptPassword`) - SELECT id, name, CONCAT(name, 'Nick'),MD5('nightmare'), id, 1, CONCAT(name, '@mydomain.com'), 'en', '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2' +INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `role`,`active`,`email`, `lang`, `image`, `password`) + SELECT id, name, CONCAT(name, 'Nick'), id, 1, CONCAT(name, '@mydomain.com'), 'en', '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2' FROM `account`.`role` WHERE id <> 20 ORDER BY id; @@ -98,20 +98,24 @@ INSERT INTO `hedera`.`tpvConfig`(`id`, `currency`, `terminal`, `transactionType` VALUES (1, 978, 1, 0, 2000, 9, 0); -INSERT INTO `account`.`user`(`id`,`name`,`nickname`, `bcryptPassword`, `password`,`role`,`active`,`email`,`lang`, `image`) +INSERT INTO `account`.`user`(`id`,`name`,`nickname`, `password`,`role`,`active`,`email`,`lang`, `image`) VALUES - (1101, 'BruceWayne', 'Bruce Wayne', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'BruceWayne@mydomain.com', 'es', 'e7723f0b24ff05b32ed09d95196f2f29'), - (1102, 'PetterParker', 'Petter Parker', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'PetterParker@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'), - (1103, 'ClarkKent', 'Clark Kent', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'ClarkKent@mydomain.com', 'fr', 'e7723f0b24ff05b32ed09d95196f2f29'), - (1104, 'TonyStark', 'Tony Stark', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'TonyStark@mydomain.com', 'es', 'e7723f0b24ff05b32ed09d95196f2f29'), - (1105, 'MaxEisenhardt', 'Max Eisenhardt', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 2, 1, 'MaxEisenhardt@mydomain.com', 'pt', 'e7723f0b24ff05b32ed09d95196f2f29'), - (1106, 'DavidCharlesHaller', 'David Charles Haller', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'DavidCharlesHaller@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'), - (1107, 'HankPym', 'Hank Pym', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'HankPym@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'), - (1108, 'CharlesXavier', 'Charles Xavier', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'CharlesXavier@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'), - (1109, 'BruceBanner', 'Bruce Banner', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'BruceBanner@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'), - (1110, 'JessicaJones', 'Jessica Jones', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 1, 1, 'JessicaJones@mydomain.com', 'en', NULL), - (1111, 'Missing', 'Missing', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en', NULL), - (1112, 'Trash', 'Trash', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 'ac754a330530832ba1bf7687f577da91', 2, 0, NULL, 'en', NULL); + (1101, 'BruceWayne', 'Bruce Wayne', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 2, 1, 'BruceWayne@mydomain.com', 'es', 'e7723f0b24ff05b32ed09d95196f2f29'), + (1102, 'PetterParker', 'Petter Parker', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 2, 1, 'PetterParker@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'), + (1103, 'ClarkKent', 'Clark Kent', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 2, 1, 'ClarkKent@mydomain.com', 'fr', 'e7723f0b24ff05b32ed09d95196f2f29'), + (1104, 'TonyStark', 'Tony Stark', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 2, 1, 'TonyStark@mydomain.com', 'es', 'e7723f0b24ff05b32ed09d95196f2f29'), + (1105, 'MaxEisenhardt', 'Max Eisenhardt', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 2, 1, 'MaxEisenhardt@mydomain.com', 'pt', 'e7723f0b24ff05b32ed09d95196f2f29'), + (1106, 'DavidCharlesHaller', 'David Charles Haller', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 1, 1, 'DavidCharlesHaller@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'), + (1107, 'HankPym', 'Hank Pym', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 1, 1, 'HankPym@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'), + (1108, 'CharlesXavier', 'Charles Xavier', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 1, 1, 'CharlesXavier@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'), + (1109, 'BruceBanner', 'Bruce Banner', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 1, 1, 'BruceBanner@mydomain.com', 'en', 'e7723f0b24ff05b32ed09d95196f2f29'), + (1110, 'JessicaJones', 'Jessica Jones', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 1, 1, 'JessicaJones@mydomain.com', 'en', NULL), + (1111, 'Missing', 'Missing', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 2, 0, NULL, 'en', NULL), + (1112, 'Trash', 'Trash', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 2, 0, NULL, 'en', NULL); + +UPDATE account.`user` + SET passExpired = DATE_SUB(util.VN_CURDATE(), INTERVAL 1 YEAR) + WHERE name = 'maintenance'; INSERT INTO `account`.`mailAlias`(`id`, `alias`, `description`, `isPublic`) VALUES @@ -2732,7 +2736,8 @@ INSERT INTO `util`.`notification` (`id`, `name`, `description`) (1, 'print-email', 'notification fixture one'), (2, 'invoice-electronic', 'A electronic invoice has been generated'), (3, 'not-main-printer-configured', 'A printer distinct than main has been configured'), - (4, 'supplier-pay-method-update', 'A supplier pay method has been updated'); + (4, 'supplier-pay-method-update', 'A supplier pay method has been updated'), + (5, 'modified-entry', 'An entry has been modified'); INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`) VALUES @@ -2786,7 +2791,9 @@ INSERT INTO `vn`.`ticketLog` (`originFk`, userFk, `action`, changedModel, oldIns (7, 18, 'update', 'Sale', '{"quantity":1}', '{"quantity":10}', 1, NULL), (7, 18, 'update', 'Ticket', '{"quantity":1,"concept":"Chest ammo box"}', '{"quantity":10,"concept":"Chest ammo box"}', 1, NULL), (7, 18, 'update', 'Sale', '{"price":3}', '{"price":5}', 1, NULL), - (7, 18, 'update', NULL, NULL, NULL, NULL, "Cambio cantidad Melee weapon heavy shield 1x0.5m de '5' a '10'"); + (7, 18, 'update', NULL, NULL, NULL, NULL, "Cambio cantidad Melee weapon heavy shield 1x0.5m de '5' a '10'"), + (16, 9, 'update', 'Sale', '{"quantity":10,"concept":"Shield", "price": 10.5, "itemFk": 1}', '{"quantity":8,"concept":"Shield", "price": 10.5, "itemFk": 1}' , 5689, 'Shield'); + INSERT INTO `vn`.`ticketLog` (originFk, userFk, `action`, creationDate, changedModel, changedModelId, changedModelValue, oldInstance, newInstance, description) VALUES @@ -2800,7 +2807,6 @@ INSERT INTO `vn`.`ticketLog` (originFk, userFk, `action`, creationDate, changedM (1, 18, 'select', '2000-12-27 03:40:30', 'Ticket', 45, NULL , NULL, NULL, NULL), (1, 18, 'insert', '2000-04-10 09:40:15', 'Sale', 5689, 'Shield' , NULL, '{"quantity":10,"concept":"Shield", "price": 10.5, "itemFk": 1}', NULL), (1, 18, 'insert', '1999-05-09 10:00:00', 'Ticket', 45, 'Super Man' , NULL, '{"id":45,"clientFk":8608,"warehouseFk":60,"shipped":"2023-05-16T22:00:00.000Z","nickname":"Super Man","addressFk":48637,"isSigned":true,"isLabeled":true,"isPrinted":true,"packages":0,"hour":0,"created":"2023-05-16T11:42:56.000Z","isBlocked":false,"hasPriority":false,"companyFk":442,"agencyModeFk":639,"landed":"2023-05-17T22:00:00.000Z","isBoxed":true,"isDeleted":true,"zoneFk":713,"zonePrice":13,"zoneBonus":0}', NULL); - INSERT INTO `vn`.`osTicketConfig` (`id`, `host`, `user`, `password`, `oldStatus`, `newStatusId`, `day`, `comment`, `hostDb`, `userDb`, `passwordDb`, `portDb`, `responseType`, `fromEmailId`, `replyTo`) VALUES (0, 'http://localhost:56596/scp', 'ostadmin', 'Admin1', '1,6', 3, 60, 'Este CAU se ha cerrado automáticamente. Si el problema persiste responda a este mensaje.', 'localhost', 'osticket', 'osticket', 40003, 'reply', 1, 'all'); @@ -2889,6 +2895,10 @@ INSERT INTO `vn`.`wagonTypeTray` (`id`, `typeFk`, `height`, `colorFk`) (2, 1, 50, 2), (3, 1, 0, 3); +INSERT INTO `salix`.`accessTokenConfig` (`id`, `renewPeriod`, `renewInterval`) + VALUES + (1, 21600, 300); + INSERT INTO `vn`.`travelConfig` (`id`, `warehouseInFk`, `warehouseOutFk`, `agencyFk`, `companyFk`) VALUES (1, 1, 1, 1, 442); @@ -2896,3 +2906,10 @@ INSERT INTO `vn`.`travelConfig` (`id`, `warehouseInFk`, `warehouseOutFk`, `agenc INSERT INTO `vn`.`buyConfig` (`id`, `monthsAgo`) VALUES (1, 6); + +INSERT INTO `vn`.`invoiceInSerial` (`code`, `description`, `cplusTerIdNifFk`, `taxAreaFk`) + VALUES + ('C', 'Asgard', 1, 'WORLD'), + ('E', 'Midgard', 1, 'CEE'), + ('R', 'Jotunheim', 1, 'NATIONAL'), + ('W', 'Vanaheim', 1, 'WORLD'); diff --git a/db/dump/mockDate.sql b/db/dump/mockDate.sql index 2b4c33d74..5d4d0c351 100644 --- a/db/dump/mockDate.sql +++ b/db/dump/mockDate.sql @@ -1,30 +1,19 @@ -DROP FUNCTION IF EXISTS `util`.`mockTime`; DELIMITER $$ -$$ -CREATE DEFINER=`root`@`localhost` FUNCTION `util`.`mockTime`() RETURNS datetime + +CREATE OR REPLACE DEFINER=`root`@`localhost` FUNCTION `util`.`mockTime`() RETURNS datetime DETERMINISTIC BEGIN RETURN CONVERT_TZ('@mockDate', 'utc', 'Europe/Madrid'); END$$ -DELIMITER ; -DROP FUNCTION IF EXISTS `util`.`mockUtcTime`; - -DELIMITER $$ -$$ -CREATE DEFINER=`root`@`localhost` FUNCTION `util`.`mockUtcTime`() RETURNS datetime +CREATE OR REPLACE DEFINER=`root`@`localhost` FUNCTION `util`.`mockUtcTime`() RETURNS datetime DETERMINISTIC BEGIN RETURN CONVERT_TZ('@mockDate', 'utc', 'Europe/Madrid'); END$$ -DELIMITER ; -DROP FUNCTION IF EXISTS `util`.`mockTimeBase`; - -DELIMITER $$ -$$ -CREATE DEFINER=`root`@`localhost` FUNCTION `util`.`mockTimeBase`(vIsUtc BOOL) RETURNS datetime +CREATE OR REPLACE DEFINER=`root`@`localhost` FUNCTION `util`.`mockTimeBase`(vIsUtc BOOL) RETURNS datetime DETERMINISTIC BEGIN RETURN CONVERT_TZ('@mockDate', 'utc', 'Europe/Madrid'); diff --git a/db/dump/mysqlPlugins.sql b/db/dump/mysqlPlugins.sql deleted file mode 100644 index c3b6f6ee4..000000000 --- a/db/dump/mysqlPlugins.sql +++ /dev/null @@ -1,4 +0,0 @@ - --- Import compiled functions -CREATE AGGREGATE FUNCTION minacum RETURNS INT SONAME 'minacum.so'; -CREATE AGGREGATE FUNCTION multimax RETURNS INT SONAME 'multimax.so'; diff --git a/db/dump/structure.sql b/db/dump/structure.sql index 3ce7f7bb5..b07e88fde 100644 --- a/db/dump/structure.sql +++ b/db/dump/structure.sql @@ -15620,6 +15620,18 @@ CREATE TABLE `ClavesOperacion` ( -- -- Table structure for table `Municipios` -- +DROP TABLE IF EXISTS `taxType`; + +CREATE TABLE `taxType` ( + id INT(11) NOT NULL, + code VARCHAR(25) DEFAULT NULL NULL, + isIntracommunity TINYINT(1) DEFAULT FALSE NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `taxType_UN` (`code`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci COMMENT='Coincidencia del id con Sage.TiposIVA.CodigoIva(propia de Sage), en ningún caso vincular mediate FK'; + +ALTER TABLE `sage`.`taxType` ADD CONSTRAINT taxType_PK PRIMARY KEY IF NOT EXISTS (id); +ALTER TABLE `sage`.`taxType` ADD CONSTRAINT taxType_UN UNIQUE KEY IF NOT EXISTS (code); DROP TABLE IF EXISTS `Municipios`; /*!40101 SET @saved_cs_client = @@character_set_client */; @@ -22074,12 +22086,14 @@ CREATE TABLE `autonomy` ( `name` varchar(100) NOT NULL, `countryFk` mediumint(8) unsigned NOT NULL, `geoFk` int(11) DEFAULT NULL, + `isUeeMember` tinyint(1) DEFAULT NULL, + `hasDailyInvoice` tinyint(4) DEFAULT NULL, PRIMARY KEY (`id`), KEY `autonomy_FK` (`countryFk`), KEY `autonomy_FK_1` (`geoFk`), CONSTRAINT `autonomy_FK` FOREIGN KEY (`countryFk`) REFERENCES `country` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `autonomy_FK_1` FOREIGN KEY (`geoFk`) REFERENCES `zoneGeo` (`id`) ON UPDATE CASCADE -) ENGINE=InnoDBDEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci COMMENT='Comunidades autónomas o su equivalente en otros paises. Agrupación de provincias, en una categoria inferior a country.'; +) ENGINE=InnoDB AUTO_INCREMENT=109 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci COMMENT='Comunidades autónomas o su equivalente en otros paises. Agrupación de provincias, en una categoria inferior a country.'; /*!40101 SET character_set_client = @saved_cs_client */; /*!50003 SET @saved_cs_client = @@character_set_client */ ; /*!50003 SET @saved_cs_results = @@character_set_results */ ; @@ -28805,7 +28819,10 @@ CREATE TABLE `expence` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; - +ALTER TABLE `vn`.`expence` + ADD code VARCHAR(25) DEFAULT NULL NULL; +ALTER TABLE `vn`.`expence` + ADD CONSTRAINT expence_UN UNIQUE KEY IF NOT EXISTS (code); -- -- Table structure for table `farming` -- @@ -57317,7 +57334,7 @@ DELIMITER ; /*!50003 SET character_set_client = @saved_cs_client */ ; /*!50003 SET character_set_results = @saved_cs_results */ ; /*!50003 SET collation_connection = @saved_col_connection */ ; -/*!50003 DROP PROCEDURE IF EXISTS `invoiceInBookingMain` */; +/*!50003 DROP PROCEDURE IF EXISTS `invoiceIn_booking` */; /*!50003 SET @saved_cs_client = @@character_set_client */ ; /*!50003 SET @saved_cs_results = @@character_set_results */ ; /*!50003 SET @saved_col_connection = @@collation_connection */ ; @@ -57326,28 +57343,71 @@ DELIMITER ; /*!50003 SET collation_connection = utf8mb4_general_ci */ ; /*!50003 SET @saved_sql_mode = @@sql_mode */ ; /*!50003 SET sql_mode = 'NO_ENGINE_SUBSTITUTION' */ ; + + + DELIMITER ;; -CREATE DEFINER=`root`@`localhost` PROCEDURE `invoiceInBookingMain`(vInvoiceInId INT) +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`invoiceIn_booking`(vSelf INT) BEGIN - DECLARE vTotalAmount,vTotalAmountDivisa DECIMAL(10,2); - DECLARE vBookNumber,vSerialNumber INT; - DECLARE vRate DECIMAL(10,4); + DECLARE vBookNumber INT; - CALL invoiceInBookingCommon(vInvoiceInId,vSerialNumber); - - SELECT SUM(iit.taxableBase * IF( i.serial= 'R' AND ti.Iva <> 'HP DEVENGADO 21 ISP', 1 +(ti.PorcentajeIva/100),1)), - SUM(iit.foreignValue * IF( i.serial= 'R', 1 + (ti.PorcentajeIva/100),1)), - iit.taxableBase/iit.foreignValue - INTO vTotalAmount, vTotalAmountDivisa, vRate - FROM newInvoiceIn i - JOIN invoiceInTax iit ON iit.invoiceInFk = i.id - LEFT JOIN sage.TiposIva ti ON ti.CodigoIva = iit.taxTypeSageFk; + DROP TEMPORARY TABLE IF EXISTS tInvoiceIn; + CREATE TEMPORARY TABLE tInvoiceIn + ENGINE = MEMORY + SELECT ii.bookEntried, + iit.foreignValue, + ii.companyFk, + ii.expenceFkDeductible, + iit.taxableBase, + ii.serial, + ii.issued, + ii.operated, + ii.supplierRef, + ii.cplusTrascendency472Fk, + ii.cplusTaxBreakFk, + ii.cplusSubjectOpFk, + ii.cplusInvoiceType472Fk, + ii.cplusRectificationTypeFk, + ii.booked, + IFNULL(a.isUeeMember, c.isUeeMember) isUeeMember, + (c.id = cc.id) isSameCountry, + s.account supplierAccount, + s.name supplierName, + s.nif, + iit.taxTypeSageFk, + tt.code taxCode, + ti.Iva, + ti.CuentaIvaSoportado, + ti.PorcentajeIva, + ti.CuentaIvaRepercutido, + ttr.ClaveOperacionDefecto, + iis.cplusTerIdNifFk, + cit.id invoicesCount, + e.code, + e.isWithheld, + e.id expenceFk, + e.name expenceName + FROM invoiceIn ii + JOIN supplier s ON s.id = ii.supplierFk + LEFT JOIN province p ON p.id = s.provinceFk + LEFT JOIN autonomy a ON a.id = p.autonomyFk + JOIN country c ON c.id = s.countryFk + JOIN supplier sc ON sc.id = ii.companyFk + JOIN country cc ON cc.id = sc.countryFk + JOIN invoiceInSerial iis ON iis.code = ii.serial + JOIN cplusInvoiceType472 cit ON cit.id = ii.cplusInvoiceType472Fk + LEFT JOIN invoiceInTax iit ON iit.invoiceInFk = ii.id + LEFT JOIN sage.TiposTransacciones ttr ON ttr.CodigoTransaccion = iit.transactionTypeSageFk + LEFT JOIN expence e ON e.id = iit.expenceFk + LEFT JOIN sage.TiposIva ti ON ti.CodigoIva = iit.taxTypeSageFk + LEFT JOIN sage.taxType tt ON tt.id = ti.CodigoIva + WHERE ii.id = vSelf; CALL vn.ledger_next(vBookNumber); -- Apunte del proveedor - - INSERT INTO XDiario(ASIEN, + INSERT INTO XDiario( + ASIEN, FECHA, SUBCTA, EUROHABER, @@ -57356,24 +57416,30 @@ BEGIN HABERME, NFACTICK, CLAVE, - empresa_id - ) + empresa_id) SELECT - vBookNumber, - n.bookEntried, - s.supplierAccount, - vTotalAmount EUROHABER, - n.conceptWithSupplier, - vRate, - vTotalAmountDivisa, - n.invoicesCount, - vInvoiceInId, - n.companyFk - FROM newInvoiceIn n - JOIN newSupplier s; + vBookNumber ASIEN, + tii.bookEntried FECHA, + tii.supplierAccount SUBCTA, + SUM(tii.taxableBase * + IF(tii.serial= 'R' AND ((tii.taxCode IS NULL OR tii.taxCode <> 'ISP21') + AND tii.taxTypeSageFk IS NOT NULL), + 1 + (tii.PorcentajeIva / 100), + 1)) EUROHABER, + CONCAT('s/fra', + RIGHT(tii.supplierRef, 8), + ':', + LEFT(tii.supplierName, 10)) CONCEPTO, + CAST(tii.taxableBase / tii.foreignValue AS DECIMAL (10,4)) CAMBIO, + SUM(tii.foreignValue * IF(tii.serial = 'R', 1 + (tii.PorcentajeIva / 100), 1)) HABERME, + tii.invoicesCount NFACTICK, + vSelf CLAVE, + tii.companyFk empresa_id + FROM tInvoiceIn tii; -- Línea de Gastos - INSERT INTO XDiario ( ASIEN, + INSERT INTO XDiario( + ASIEN, FECHA, SUBCTA, CONTRA, @@ -57384,30 +57450,29 @@ BEGIN DEBEME, HABERME, NFACTICK, - empresa_id - ) + empresa_id) SELECT vBookNumber ASIEN, - n.bookEntried FECHA, - IF(e.isWithheld , LPAD(RIGHT(s.supplierAccount,5),10,iit.expenceFk),iit.expenceFk) SUBCTA, - s.supplierAccount CONTRA, - IF(e.isWithheld AND iit.taxableBase < 0, NULL, ROUND(SUM(iit.taxableBase),2)) EURODEBE, - IF(e.isWithheld AND iit.taxableBase < 0,ROUND(SUM(-iit.taxableBase),2),NULL) EUROHABER, - n.conceptWithSupplier CONCEPTO, - vRate, - IF(e.isWithheld,NULL,ABS(ROUND(SUM(iit.foreignValue),2))) DEBEME, - IF(e.isWithheld,ABS(ROUND(SUM(iit.foreignValue),2)),NULL) HABERME, - n.invoicesCount NFACTICK, - n.companyFk empresa_id - FROM newInvoiceIn n - JOIN newSupplier s - JOIN invoiceInTax iit ON iit.invoiceInFk = n.id - JOIN (SELECT * FROM expence e GROUP BY e.id)e ON e.id = iit.expenceFk - WHERE e.name != 'Suplidos Transitarios nacionales' - GROUP BY iit.expenceFk; + tii.bookEntried FECHA, + IF(tii.isWithheld, LPAD(RIGHT(tii.supplierAccount, 5), 10, tii.expenceFk),tii.expenceFk) SUBCTA, + tii.supplierAccount CONTRA, + IF(tii.isWithheld AND tii.taxableBase < 0, NULL, ROUND(SUM(tii.taxableBase),2)) EURODEBE, + IF(tii.isWithheld AND tii.taxableBase < 0, ROUND(SUM(-tii.taxableBase), 2), NULL) EUROHABER, + CONCAT('s/fra', + RIGHT(tii.supplierRef, 8), + ':', + LEFT(tii.supplierName, 10)) CONCEPTO, + CAST(tii.taxableBase / tii.foreignValue AS DECIMAL (10, 4)) CAMBIO, + IF(tii.isWithheld, NULL,ABS(ROUND(SUM(tii.foreignValue), 2))) DEBEME, + IF(tii.isWithheld, ABS(ROUND(SUM(tii.foreignValue), 2)) ,NULL) HABERME, + tii.invoicesCount NFACTICK, + tii.companyFk empresa_id + FROM tInvoiceIn tii + WHERE tii.code IS NULL OR tii.code <> 'suplido' + GROUP BY tii.expenceFk; -- Líneas de IVA - - INSERT INTO XDiario( ASIEN, + INSERT INTO XDiario( + ASIEN, FECHA, SUBCTA, CONTRA, @@ -57434,56 +57499,50 @@ BEGIN TERNIF, TERNOM, FECREGCON, - empresa_id - ) + empresa_id) SELECT vBookNumber ASIEN, - n.bookEntried FECHA, - IF(n.expenceFkDeductible>0, n.expenceFkDeductible, ti.CuentaIvaSoportado) SUBCTA, - s.supplierAccount CONTRA, - SUM(ROUND(ti.PorcentajeIva * it.taxableBase / 100 /* + 0.0001*/ , 2)) EURODEBE, - SUM(it.taxableBase) BASEEURO, - GROUP_CONCAT(DISTINCT e.`name` SEPARATOR ', ') CONCEPTO, - vSerialNumber FACTURA, - ti.PorcentajeIva IVA, - IF(isUeeMember AND eWithheld.id IS NULL,'','*') AUXILIAR, - n.serial SERIE, - ttr.ClaveOperacionDefecto, - n.issued FECHA_EX, - n.operated FECHA_OP, - n.invoicesCount NFACTICK, - n.supplierRef FACTURAEX, + tii.bookEntried FECHA, + IF(tii.expenceFkDeductible>0, tii.expenceFkDeductible, tii.CuentaIvaSoportado) SUBCTA, + tii.supplierAccount CONTRA, + SUM(ROUND(tii.PorcentajeIva * tii.taxableBase / 100, 2)) EURODEBE, + SUM(tii.taxableBase) BASEEURO, + GROUP_CONCAT(DISTINCT tii.expenceName SEPARATOR ', ') CONCEPTO, + vSelf FACTURA, + tii.PorcentajeIva IVA, + IF(tii.isUeeMember AND eWithheld.id IS NULL, '', '*') AUXILIAR, + tii.serial SERIE, + tii.ClaveOperacionDefecto, + tii.issued FECHA_EX, + tii.operated FECHA_OP, + tii.invoicesCount NFACTICK, + tii.supplierRef FACTURAEX, TRUE L340, - (isSameCountry OR NOT isUeeMember) LRECT349, - n.cplusTrascendency472Fk TIPOCLAVE, - n.cplusTaxBreakFk TIPOEXENCI, - n.cplusSubjectOpFk TIPONOSUJE, - n.cplusInvoiceType472Fk TIPOFACT, - n.cplusRectificationTypeFk TIPORECTIF, - iis.cplusTerIdNifFk TERIDNIF, - s.nif AS TERNIF, - s.name AS TERNOM, - n.booked FECREGCON, - n.companyFk - FROM newInvoiceIn n - JOIN newSupplier s - JOIN invoiceInTax it ON n.id = it.invoiceInFk - JOIN sage.TiposIva ti ON ti.CodigoIva = it.taxTypeSageFk - JOIN sage.TiposTransacciones ttr ON ttr.CodigoTransaccion = it.transactionTypeSageFk - JOIN invoiceInSerial iis ON iis.code = n.serial - JOIN (SELECT * FROM expence e GROUP BY e.id)e ON e.id = it.expenceFk + (tii.isSameCountry OR NOT tii.isUeeMember) LRECT349, + tii.cplusTrascendency472Fk TIPOCLAVE, + tii.cplusTaxBreakFk TIPOEXENCI, + tii.cplusSubjectOpFk TIPONOSUJE, + tii.cplusInvoiceType472Fk TIPOFACT, + tii.cplusRectificationTypeFk TIPORECTIF, + tii.cplusTerIdNifFk TERIDNIF, + tii.nif TERNIF, + tii.supplierName TERNOM, + tii.booked FECREGCON, + tii.companyFk + FROM tInvoiceIn tii LEFT JOIN ( - SELECT eWithheld.id - FROM invoiceInTax hold - JOIN expence eWithheld ON eWithheld.id = hold.expenceFk AND eWithheld.isWithheld - WHERE hold.invoiceInFk = vInvoiceInId LIMIT 1 + SELECT e.id + FROM tInvoiceIn tii + JOIN expence e ON e.id = tii.expenceFk + WHERE e.isWithheld + LIMIT 1 ) eWithheld ON TRUE - WHERE it.taxTypeSageFk IS NOT NULL - AND it.taxTypeSageFk NOT IN (22, 90) - GROUP BY ti.PorcentajeIva, e.id; + WHERE tii.taxTypeSageFk IS NOT NULL + AND (tii.taxCode IS NULL OR tii.taxCode NOT IN ('import10', 'import21')) + GROUP BY tii.PorcentajeIva, tii.expenceFk; -- Línea iva inversor sujeto pasivo - - INSERT INTO XDiario( ASIEN, + INSERT INTO XDiario( + ASIEN, FECHA, SUBCTA, CONTRA, @@ -57509,50 +57568,43 @@ BEGIN TERIDNIF, TERNIF, TERNOM, - empresa_id - ) + empresa_id) SELECT vBookNumber ASIEN, - n.bookEntried FECHA, - ti.CuentaIvaRepercutido SUBCTA, - s.supplierAccount CONTRA, - SUM(ROUND(ti.PorcentajeIva * it.taxableBase / 100,2)) EUROHABER, - ROUND(SUM(it.taxableBase),2) BASEEURO, - GROUP_CONCAT(DISTINCT e.`name` SEPARATOR ', ') CONCEPTO, - vSerialNumber FACTURA, - ti.PorcentajeIva IVA, + tii.bookEntried FECHA, + tii.CuentaIvaRepercutido SUBCTA, + tii.supplierAccount CONTRA, + SUM(ROUND(tii.PorcentajeIva * tii.taxableBase / 100,2)) EUROHABER, + ROUND(SUM(tii.taxableBase),2) BASEEURO, + GROUP_CONCAT(DISTINCT tii.expenceName SEPARATOR ', ') CONCEPTO, + vSelf FACTURA, + tii.PorcentajeIva IVA, '*' AUXILIAR, - n.serial SERIE, - ttr.ClaveOperacionDefecto, - n.issued FECHA_EX, - n.operated FECHA_OP, - n.invoicesCount NFACTICK, - n.supplierRef FACTURAEX, + tii.serial SERIE, + tii.ClaveOperacionDefecto, + tii.issued FECHA_EX, + tii.operated FECHA_OP, + tii.invoicesCount NFACTICK, + tii.supplierRef FACTURAEX, FALSE L340, - (isSameCountry OR NOT isUeeMember) LRECT349, + (tii.isSameCountry OR NOT tii.isUeeMember) LRECT349, 1 TIPOCLAVE, - n.cplusTaxBreakFk TIPOEXENCI, - n.cplusSubjectOpFk TIPONOSUJE, - n.cplusInvoiceType472Fk TIPOFACT, - n.cplusRectificationTypeFk TIPORECTIF, - iis.cplusTerIdNifFk TERIDNIF, - s.nif AS TERNIF, - s.name AS TERNOM, - n.companyFk - FROM newInvoiceIn n - JOIN newSupplier s - JOIN invoiceInTax it ON n.id = it.invoiceInFk - JOIN sage.TiposIva ti ON ti.CodigoIva = it.taxTypeSageFk - JOIN sage.TiposTransacciones ttr ON ttr.CodigoTransaccion = it.transactionTypeSageFk - JOIN invoiceInSerial iis ON iis.code = n.serial - JOIN (SELECT * FROM expence e GROUP BY e.id)e ON e.id = it.expenceFk - WHERE ti.Iva = 'HP DEVENGADO 21 ISP' OR MID(s.account, 4, 1) = '1' - GROUP BY ti.PorcentajeIva, e.id; + tii.cplusTaxBreakFk TIPOEXENCI, + tii.cplusSubjectOpFk TIPONOSUJE, + tii.cplusInvoiceType472Fk TIPOFACT, + tii.cplusRectificationTypeFk TIPORECTIF, + tii.cplusTerIdNifFk TERIDNIF, + tii.nif TERNIF, + tii.supplierName TERNOM, + tii.companyFk + FROM tInvoiceIn tii + WHERE tii.taxCode = 'ISP21' OR MID(tii.supplierAccount, 4, 1) = '1' + AND tii.taxTypeSageFk IS NOT NULL + GROUP BY tii.PorcentajeIva, tii.expenceFk; -- Actualización del registro original UPDATE invoiceIn ii - JOIN newInvoiceIn ni ON ii.id = ni.id - SET ii.serialNumber = vSerialNumber, - ii.isBooked = TRUE; + SET ii.isBooked = TRUE + WHERE ii.id = vSelf; -- Problemas derivados de la precisión en los decimales al calcular los impuestos UPDATE XDiario @@ -57569,8 +57621,12 @@ BEGIN ORDER BY id DESC LIMIT 1; + DROP TEMPORARY TABLE tInvoiceIn; END ;; DELIMITER ; + + + /*!50003 SET sql_mode = @saved_sql_mode */ ; /*!50003 SET character_set_client = @saved_cs_client */ ; /*!50003 SET character_set_results = @saved_cs_results */ ; diff --git a/db/export-data.sh b/db/export-data.sh index 2288c1c62..1df4db030 100755 --- a/db/export-data.sh +++ b/db/export-data.sh @@ -110,5 +110,6 @@ TABLES=( TiposIva TiposTransacciones TiposRetencion + taxType ) dump_tables ${TABLES[@]} diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index ed9c9daba..5c6350ba5 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -311,9 +311,9 @@ export default { }, clientDefaulter: { anyClient: 'vn-client-defaulter tbody > tr', - firstClientName: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(2) > span', - firstSalesPersonName: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(3) > span', - firstObservation: 'vn-client-defaulter tbody > tr:nth-child(1) > td:nth-child(8) > vn-textarea[ng-model="defaulter.observation"]', + firstClientName: 'vn-client-defaulter tbody > tr:nth-child(2) > td:nth-child(2) > span', + firstSalesPersonName: 'vn-client-defaulter tbody > tr:nth-child(2) > td:nth-child(3) > span', + firstObservation: 'vn-client-defaulter tbody > tr:nth-child(2) > td:nth-child(8) > vn-textarea[ng-model="defaulter.observation"]', allDefaulterCheckbox: 'vn-client-defaulter thead vn-multi-check', addObservationButton: 'vn-client-defaulter vn-button[icon="icon-notes"]', observation: '.vn-dialog.shown vn-textarea[ng-model="$ctrl.defaulter.observation"]', @@ -334,15 +334,15 @@ export default { }, itemsIndex: { createItemButton: `vn-float-button`, - firstSearchResult: 'vn-item-index tbody tr:nth-child(1)', + firstSearchResult: 'vn-item-index tbody tr:nth-child(2)', searchResult: 'vn-item-index tbody tr:not(.empty-rows)', - firstResultPreviewButton: 'vn-item-index tbody > :nth-child(1) .buttons > [icon="preview"]', + firstResultPreviewButton: 'vn-item-index tbody > :nth-child(2) .buttons > [icon="preview"]', searchResultCloneButton: 'vn-item-index .buttons > [icon="icon-clone"]', acceptClonationAlertButton: '.vn-confirm.shown [response="accept"]', closeItemSummaryPreview: '.vn-popup.shown', shownColumns: 'vn-item-index vn-button[id="shownColumns"]', shownColumnsList: '.vn-popover.shown .content', - firstItemImage: 'vn-item-index tbody > tr:nth-child(1) > td:nth-child(1) > img', + firstItemImage: 'vn-item-index tbody > tr:nth-child(2) > td:nth-child(1) > img', firstItemImageTd: 'vn-item-index smart-table tr:nth-child(1) td:nth-child(1)', firstItemId: 'vn-item-index tbody > tr:nth-child(1) > td:nth-child(2)', idCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Identifier"]', @@ -523,11 +523,11 @@ export default { searchResultDate: 'vn-ticket-summary [label=Landed] span', topbarSearch: 'vn-searchbar', moreMenu: 'vn-ticket-index vn-icon-button[icon=more_vert]', - fourthWeeklyTicket: 'vn-ticket-weekly-index vn-card smart-table slot-table tr:nth-child(4)', - fiveWeeklyTicket: 'vn-ticket-weekly-index vn-card smart-table slot-table tr:nth-child(5)', + fourthWeeklyTicket: 'vn-ticket-weekly-index vn-card smart-table slot-table tr:nth-child(5)', + fiveWeeklyTicket: 'vn-ticket-weekly-index vn-card smart-table slot-table tr:nth-child(6)', weeklyTicket: 'vn-ticket-weekly-index vn-card smart-table slot-table table tbody tr', - firstWeeklyTicketDeleteIcon: 'vn-ticket-weekly-index vn-card smart-table slot-table tr:nth-child(1) vn-icon-button[icon="delete"]', - firstWeeklyTicketAgency: 'vn-ticket-weekly-index vn-card smart-table slot-table tr:nth-child(1) [ng-model="weekly.agencyModeFk"]', + firstWeeklyTicketDeleteIcon: 'vn-ticket-weekly-index vn-card smart-table slot-table tr:nth-child(2) vn-icon-button[icon="delete"]', + firstWeeklyTicketAgency: 'vn-ticket-weekly-index vn-card smart-table slot-table tr:nth-child(2) [ng-model="weekly.agencyModeFk"]', acceptDeleteTurn: '.vn-confirm.shown button[response="accept"]' }, createTicketView: { @@ -572,15 +572,15 @@ export default { submitNotesButton: 'button[type=submit]' }, ticketExpedition: { - firstSaleCheckbox: 'vn-ticket-expedition vn-tr:nth-child(1) vn-check[ng-model="expedition.checked"]', - thirdSaleCheckbox: 'vn-ticket-expedition vn-tr:nth-child(3) vn-check[ng-model="expedition.checked"]', - deleteExpeditionButton: 'vn-ticket-expedition vn-tool-bar > vn-button[icon="delete"]', - moveExpeditionButton: 'vn-ticket-expedition vn-tool-bar > vn-button[icon="keyboard_arrow_down"]', + firstSaleCheckbox: 'vn-ticket-expedition tr:nth-child(2) vn-check[ng-model="expedition.checked"]', + thirdSaleCheckbox: 'vn-ticket-expedition tr:nth-child(4) vn-check[ng-model="expedition.checked"]', + deleteExpeditionButton: 'vn-ticket-expedition slot-actions > vn-button[icon="delete"]', + moveExpeditionButton: 'vn-ticket-expedition slot-actions > vn-button[icon="keyboard_arrow_down"]', moreMenuWithoutRoute: 'vn-item[name="withoutRoute"]', moreMenuWithRoute: 'vn-item[name="withRoute"]', newRouteId: '.vn-dialog.shown vn-textfield[ng-model="$ctrl.newRoute"]', saveButton: '.vn-dialog.shown [response="accept"]', - expeditionRow: 'vn-ticket-expedition vn-table vn-tbody > vn-tr' + expeditionRow: 'vn-ticket-expedition table tbody > tr' }, ticketSales: { setOk: 'vn-ticket-sale vn-tool-bar > vn-button[label="Ok"] > button', @@ -712,7 +712,7 @@ export default { problems: 'vn-check[label="With problems"]', tableButtonSearch: 'vn-button[vn-tooltip="Search"]', moveButton: 'vn-button[vn-tooltip="Future tickets"]', - firstCheck: 'tbody > tr:nth-child(1) > td > vn-check', + firstCheck: 'tbody > tr:nth-child(2) > td > vn-check', multiCheck: 'vn-multi-check', tableId: 'vn-textfield[name="id"]', tableFutureId: 'vn-textfield[name="futureId"]', @@ -736,7 +736,7 @@ export default { tableButtonSearch: 'vn-button[vn-tooltip="Search"]', moveButton: 'vn-button[vn-tooltip="Advance tickets"]', acceptButton: '.vn-confirm.shown button[response="accept"]', - firstCheck: 'tbody > tr:nth-child(1) > td > vn-check', + firstCheck: 'tbody > tr:nth-child(2) > td > vn-check', tableId: 'vn-textfield[name="id"]', tableFutureId: 'vn-textfield[name="futureId"]', tableLiters: 'vn-textfield[name="liters"]', @@ -810,7 +810,7 @@ export default { claimAction: { importClaimButton: 'vn-claim-action vn-button[label="Import claim"]', anyLine: 'vn-claim-action vn-tbody > vn-tr', - firstDeleteLine: 'vn-claim-action tr:nth-child(1) vn-icon-button[icon="delete"]', + firstDeleteLine: 'vn-claim-action tr:nth-child(2) vn-icon-button[icon="delete"]', isPaidWithManaCheckbox: 'vn-claim-action vn-check[ng-model="$ctrl.claim.isChargedToMana"]' }, ordersIndex: { @@ -1214,7 +1214,7 @@ export default { addTagButton: 'vn-icon-button[vn-tooltip="Add tag"]', itemTagInput: 'vn-autocomplete[ng-model="itemTag.tagFk"]', itemTagValueInput: 'vn-autocomplete[ng-model="itemTag.value"]', - firstBuy: 'vn-entry-latest-buys tbody > tr:nth-child(1)', + firstBuy: 'vn-entry-latest-buys tbody > tr:nth-child(2)', allBuysCheckBox: 'vn-entry-latest-buys thead vn-check', secondBuyCheckBox: 'vn-entry-latest-buys tbody tr:nth-child(2) vn-check[ng-model="buy.checked"]', editBuysButton: 'vn-entry-latest-buys vn-button[icon="edit"]', diff --git a/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js b/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js index 526afa140..9c37ce9ba 100644 --- a/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js +++ b/e2e/paths/01-salix/03_smartTable_searchBar_integrations.spec.js @@ -19,15 +19,14 @@ describe('SmartTable SearchBar integration', () => { await page.waitToClick(selectors.itemsIndex.openAdvancedSearchButton); await page.autocompleteSearch(selectors.itemsIndex.advancedSearchItemType, 'Anthurium'); await page.waitToClick(selectors.itemsIndex.advancedSearchButton); - await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 3); + await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 4); await page.reload({ waitUntil: 'networkidle2' }); - await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 3); + await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 4); - await page.waitToClick(selectors.itemsIndex.advancedSmartTableButton); await page.write(selectors.itemsIndex.advancedSmartTableGrouping, '1'); await page.keyboard.press('Enter'); await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 2); @@ -36,7 +35,7 @@ describe('SmartTable SearchBar integration', () => { waitUntil: 'networkidle2' }); - await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 2); + await page.waitForNumberOfElements(selectors.itemsIndex.searchResult, 1); }); it('should filter in section without smart-table and search in searchBar go to zone section', async() => { diff --git a/e2e/paths/01-salix/05_changePassword.spec.js b/e2e/paths/01-salix/05_changePassword.spec.js new file mode 100644 index 000000000..6e4cfb7f3 --- /dev/null +++ b/e2e/paths/01-salix/05_changePassword.spec.js @@ -0,0 +1,71 @@ +import getBrowser from '../../helpers/puppeteer'; + +const $ = { + form: 'vn-out-layout form' +}; + +describe('ChangePassword path', async() => { + let browser; + let page; + beforeAll(async() => { + browser = await getBrowser(); + page = browser.page; + }); + + afterAll(async() => { + await browser.close(); + }); + + const oldPassword = 'nightmare'; + const newPassword = 'newPass.1234'; + describe('Bad login', async() => { + it('should receive an error when the password is expired', async() => { + // Expired login + await page.doLogin('Maintenance', oldPassword); + let message = await page.waitForSnackbar(); + + expect(message.text).toContain('The password has expired, change it from Salix'); + expect(await page.getState()).toContain('change-password'); + + // Bad attempt: incorrect current password + message = await page.sendForm($.form, { + oldPassword: newPassword, + newPassword: oldPassword, + repeatPassword: oldPassword + }); + + expect(message.text).toContain('Invalid current password'); + + // Bad attempt: password not meet requirements + message = await page.sendForm($.form, { + oldPassword: oldPassword, + newPassword: oldPassword, + repeatPassword: oldPassword + }); + + expect(message.text).toContain('Password does not meet requirements'); + + // Correct attempt: change password + message = await page.sendForm($.form, { + oldPassword: oldPassword, + newPassword: newPassword, + repeatPassword: newPassword + }); + + expect(message.text).toContain('Password updated!'); + expect(await page.getState()).toContain('login'); + + // Bad login, old password + await page.doLogin('Maintenance', oldPassword); + message = await page.waitForSnackbar(); + + expect(message.text).toContain('Invalid login'); + + // Correct login, new password + await page.doLogin('Maintenance', newPassword); + await page.waitForSelector('vn-home'); + + expect(await page.getState()).toBe('home'); + }); + }); +}); diff --git a/e2e/paths/02-client/21_defaulter.spec.js b/e2e/paths/02-client/21_defaulter.spec.js index 97e62abef..2bb3d6254 100644 --- a/e2e/paths/02-client/21_defaulter.spec.js +++ b/e2e/paths/02-client/21_defaulter.spec.js @@ -19,7 +19,7 @@ describe('Client defaulter path', () => { it('should count the amount of clients in the turns section', async() => { const result = await page.countElement(selectors.clientDefaulter.anyClient); - expect(result).toEqual(5); + expect(result).toEqual(6); }); it('should check contain expected client', async() => { diff --git a/e2e/paths/03-worker/06_create.spec.js b/e2e/paths/03-worker/06_create.spec.js index 98e67edbf..11d36b3cf 100644 --- a/e2e/paths/03-worker/06_create.spec.js +++ b/e2e/paths/03-worker/06_create.spec.js @@ -53,7 +53,7 @@ describe('Worker create path', () => { expect(message.text).toContain('Data saved!'); // 'rollback' - await page.loginAndModule('sysadmin', 'account'); + await page.loginAndModule('itManagement', 'account'); await page.accessToSearchResult(newWorker); await page.waitToClick(selectors.accountDescriptor.menuButton); diff --git a/e2e/paths/04-item/01_summary.spec.js b/e2e/paths/04-item/01_summary.spec.js index e24fa6a9f..e09ecb778 100644 --- a/e2e/paths/04-item/01_summary.spec.js +++ b/e2e/paths/04-item/01_summary.spec.js @@ -18,11 +18,11 @@ describe('Item summary path', () => { await page.doSearch('Ranged weapon'); const resultsCount = await page.countElement(selectors.itemsIndex.searchResult); - await page.waitForTextInElement(selectors.itemsIndex.searchResult, 'Ranged weapon'); + await page.waitForTextInElement(selectors.itemsIndex.firstSearchResult, 'Ranged weapon'); await page.waitToClick(selectors.itemsIndex.firstResultPreviewButton); const isVisible = await page.isVisible(selectors.itemSummary.basicData); - expect(resultsCount).toBe(3); + expect(resultsCount).toBe(4); expect(isVisible).toBeTruthy(); }); @@ -66,7 +66,7 @@ describe('Item summary path', () => { await page.waitToClick(selectors.itemsIndex.firstResultPreviewButton); await page.waitForSelector(selectors.itemSummary.basicData, {visible: true}); - expect(resultsCount).toBe(2); + expect(resultsCount).toBe(3); }); it(`should now check the item summary preview shows fields from basic data`, async() => { diff --git a/e2e/paths/04-item/10_item_log.spec.js b/e2e/paths/04-item/10_item_log.spec.js index dc467044d..c88fbd337 100644 --- a/e2e/paths/04-item/10_item_log.spec.js +++ b/e2e/paths/04-item/10_item_log.spec.js @@ -18,7 +18,7 @@ describe('Item log path', () => { await page.doSearch('Knowledge artifact'); const nResults = await page.countElement(selectors.itemsIndex.searchResult); - expect(nResults).toEqual(0); + expect(nResults).toEqual(1); }); it('should access to the create item view by clicking the create floating button', async() => { diff --git a/e2e/paths/05-ticket/02_expeditions_and_log.spec.js b/e2e/paths/05-ticket/02_expeditions_and_log.spec.js index edccd5561..b97576940 100644 --- a/e2e/paths/05-ticket/02_expeditions_and_log.spec.js +++ b/e2e/paths/05-ticket/02_expeditions_and_log.spec.js @@ -27,6 +27,6 @@ describe('Ticket expeditions and log path', () => { const result = await page .countElement(selectors.ticketExpedition.expeditionRow); - expect(result).toEqual(3); + expect(result).toEqual(4); }); }); diff --git a/e2e/paths/05-ticket/09_weekly.spec.js b/e2e/paths/05-ticket/09_weekly.spec.js index 0ba57aa9d..a9cce2ead 100644 --- a/e2e/paths/05-ticket/09_weekly.spec.js +++ b/e2e/paths/05-ticket/09_weekly.spec.js @@ -19,7 +19,7 @@ describe('Ticket descriptor path', () => { it('should count the amount of tickets in the turns section', async() => { const result = await page.countElement(selectors.ticketsIndex.weeklyTicket); - expect(result).toEqual(6); + expect(result).toEqual(7); }); it('should go back to the ticket index then search and access a ticket summary', async() => { @@ -89,7 +89,7 @@ describe('Ticket descriptor path', () => { await page.doSearch('11'); const nResults = await page.countElement(selectors.ticketsIndex.searchWeeklyResult); - expect(nResults).toEqual(1); + expect(nResults).toEqual(2); }); it('should delete the weekly ticket 11', async() => { @@ -104,7 +104,7 @@ describe('Ticket descriptor path', () => { await page.doSearch(); const nResults = await page.countElement(selectors.ticketsIndex.searchWeeklyResult); - expect(nResults).toEqual(6); + expect(nResults).toEqual(7); }); it('should update the agency then remove it afterwards', async() => { diff --git a/e2e/paths/05-ticket/20_moveExpedition.spec.js b/e2e/paths/05-ticket/20_moveExpedition.spec.js index cf1c5ded3..ae23c9c99 100644 --- a/e2e/paths/05-ticket/20_moveExpedition.spec.js +++ b/e2e/paths/05-ticket/20_moveExpedition.spec.js @@ -29,7 +29,7 @@ describe('Ticket expeditions', () => { const result = await page .countElement(selectors.ticketExpedition.expeditionRow); - expect(result).toEqual(1); + expect(result).toEqual(2); }); it(`should move one expedition to new ticket with route`, async() => { @@ -45,6 +45,6 @@ describe('Ticket expeditions', () => { const result = await page .countElement(selectors.ticketExpedition.expeditionRow); - expect(result).toEqual(1); + expect(result).toEqual(2); }); }); diff --git a/e2e/paths/05-ticket/21_future.spec.js b/e2e/paths/05-ticket/21_future.spec.js index c854dcbaf..7b7d478f7 100644 --- a/e2e/paths/05-ticket/21_future.spec.js +++ b/e2e/paths/05-ticket/21_future.spec.js @@ -87,7 +87,7 @@ describe('Ticket Future path', () => { await page.clearInput(selectors.ticketFuture.futureState); await page.waitToClick(selectors.ticketFuture.submit); - await page.waitForNumberOfElements(selectors.ticketFuture.searchResult, 4); + await page.waitForNumberOfElements(selectors.ticketFuture.searchResult, 5); await page.waitToClick(selectors.ticketFuture.multiCheck); await page.waitToClick(selectors.ticketFuture.firstCheck); await page.waitToClick(selectors.ticketFuture.moveButton); diff --git a/e2e/paths/14-account/01_create_and_basic_data.spec.js b/e2e/paths/14-account/01_create_and_basic_data.spec.js index 54e4d1f12..e38d1aeec 100644 --- a/e2e/paths/14-account/01_create_and_basic_data.spec.js +++ b/e2e/paths/14-account/01_create_and_basic_data.spec.js @@ -8,7 +8,7 @@ describe('Account create and basic data path', () => { beforeAll(async() => { browser = await getBrowser(); page = browser.page; - await page.loginAndModule('developer', 'account'); + await page.loginAndModule('itManagement', 'account'); }); afterAll(async() => { diff --git a/front/core/components/smart-table/index.js b/front/core/components/smart-table/index.js index b2380a62f..c3b927c62 100644 --- a/front/core/components/smart-table/index.js +++ b/front/core/components/smart-table/index.js @@ -40,6 +40,8 @@ export default class SmartTable extends Component { this._options = options; if (!options) return; + options.defaultSearch = true; + if (options.defaultSearch) this.displaySearch(); diff --git a/front/core/services/auth.js b/front/core/services/auth.js index 0b89a8e88..ef6c07637 100644 --- a/front/core/services/auth.js +++ b/front/core/services/auth.js @@ -24,7 +24,7 @@ export default class Auth { initialize() { let criteria = { to: state => { - const outLayout = ['login', 'recover-password', 'reset-password']; + const outLayout = ['login', 'recover-password', 'reset-password', 'change-password']; return !outLayout.some(ol => ol == state.name); } }; @@ -59,12 +59,12 @@ export default class Auth { password: password || undefined }; - return this.$http.post('VnUsers/signIn', params).then( - json => this.onLoginOk(json, remember)); + return this.$http.post('VnUsers/signIn', params) + .then(json => this.onLoginOk(json, remember)); } onLoginOk(json, remember) { - this.vnToken.set(json.data.token, remember); + this.vnToken.set(json.data.token, json.data.created, remember); return this.loadAcls().then(() => { let continueHash = this.$state.params.continue; diff --git a/front/core/services/index.js b/front/core/services/index.js index 867a13df0..855f2fab1 100644 --- a/front/core/services/index.js +++ b/front/core/services/index.js @@ -11,3 +11,4 @@ import './report'; import './email'; import './file'; import './date'; + diff --git a/front/core/services/token.js b/front/core/services/token.js index 126fbb604..c1bb5a173 100644 --- a/front/core/services/token.js +++ b/front/core/services/token.js @@ -9,25 +9,33 @@ export default class Token { constructor() { try { this.token = sessionStorage.getItem('vnToken'); - if (!this.token) + this.created = sessionStorage.getItem('vnTokenCreated'); + if (!this.token) { this.token = localStorage.getItem('vnToken'); + this.created = localStorage.getItem('vnTokenCreated'); + } } catch (e) {} } - set(value, remember) { + set(token, created, remember) { this.unset(); try { - if (remember) - localStorage.setItem('vnToken', value); - else - sessionStorage.setItem('vnToken', value); + if (remember) { + localStorage.setItem('vnToken', token); + localStorage.setItem('vnTokenCreated', created); + } else { + sessionStorage.setItem('vnToken', token); + sessionStorage.setItem('vnTokenCreated', created); + } } catch (e) {} - this.token = value; + this.token = token; + this.created = created; } unset() { localStorage.removeItem('vnToken'); sessionStorage.removeItem('vnToken'); this.token = null; + this.created = null; } } diff --git a/front/core/styles/util.scss b/front/core/styles/util.scss index 95a523ff4..8c345ddcc 100644 --- a/front/core/styles/util.scss +++ b/front/core/styles/util.scss @@ -1,4 +1,11 @@ +@import "./variables"; +@import "./effects"; +@mixin mobile { + @media screen and (max-width: $mobile-width) { + @content; + } +} @mixin browser($browser) { html[data-browser*="#{$browser}"] & { @content; diff --git a/front/core/styles/variables.scss b/front/core/styles/variables.scss index bcc9fab66..0a74c8277 100644 --- a/front/core/styles/variables.scss +++ b/front/core/styles/variables.scss @@ -1,5 +1,3 @@ -@import "./util"; - $font-size: 11pt; $menu-width: 256px; $topbar-height: 56px; diff --git a/front/salix/components/background/style.scss b/front/salix/components/background/style.scss index c75b69c52..5bc56683e 100644 --- a/front/salix/components/background/style.scss +++ b/front/salix/components/background/style.scss @@ -1,4 +1,4 @@ -@import "variables"; +@import "util"; @keyframes fadein { from { @@ -16,7 +16,7 @@ vn-background { background-color: black; z-index: 14; - @media screen and (max-width: $mobile-width) { + @include mobile { &.shown { display: block; opacity: .3; diff --git a/front/salix/components/change-password/index.html b/front/salix/components/change-password/index.html new file mode 100644 index 000000000..8d338d411 --- /dev/null +++ b/front/salix/components/change-password/index.html @@ -0,0 +1,29 @@ +
+ |
+ + Expedition + | ++ Item + | ++ Name + | ++ Package Type + | ++ Counter + | ++ externalId + | ++ Created + | ++ State + | ++ |
---|---|---|---|---|---|---|---|---|---|
+ |
+ {{expedition.id}} | ++ + {{expedition.packagingItemFk}} + + | +{{::expedition.packageItemName}} | +{{::expedition.freightItemName}} | +{{::expedition.counter}} | +{{::expedition.externalId}} | +{{::expedition.created | date:'dd/MM/yyyy HH:mm'}} | +{{::expedition.state}} | +
+ |
+
+ {{$t('dear')}} +
+ ++ {{ $t('description') }}: + {{ url }} +
+