diff --git a/back/methods/dms/saveSign.js b/back/methods/dms/saveSign.js new file mode 100644 index 000000000..f668c5ed2 --- /dev/null +++ b/back/methods/dms/saveSign.js @@ -0,0 +1,215 @@ +const md5 = require('md5'); +const fs = require('fs-extra'); + +module.exports = Self => { + Self.remoteMethodCtx('saveSign', { + description: 'Save sign', + accessType: 'WRITE', + accepts: + [ + { + arg: 'signContent', + type: 'string', + required: true, + description: 'The sign content' + }, { + arg: 'tickets', + type: ['number'], + required: true, + description: 'The tickets' + }, { + arg: 'signedTime', + type: 'date', + description: 'The signed time' + }, { + arg: 'addressFk', + type: 'number', + required: true, + description: 'The address fk' + } + ], + returns: { + type: 'Object', + root: true + }, + http: { + path: `/saveSign`, + verb: 'POST' + } + }); + + async function createGestDoc(ticketId, userFk) { + const models = Self.app.models; + if (!await gestDocExists(ticketId)) { + const result = await models.Ticket.findOne({ + where: { + id: ticketId + }, + include: [ + { + relation: 'warehouse', + scope: { + fields: ['id'] + } + }, { + relation: 'client', + scope: { + fields: ['name'] + } + }, { + relation: 'route', + scope: { + fields: ['id'] + } + } + ] + }); + + const warehouseFk = result.warehouseFk; + const companyFk = result.companyFk; + const client = result.client.name; + const route = result.route.id; + + const resultDmsType = await models.DmsType.findOne({ + where: { + code: 'Ticket' + } + }); + + const resultDms = await models.Dms.create({ + dmsTypeFk: resultDmsType.id, + reference: ticketId, + description: `Ticket ${ticketId} Cliente ${client} Ruta ${route}`, + companyFk: companyFk, + warehouseFk: warehouseFk, + workerFk: userFk + }); + + return resultDms.insertId; + } + } + + async function gestDocExists(ticket) { + const models = Self.app.models; + const result = await models.TicketDms.findOne({ + where: { + ticketFk: ticket + }, + fields: ['dmsFk'] + }); + + if (result == null) + return false; + + const isSigned = await models.Ticket.findOne({ + where: { + id: ticket + }, + fields: ['isSigned'] + }); + + if (isSigned) + return true; + else + await models.Dms.destroyById(ticket); + } + + async function dmsRecover(ticket, signContent) { + const models = Self.app.models; + await models.DmsRecover.create({ + ticketFk: ticket, + sign: signContent + }); + } + + async function ticketGestdoc(ticket, dmsFk) { + const models = Self.app.models; + models.TicketDms.replaceOrCreate({ + ticketFk: ticket, + dmsFk: dmsFk + }); + + const queryVnTicketSetState = `CALL vn.ticket_setState(?, ?)`; + + await Self.rawSql(queryVnTicketSetState, [ticket, 'DELIVERED']); + } + + async function updateGestdoc(file, ticket) { + const models = Self.app.models; + models.Dms.updateOne({ + where: { + id: ticket + }, + file: file, + contentType: 'image/png' + }); + } + + Self.saveSign = async(ctx, signContent, tickets, signedTime) => { + const models = Self.app.models; + let tx = await Self.beginTransaction({}); + try { + const userId = ctx.req.accessToken.userId; + + const dmsDir = `storage/dms`; + + let image = null; + + for (let i = 0; i < tickets.length; i++) { + const alertLevel = await models.TicketState.findOne({ + where: { + ticketFk: tickets[i] + }, + fields: ['alertLevel'] + }); + + signedTime ? signedTime != undefined : signedTime = new Date(); + + if (alertLevel >= 2) { + let dir; + let id = null; + let fileName = null; + + if (!await gestDocExists(tickets[i])) { + id = await createGestDoc(tickets[i], userId); + + const hashDir = md5(id).substring(0, 3); + dir = `${dmsDir}/${hashDir}`; + + if (!fs.existsSync(dir)) + fs.mkdirSync(dir); + + fileName = `${id}.png`; + image = `${dir}/${fileName}`; + } else + + if (image != null) { + if (!fs.existsSync(dir)) + dmsRecover(tickets[i], signContent); + else { + fs.writeFile(image, signContent, 'base64', async function(err) { + if (err) { + await tx.rollback(); + return err.message; + } + }); + } + } else + dmsRecover(tickets[i], signContent); + + if (id != null && fileName.length > 0) { + ticketGestdoc(tickets[i], id); + updateGestdoc(id, fileName); + } + } + } + + if (tx) await tx.commit(); + + return 'OK'; + } catch (err) { + await tx.rollback(); + throw err.message; + } + }; +}; diff --git a/back/models/dms.js b/back/models/dms.js index 24c072f56..fc586201f 100644 --- a/back/models/dms.js +++ b/back/models/dms.js @@ -6,6 +6,7 @@ module.exports = Self => { require('../methods/dms/removeFile')(Self); require('../methods/dms/updateFile')(Self); require('../methods/dms/deleteTrashFiles')(Self); + require('../methods/dms/saveSign')(Self); Self.checkRole = async function(ctx, id) { const models = Self.app.models; diff --git a/db/changes/10510-december/00-mdbApp.sql b/db/changes/10510-december/00-mdbApp.sql new file mode 100644 index 000000000..3202e3f08 --- /dev/null +++ b/db/changes/10510-december/00-mdbApp.sql @@ -0,0 +1,11 @@ +CREATE TABLE `vn`.`mdbApp` ( + `app` varchar(100) COLLATE utf8mb3_unicode_ci NOT NULL, + `baselineBranchFk` varchar(255) COLLATE utf8mb3_unicode_ci DEFAULT NULL, + `userFk` int(10) unsigned DEFAULT NULL, + `locked` datetime DEFAULT NULL, + PRIMARY KEY (`app`), + KEY `mdbApp_FK` (`userFk`), + KEY `mdbApp_FK_1` (`baselineBranchFk`), + CONSTRAINT `mdbApp_FK` FOREIGN KEY (`userFk`) REFERENCES `account`.`user` (`id`) ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT `mdbApp_FK_1` FOREIGN KEY (`baselineBranchFk`) REFERENCES `mdbBranch` (`name`) ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci diff --git a/db/changes/225001/00-aclMdbApp.sql b/db/changes/225001/00-aclMdbApp.sql new file mode 100644 index 000000000..b5b60546c --- /dev/null +++ b/db/changes/225001/00-aclMdbApp.sql @@ -0,0 +1,4 @@ +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('MdbApp', 'lock', 'WRITE', 'ALLOW', 'ROLE', 'developer'), + ('MdbApp', 'unlock', 'WRITE', 'ALLOW', 'ROLE', 'developer'); diff --git a/db/changes/225201/00-ACL_saveSign.sql b/db/changes/225201/00-ACL_saveSign.sql new file mode 100644 index 000000000..16f9931c4 --- /dev/null +++ b/db/changes/225201/00-ACL_saveSign.sql @@ -0,0 +1 @@ +INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`) VALUES ('Dms','saveSign','*','ALLOW','employee'); diff --git a/db/changes/225201/00-accountingMovements_add.sql b/db/changes/225201/00-accountingMovements_add.sql new file mode 100644 index 000000000..12dc176bc --- /dev/null +++ b/db/changes/225201/00-accountingMovements_add.sql @@ -0,0 +1,438 @@ +DROP PROCEDURE IF EXISTS `sage`.`accountingMovements_add`; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`localhost` PROCEDURE `sage`.`accountingMovements_add`(vYear INT, vCompanyFk INT) +BEGIN +/** + * Traslada la info de contabilidad generada en base a vn.XDiario a la tabla sage.movConta para poder ejecutar posteriormente el proceso de importación de datos de SQL Server + * Solo traladará los asientos marcados con el campo vn.XDiario.enlazadoSage = FALSE + * @vYear Año contable del que se quiere trasladar la información + * @vCompanyFk Empresa de la que se quiere trasladar datos + */ + DECLARE vDatedFrom DATETIME; + DECLARE vDatedTo DATETIME; + DECLARE vDuaTransactionFk INT; + DECLARE vTaxImportFk INT; + DECLARE vTaxImportReducedFk INT; + DECLARE vTaxImportSuperReducedFk INT; + DECLARE vTransactionExportFk INT; + DECLARE vTransactionExportTaxFreeFk INT; + DECLARE vSerialDua VARCHAR(1) DEFAULT 'D'; + DECLARE vInvoiceTypeInformativeCode VARCHAR(1); + DECLARE vCountryCanariasCode, vCountryCeutaMelillaCode VARCHAR(2) ; + DECLARE vBookEntries TEXT; + + SELECT SiglaNacion INTO vCountryCanariasCode + FROM Naciones + WHERE Nacion ='ISLAS CANARIAS'; + + SELECT SiglaNacion INTO vCountryCeutaMelillaCode + FROM Naciones + WHERE Nacion ='CEUTA Y MELILLA'; + + SELECT CodigoTransaccion INTO vDuaTransactionFk + FROM TiposTransacciones + WHERE Transaccion = 'Import. bienes y serv. corrientes pdte. liquidar'; + + SELECT CodigoIva INTO vTaxImportFk + FROM TiposIva + WHERE Iva = 'IVA 21% importaciones'; + + SELECT CodigoIva INTO vTaxImportReducedFk + FROM TiposIva + WHERE Iva = 'IVA 10% importaciones'; + + SELECT CodigoIva INTO vTaxImportSuperReducedFk + FROM TiposIva + WHERE Iva = 'H.P. IVA Soportado Impor 4%'; + + SELECT CodigoTransaccion INTO vTransactionExportFk + FROM TiposTransacciones + WHERE Transaccion = 'Exportaciones definitivas'; + + SELECT CodigoTransaccion INTO vTransactionExportTaxFreeFk + FROM TiposTransacciones + WHERE Transaccion = 'Envíos definitivos a Canarias, Ceuta y Melilla'; + + SELECT codeSage INTO vInvoiceTypeInformativeCode + FROM invoiceType WHERE code ='informative'; + + SELECT CAST(CONCAT(vYear, '-01-01') AS DATETIME), util.dayEnd(CAST(CONCAT(vYear, '-12-31') AS DATE)) + INTO vDatedFrom, vDatedTo; + + TRUNCATE movContaIVA; + + DELETE FROM movConta + WHERE enlazadoSage = FALSE + AND Asiento <> 1 ; + + CALL clientSupplier_add(vCompanyFk); + CALL pgc_add(vCompanyFk); + CALL invoiceOut_manager(vYear, vCompanyFk); + CALL invoiceIn_manager(vYear, vCompanyFk); + + INSERT INTO movConta(TipoEntrada, + Ejercicio, + CodigoEmpresa, + Asiento, + CargoAbono, + CodigoCuenta, + Contrapartida, + FechaAsiento, + Comentario, + ImporteAsiento, + NumeroPeriodo, + FechaGrabacion, + CodigoDivisa, + ImporteCambio, + ImporteDivisa, + FactorCambio, + IdProcesoIME, + TipoCarteraIME, + TipoAnaliticaIME, + StatusTraspasadoIME, + TipoImportacionIME, + Metalico347, + BaseIva1, + PorBaseCorrectora1, + PorIva1, + CuotaIva1, + PorRecargoEquivalencia1, + RecargoEquivalencia1, + CodigoTransaccion1, + BaseIva2, + PorBaseCorrectora2, + PorIva2, + CuotaIva2, + PorRecargoEquivalencia2, + RecargoEquivalencia2, + CodigoTransaccion2, + BaseIva3, + PorBaseCorrectora3, + PorIva3, + CuotaIva3, + PorRecargoEquivalencia3, + RecargoEquivalencia3, + CodigoTransaccion3, + BaseIva4, + PorBaseCorrectora4, + PorIva4, + CuotaIva4, + PorRecargoEquivalencia4, + RecargoEquivalencia4, + CodigoTransaccion4, + Año, + Serie, + Factura, + SuFacturaNo, + FechaFactura, + ImporteFactura, + TipoFactura, + CodigoCuentaFactura, + CifDni, + Nombre, + CodigoRetencion, + BaseRetencion, + PorRetencion, + ImporteRetencion, + SiglaNacion, + EjercicioFactura, + FechaOperacion, + Exclusion347, + MantenerAsiento, + ClaveOperacionFactura_, + TipoRectificativa, + FechaFacturaOriginal, + BaseImponibleOriginal, + CuotaIvaOriginal, + ClaseAbonoRectificativas, + RecargoEquivalenciaOriginal, + LibreA1, + CodigoIva1, + CodigoIva2, + CodigoIva3, + CodigoIva4, + IvaDeducible1, + IvaDeducible2, + IvaDeducible3, + IvaDeducible4, + Intracomunitaria + ) + SELECT 'EN' TipoEntrada, + YEAR(x.FECHA) Ejercicio, + company_getCode(vCompanyFk) AS CodigoEmpresa, + x.ASIEN Asiento, + IF(EURODEBE <> 0 OR (EURODEBE = 0 AND EUROHABER IS NULL), 'D', 'H') CargoAbono, + x.SUBCTA CodigoCuenta, + x.CONTRA Contrapartida, + x.FECHA FechaAsiento, + x.CONCEPTO Comentario, + IF(x.EURODEBE, x.EURODEBE, x.EUROHABER) ImporteAsiento, + MONTH(x.FECHA) NumeroPeriodo, + IF(sub2.FECREGCON IS NULL, sub2.FECHA_EX, sub2.FECREGCON) FechaGrabacion, + IF(x.CAMBIO, IFNULL(mci.CodigoDivisa, sub3.code), '') CodigoDivisa, + x.CAMBIO ImporteCambio, + IFNULL(x.DEBEME, x.HABERME) ImporteDivisa, + IF(x.CAMBIO, TRUE, FALSE) FactorCambio, + NULL IdProcesoIME, + 0 TipoCarteraIME, + 0 TipoAnaliticaIME, + 0 StatusTraspasadoIME, + 0 TipoImportacionIME, + x.METAL Metalico347, + mci.BaseIva1, + mci.PorBaseCorrectora1, + mci.PorIva1, + mci.CuotaIva1, + mci.PorRecargoEquivalencia1, + mci.RecargoEquivalencia1, + mci.CodigoTransaccion1, + mci.BaseIva2, + mci.PorBaseCorrectora2, + mci.PorIva2, + mci.CuotaIva2, + mci.PorRecargoEquivalencia2, + mci.RecargoEquivalencia2, + mci.CodigoTransaccion2, + mci.BaseIva3, + mci.PorBaseCorrectora3, + mci.PorIva3, + mci.CuotaIva3, + mci.PorRecargoEquivalencia3, + mci.RecargoEquivalencia3, + mci.CodigoTransaccion3, + mci.BaseIva4, + mci.PorBaseCorrectora4, + mci.PorIva4, + mci.CuotaIva4, + mci.PorRecargoEquivalencia4, + mci.RecargoEquivalencia4, + mci.CodigoTransaccion4, + mci.Año, + mci.Serie, + mci.Factura, + mci.SuFacturaNo, + mci.FechaFactura, + mci.ImporteFactura, + mci.TipoFactura, + mci.CodigoCuentaFactura, + mci.CifDni, + mci.Nombre, + mci.CodigoRetencion, + mci.BaseRetencion, + mci.PorRetencion, + mci.ImporteRetencion, + mci.SiglaNacion, + mci.EjercicioFactura, + mci.FechaOperacion, + mci.Exclusion347, + TRUE, + mci.ClaveOperacionFactura, + mci.TipoRectificativa, + mci.FechaFacturaOriginal, + mci.BaseImponibleOriginal, + mci.CuotaIvaOriginal, + mci.ClaseAbonoRectificativas, + mci.RecargoEquivalenciaOriginal, + mci.LibreA1, + mci.CodigoIva1, + mci.CodigoIva2, + mci.CodigoIva3, + mci.CodigoIva4, + mci.IvaDeducible1, + mci.IvaDeducible2, + mci.IvaDeducible3, + mci.IvaDeducible4, + mci.Intracomunitaria + FROM vn.XDiario x + LEFT JOIN movContaIVA mci ON mci.id = x.id + LEFT JOIN (SELECT * + FROM (SELECT DISTINCT ASIEN, FECREGCON, FECHA_EX + FROM vn.XDiario + WHERE enlazadoSage = FALSE + ORDER BY ASIEN, FECREGCON DESC, FECHA_EX DESC + LIMIT 10000000000000000000 + ) sub GROUP BY ASIEN + )sub2 ON sub2.ASIEN = x.ASIEN + LEFT JOIN ( SELECT DISTINCT(account),cu.code + FROM vn.bank b + JOIN vn.currency cu ON cu.id = b.currencyFk + WHERE cu.code <> 'EUR' -- no se informa cuando la divisa en EUR + )sub3 ON sub3.account = x.SUBCTA + WHERE x.enlazadoSage = FALSE + AND x.empresa_id = vCompanyFk + AND x.FECHA BETWEEN vDatedFrom AND vDatedTo; + +-- Metálicos + UPDATE movConta m + JOIN (SELECT Asiento, + c.socialName name, + c.fi, + n.SiglaNacion, + m.CodigoCuenta, + m.Contrapartida + FROM movConta m + LEFT JOIN vn.client c ON c.id = IF(m.CargoAbono = 'H', + CAST(SUBSTRING(m.CodigoCuenta, 3, LENGTH(m.CodigoCuenta)) AS UNSIGNED), + CAST(SUBSTRING(m.Contrapartida, 3, LENGTH(m.Contrapartida)) AS UNSIGNED)) + LEFT JOIN Naciones n ON n.countryFk = c.countryFk + WHERE m.Metalico347 = TRUE + AND m.enlazadoSage = FALSE + )sub ON m.Asiento = sub.Asiento + SET m.Metalico347 = TRUE, + m.TipoFactura = vInvoiceTypeInformativeCode, + m.CifDni = sub.fi, + m.Nombre = sub.name, + m.SiglaNacion = sub.SiglaNacion + WHERE m.enlazadoSage = FALSE; + + UPDATE movConta m + SET m.Metalico347 = FALSE, + m.TipoFactura = '' + WHERE m.CargoAbono = 'D' + AND m.enlazadoSage = FALSE; + +-- Elimina cuentas de cliente/proveedor que no se utilizarán en la importación + DELETE cp + FROM clientesProveedores cp + LEFT JOIN movConta mc ON mc.codigoCuenta = cp.codigoCuenta + AND mc.enlazadoSage = FALSE + WHERE mc.codigoCuenta IS NULL; + +-- Elimina cuentas contables que no se utilizarán en la importación + DELETE pc + FROM planCuentasPGC pc + LEFT JOIN movConta mc ON mc.codigoCuenta = pc.codigoCuenta + AND mc.enlazadoSage = FALSE + WHERE mc.codigoCuenta IS NULL; + +-- DUAS + UPDATE movConta mci + JOIN vn.XDiario x ON x.ASIEN = mci.Asiento + JOIN TiposIva ti ON ti.CodigoIva = x.IVA + JOIN vn.pgcMaster pm ON pm.code = mci.CodigoCuenta COLLATE utf8mb3_unicode_ci + SET mci.BaseIva1 = x.BASEEURO, + mci.PorIva1 = x.IVA, + mci.CuotaIva1 = CAST((x.IVA / 100) * x.BASEEURO AS DECIMAL(10, 2)), + mci.CodigoTransaccion1 = vDuaTransactionFk, + mci.CodigoIva1 = vTaxImportReducedFk, + mci.IvaDeducible1 = TRUE, + mci.FechaFacturaOriginal = x.FECHA_EX, + mci.SuFacturaNo = x.FACTURAEX, + mci.FechaOperacion = x.FECHA_OP, + mci.ImporteFactura = mci.ImporteFactura + x.BASEEURO + CAST((x.IVA / 100) * x.BASEEURO AS DECIMAL(10, 2)) + WHERE pm.description = 'HP Iva pendiente' + AND mci.enlazadoSage = FALSE + AND x.SERIE = vSerialDua COLLATE utf8mb3_unicode_ci + AND ti.Iva = 'I.V.A. 10% Nacional'; + + UPDATE movConta mci + JOIN vn.XDiario x ON x.ASIEN = mci.Asiento + JOIN TiposIva ti ON ti.CodigoIva = x.IVA + JOIN vn.pgcMaster pm ON pm.code = mci.CodigoCuenta COLLATE utf8mb3_unicode_ci + SET mci.BaseIva2 = x.BASEEURO , + mci.PorIva2 = x.IVA, + mci.CuotaIva2 = CAST((x.IVA / 100) * x.BASEEURO AS DECIMAL(10,2)), + mci.CodigoTransaccion2 = vDuaTransactionFk , + mci.CodigoIva2 = vTaxImportFk, + mci.IvaDeducible2 = TRUE, + mci.ImporteFactura = mci.ImporteFactura + x.BASEEURO + CAST((x.IVA / 100) * x.BASEEURO AS DECIMAL(10, 2)) + WHERE pm.description = 'HP Iva pendiente' + AND mci.enlazadoSage = FALSE + AND x.SERIE = vSerialDua COLLATE utf8mb3_unicode_ci + AND ti.Iva = 'I.V.A. 21%'; + + UPDATE movConta mci + JOIN vn.XDiario x ON x.ASIEN = mci.Asiento + JOIN TiposIva ti ON ti.CodigoIva = x.IVA + JOIN vn.pgcMaster pm ON pm.code = mci.CodigoCuenta COLLATE utf8mb3_unicode_ci + SET mci.BaseIva3 = x.BASEEURO , + mci.PorIva3 = x.IVA, + mci.CuotaIva3 = CAST((x.IVA / 100) * x.BASEEURO AS DECIMAL(10,2)), + mci.CodigoTransaccion3 = vDuaTransactionFk , + mci.CodigoIva3 = vTaxImportSuperReducedFk, + mci.IvaDeducible3 = TRUE, + mci.ImporteFactura = mci.ImporteFactura + x.BASEEURO + CAST((x.IVA / 100) * x.BASEEURO AS DECIMAL(10, 2)) + WHERE pm.description = 'HP Iva pendiente' + AND mci.enlazadoSage = FALSE + AND x.SERIE = vSerialDua COLLATE utf8mb3_unicode_ci + AND ti.Iva = 'I.V.A. 4%'; + +-- Rectificativas + UPDATE movConta mci + JOIN (SELECT x.ASIEN, x.FECHA_RT, x.SERIE_RT, x.FACTU_RT + FROM movConta mci + JOIN vn.XDiario x ON x.ASIEN = mci.Asiento + WHERE mci.TipoRectificativa > 0 + AND mci.enlazadoSage = FALSE + AND x.FACTU_RT IS NOT NULL + GROUP BY x.ASIEN + ) sub ON sub.ASIEN = mci.Asiento + SET mci.EjercicioFacturaOriginal = YEAR(sub.FECHA_RT), + mci.SerieFacturaOriginal = sub.SERIE_RT, + mci.NumeroFacturaOriginal = sub.FACTU_RT + WHERE mci.TipoRectificativa > 0 AND + mci.enlazadoSage = FALSE ; + +-- Exportaciones Andorras y Canarias cambia TT (la cuenta es compartida) + UPDATE movConta mci + SET CodigoTransaccion1 = vTransactionExportTaxFreeFk, + CodigoTransaccion2 = IF(CodigoTransaccion2 = 0, 0, vTransactionExportTaxFreeFk), + CodigoTransaccion3 = IF(CodigoTransaccion3 = 0, 0, vTransactionExportTaxFreeFk), + CodigoTransaccion4 = IF(CodigoTransaccion4 = 0, 0, vTransactionExportTaxFreeFk) + WHERE enlazadoSage = FALSE + AND (CodigoTransaccion1 = vTransactionExportFk + OR CodigoTransaccion2 = vTransactionExportFk + OR CodigoTransaccion3 = vTransactionExportFk + OR CodigoTransaccion4 = vTransactionExportFk) + AND SiglaNacion IN (vCountryCanariasCode COLLATE utf8mb3_unicode_ci, vCountryCeutaMelillaCode COLLATE utf8mb3_unicode_ci); + + UPDATE movConta mc + SET CodigoDivisa = 'USD', + FactorCambio = TRUE, + ImporteCambio = ABS( CAST( IF( ImporteDivisa <> 0 AND ImporteCambio = 0, ImporteAsiento / ImporteDivisa, ImporteCambio) AS DECIMAL( 10, 2))) + WHERE enlazadoSage = FALSE + AND (ImporteCambio <> 0 OR ImporteDivisa <> 0 OR FactorCambio); + + UPDATE movConta mc + SET importeDivisa= -importeDivisa + WHERE enlazadoSage = FALSE + AND importeDivisa > 0 + AND ImporteAsiento < 0; + + -- Comprobación que los importes e ivas sean correctos, avisa vía CAU + SELECT GROUP_CONCAT(Asiento ORDER BY Asiento ASC SEPARATOR ',') INTO vBookEntries + FROM(SELECT sub.Asiento + FROM (SELECT mc.Asiento, SUM(mc.ImporteAsiento) amount + FROM movConta mc + WHERE mc.enlazadoSage = FALSE + GROUP BY mc.Asiento)sub + JOIN (SELECT x.ASIEN, SUM(IFNULL(x.EURODEBE,0) + IFNULL(x.EUROHABER,0)) amount + FROM vn.XDiario x + WHERE x.enlazadoSage = FALSE + GROUP BY ASIEN)sub2 ON sub2.ASIEN = sub.Asiento + WHERE sub.amount <> sub2.amount + UNION ALL + SELECT sub.Asiento + FROM (SELECT Asiento, SUM(BaseIva1 + BaseIva2 + BaseIva3 + BaseIva4) amountTaxableBase + FROM movConta + WHERE TipoFactura <> 'I' + AND enlazadoSage = FALSE + GROUP BY Asiento) sub + JOIN (SELECT ASIEN, SUM(BASEEURO) amountTaxableBase + FROM (SELECT ASIEN, SUM(BASEEURO) BASEEURO + FROM vn.XDiario + WHERE FACTURA + AND auxiliar <> '*' + AND enlazadoSage = FALSE + GROUP BY FACTURA, ASIEN)sub3 + GROUP BY ASIEN) sub2 ON sub2.ASIEN = sub.Asiento + WHERE sub.amountTaxableBase<>sub2.amountTaxableBase + AND sub.amountTaxableBase/2 <> sub2.amountTaxableBase) sub; + + IF vBookEntries IS NOT NULL THEN + SELECT util.notification_send ("book-entries-imported-incorrectly", CONCAT('{"bookEntries":"', vBookEntries,'"}'), null); + END IF; +END$$ +DELIMITER ; diff --git a/db/changes/225201/00-notification_send.sql b/db/changes/225201/00-notification_send.sql new file mode 100644 index 000000000..a422cebac --- /dev/null +++ b/db/changes/225201/00-notification_send.sql @@ -0,0 +1,24 @@ +DROP FUNCTION IF EXISTS `util`.`notification_send`; + +DELIMITER $$ +$$ +CREATE DEFINER=`root`@`localhost` FUNCTION `util`.`notification_send`(vNotificationName VARCHAR(255), vParams TEXT, vAuthorFk INT) RETURNS int(11) + MODIFIES SQL DATA +BEGIN +/** + * Sends a notification. + * + * @param vNotificationName The notification name + * @param vParams The notification parameters formatted as JSON + * @param vAuthorFk The notification author or %NULL if there is no author + * @return The notification id + */ + + INSERT INTO notificationQueue + SET notificationFk = vNotificationName, + params = vParams, + authorFk = vAuthorFk; + + RETURN LAST_INSERT_ID(); +END$$ +DELIMITER ; diff --git a/db/changes/225201/00-utilNotification.sql b/db/changes/225201/00-utilNotification.sql new file mode 100644 index 000000000..5c80b81b3 --- /dev/null +++ b/db/changes/225201/00-utilNotification.sql @@ -0,0 +1,4 @@ +INSERT INTO `util`.`notification` (id, name, description) VALUES(3, 'book-entries-imported-incorrectly', 'accounting entries exported incorrectly'); +INSERT INTO `util`.`notificationAcl` (notificationFk, roleFk) VALUES(3, 5); +INSERT INTO `util`.`notificationSubscription` (notificationFk, userFk) VALUES(3, 19663); + diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index c4ce78658..762e5411a 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -1335,9 +1335,9 @@ INSERT INTO `vn`.`itemTypeTag`(`id`, `itemTypeFk`, `tagFk`, `priority`) CALL `vn`.`itemRefreshTags`(NULL); -INSERT INTO `vn`.`itemLog` (`id`, `originFk`, `userFk`, `action`, `description`) +INSERT INTO `vn`.`itemLog` (`id`, `originFk`, `userFk`, `action`, `description`, `changedModel`, `oldInstance`, `newInstance`, `changedModelId`, `changedModelValue`) VALUES - ('1', '1', '1', 'insert', 'We made a change!'); + ('1', '1', '1', 'insert', 'We made a change!', 'Item', '{}', '{}', 1, '1'); INSERT INTO `vn`.`recovery`(`id`, `clientFk`, `started`, `finished`, `amount`, `period`) VALUES @@ -2692,7 +2692,7 @@ INSERT INTO `util`.`notificationConfig` INSERT INTO `util`.`notification` (`id`, `name`, `description`) VALUES (1, 'print-email', 'notification fixture one'), - (3, 'supplier-pay-method-update', 'A supplier pay method has been updated'); + (4, 'supplier-pay-method-update', 'A supplier pay method has been updated'); INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`) VALUES @@ -2707,7 +2707,8 @@ INSERT INTO `util`.`notificationQueue` (`id`, `notificationFk`, `params`, `autho INSERT INTO `util`.`notificationSubscription` (`notificationFk`, `userFk`) VALUES (1, 1109), - (1, 1110); + (1, 1110), + (3, 1109); INSERT INTO `vn`.`routeConfig` (`id`, `defaultWorkCenterFk`) VALUES @@ -2745,10 +2746,15 @@ INSERT INTO `vn`.`osTicketConfig` (`id`, `host`, `user`, `password`, `oldStatus` VALUES (0, 'http://localhost:56596/scp', 'ostadmin', 'Admin1', 'open', 3, 60, 'Este CAU se ha cerrado automáticamente. Si el problema persiste responda a este mensaje.', 'localhost', 'osticket', 'osticket', 40003, 'reply', 1, 'all'); +INSERT INTO `vn`.`mdbApp` (`app`, `baselineBranchFk`, `userFk`, `locked`) + VALUES + ('foo', 'master', NULL, NULL), + ('bar', 'test', 9, util.VN_NOW()); INSERT INTO `vn`.`ticketLog` (`id`, `originFk`, `userFk`, `action`, `changedModel`, `oldInstance`, `newInstance`, `changedModelId`) VALUES (1, 1, 9, 'insert', 'Ticket', '{}', '{"clientFk":1, "nickname": "Bat cave"}', 1); + INSERT INTO `salix`.`url` (`appName`, `environment`, `url`) VALUES ('lilium', 'dev', 'http://localhost:8080/#/'), diff --git a/db/dump/structure.sql b/db/dump/structure.sql index 9a77662ea..47fdd6d74 100644 --- a/db/dump/structure.sql +++ b/db/dump/structure.sql @@ -16340,6 +16340,185 @@ CREATE TABLE `invoiceType` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3; /*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `movConta` +-- + +DROP TABLE IF EXISTS `movConta`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `movConta` ( + `OrdenMovimientos` int(11) NOT NULL AUTO_INCREMENT, + `MovPosicion` varchar(64) COLLATE utf8mb3_unicode_ci NOT NULL, + `Ejercicio` smallint(6) NOT NULL, + `CodigoEmpresa` smallint(6) NOT NULL, + `Asiento` int(11) NOT NULL, + `CargoAbono` varchar(1) COLLATE utf8mb3_unicode_ci NOT NULL, + `CodigoCuenta` varchar(15) CHARACTER SET utf8mb3 NOT NULL, + `Contrapartida` varchar(15) COLLATE utf8mb3_unicode_ci NOT NULL, + `FechaAsiento` datetime NOT NULL, + `TipoDocumento` varchar(6) COLLATE utf8mb3_unicode_ci NOT NULL, + `DocumentoConta` varchar(9) COLLATE utf8mb3_unicode_ci NOT NULL, + `Comentario` varchar(40) COLLATE utf8mb3_unicode_ci NOT NULL, + `ImporteAsiento` decimal(28,10) NOT NULL, + `CodigoDiario` smallint(6) NOT NULL, + `CodigoCanal` varchar(10) COLLATE utf8mb3_unicode_ci NOT NULL, + `CodigoActividad` varchar(1) COLLATE utf8mb3_unicode_ci NOT NULL, + `FechaVencimiento` datetime DEFAULT NULL, + `NumeroPeriodo` smallint(6) NOT NULL, + `CodigoUsuario` smallint(6) NOT NULL, + `FechaGrabacion` datetime NOT NULL, + `TipoEntrada` varchar(2) COLLATE utf8mb3_unicode_ci NOT NULL, + `CodigoDepartamento` varchar(10) COLLATE utf8mb3_unicode_ci NOT NULL, + `CodigoSeccion` varchar(10) COLLATE utf8mb3_unicode_ci NOT NULL, + `CodigoDivisa` varchar(3) COLLATE utf8mb3_unicode_ci NOT NULL, + `ImporteCambio` decimal(28,10) NOT NULL, + `ImporteDivisa` decimal(28,10) NOT NULL, + `FactorCambio` decimal(28,10) NOT NULL, + `CodigoProyecto` varchar(10) COLLATE utf8mb3_unicode_ci NOT NULL, + `LibreN1` int(11) NOT NULL, + `LibreN2` int(11) NOT NULL, + `LibreA1` varchar(15) COLLATE utf8mb3_unicode_ci NOT NULL, + `LibreA2` varchar(15) COLLATE utf8mb3_unicode_ci NOT NULL, + `IdDelegacion` varchar(10) COLLATE utf8mb3_unicode_ci NOT NULL, + `MovCartera` varchar(64) COLLATE utf8mb3_unicode_ci DEFAULT NULL, + `IdProcesoIME` varchar(64) COLLATE utf8mb3_unicode_ci NOT NULL, + `TipoCarteraIME` smallint(6) NOT NULL, + `TipoAnaliticaIME` smallint(6) NOT NULL, + `StatusTraspasadoIME` tinyint(4) NOT NULL, + `TipoImportacionIME` tinyint(4) NOT NULL, + `BaseIva1` decimal(28,10) NOT NULL, + `PorBaseCorrectora1` decimal(28,10) NOT NULL, + `PorIva1` decimal(28,10) NOT NULL, + `CuotaIva1` decimal(28,10) NOT NULL, + `PorRecargoEquivalencia1` decimal(28,10) NOT NULL, + `RecargoEquivalencia1` decimal(28,10) NOT NULL, + `CodigoTransaccion1` tinyint(4) NOT NULL, + `BaseIva2` decimal(28,10) NOT NULL, + `PorBaseCorrectora2` decimal(28,10) NOT NULL, + `PorIva2` decimal(28,10) NOT NULL, + `CuotaIva2` decimal(28,10) NOT NULL, + `PorRecargoEquivalencia2` decimal(28,10) NOT NULL, + `RecargoEquivalencia2` decimal(28,10) NOT NULL, + `CodigoTransaccion2` tinyint(4) NOT NULL, + `BaseIva3` decimal(28,10) NOT NULL, + `PorBaseCorrectora3` decimal(28,10) NOT NULL, + `PorIva3` decimal(28,10) NOT NULL, + `CuotaIva3` decimal(28,10) NOT NULL, + `PorRecargoEquivalencia3` decimal(28,10) NOT NULL, + `RecargoEquivalencia3` decimal(28,10) NOT NULL, + `CodigoTransaccion3` tinyint(4) NOT NULL, + `baseIva4` decimal(28,10) NOT NULL, + `PorBaseCorrectora4` decimal(28,10) NOT NULL, + `PorIva4` decimal(28,10) NOT NULL, + `CuotaIva4` decimal(28,10) NOT NULL, + `PorRecargoEquivalencia4` decimal(28,10) NOT NULL, + `RecargoEquivalencia4` decimal(28,10) NOT NULL, + `CodigoTransaccion4` tinyint(4) NOT NULL, + `Año` smallint(6) NOT NULL, + `Serie` varchar(10) COLLATE utf8mb3_unicode_ci NOT NULL, + `Factura` int(11) NOT NULL, + `SuFacturaNo` varchar(40) COLLATE utf8mb3_unicode_ci NOT NULL, + `FechaFactura` datetime NOT NULL, + `ImporteFactura` decimal(28,10) NOT NULL, + `TipoFactura` varchar(1) COLLATE utf8mb3_unicode_ci NOT NULL, + `CodigoCuentaFactura` varchar(15) COLLATE utf8mb3_unicode_ci NOT NULL, + `CifDni` varchar(13) COLLATE utf8mb3_unicode_ci NOT NULL, + `Nombre` varchar(35) COLLATE utf8mb3_unicode_ci NOT NULL, + `CodigoRetencion` smallint(6) NOT NULL, + `BaseRetencion` decimal(28,10) NOT NULL, + `PorRetencion` decimal(28,10) NOT NULL, + `ImporteRetencion` decimal(28,10) NOT NULL, + `AbonoIva` smallint(6) NOT NULL, + `CodigoActividadF` varchar(1) COLLATE utf8mb3_unicode_ci NOT NULL, + `Intracomunitaria` smallint(6) NOT NULL, + `CodigoTerritorio` smallint(6) NOT NULL, + `SiglaNacion` varchar(2) COLLATE utf8mb3_unicode_ci NOT NULL, + `RetencionInformativa` smallint(6) NOT NULL, + `EjercicioFacturaOriginal` smallint(6) NOT NULL, + `SerieFacturaOriginal` varchar(10) COLLATE utf8mb3_unicode_ci NOT NULL, + `NumeroFacturaOriginal` int(11) NOT NULL, + `EjercicioFactura` smallint(6) NOT NULL, + `CobroPagoRetencion` varchar(1) COLLATE utf8mb3_unicode_ci NOT NULL, + `FechaOperacion` datetime NOT NULL, + `Exclusion347` smallint(6) NOT NULL, + `MovIdentificadorIME` varchar(64) COLLATE utf8mb3_unicode_ci NOT NULL, + `Previsiones` varchar(1) COLLATE utf8mb3_unicode_ci NOT NULL, + `MantenerAsiento` tinyint(4) NOT NULL, + `OrdenMovIME` smallint(6) NOT NULL, + `Metalico347` smallint(6) NOT NULL, + `ClaveOperacionFactura_` varchar(1) COLLATE utf8mb3_unicode_ci NOT NULL, + `SerieAgrupacion_` varchar(10) COLLATE utf8mb3_unicode_ci NOT NULL, + `NumeroFacturaInicial_` int(11) NOT NULL, + `NumeroFacturaFinal_` int(11) NOT NULL, + `IdAsientoExterno` text COLLATE utf8mb3_unicode_ci NOT NULL, + `IdDiarioExterno` varchar(10) COLLATE utf8mb3_unicode_ci NOT NULL, + `IdFacturaExterno` text COLLATE utf8mb3_unicode_ci NOT NULL, + `IdMovimiento` varchar(40) COLLATE utf8mb3_unicode_ci NOT NULL, + `IdCuadre` smallint(6) NOT NULL, + `FechaCuadre` datetime NOT NULL, + `TipoCuadre` varchar(4) COLLATE utf8mb3_unicode_ci NOT NULL, + `AgrupacionCuadre` int(11) NOT NULL, + `StatusSaldo` smallint(6) NOT NULL, + `StatusConciliacion` smallint(6) NOT NULL, + `CodigoConciliacion` int(11) NOT NULL, + `FechaConciliacion` datetime NOT NULL, + `TipoConciliacion` smallint(6) NOT NULL, + `IndicadorContaBanco` varchar(1) COLLATE utf8mb3_unicode_ci NOT NULL, + `Descripcion3` varchar(40) COLLATE utf8mb3_unicode_ci NOT NULL, + `Descripcion4` varchar(40) COLLATE utf8mb3_unicode_ci NOT NULL, + `Descripcion5` varchar(40) COLLATE utf8mb3_unicode_ci NOT NULL, + `Descripcion6` varchar(40) COLLATE utf8mb3_unicode_ci NOT NULL, + `Descripcion7` varchar(40) COLLATE utf8mb3_unicode_ci NOT NULL, + `Descripcion8` text COLLATE utf8mb3_unicode_ci NOT NULL, + `Descripcion9` text COLLATE utf8mb3_unicode_ci NOT NULL, + `Descripcion2` text COLLATE utf8mb3_unicode_ci NOT NULL, + `Descripcion1` text COLLATE utf8mb3_unicode_ci NOT NULL, + `Punteo1` smallint(6) NOT NULL, + `Punteo9` smallint(6) NOT NULL, + `Punteo8` smallint(6) NOT NULL, + `Punteo7` smallint(6) NOT NULL, + `Punteo6` smallint(6) NOT NULL, + `Punteo5` smallint(6) NOT NULL, + `Punteo4` smallint(6) NOT NULL, + `Punteo3` smallint(6) NOT NULL, + `Punteo2` smallint(6) NOT NULL, + `CodigoIva1` smallint(6) NOT NULL, + `CodigoIva2` smallint(6) NOT NULL, + `CodigoIva3` smallint(6) NOT NULL, + `CodigoIva4` smallint(6) NOT NULL, + `CriterioIva` tinyint(4) NOT NULL, + `FechaMaxVencimiento` datetime NOT NULL, + `TipoCriterioCaja` tinyint(4) NOT NULL, + `MovFacturaOrigenIME` text COLLATE utf8mb3_unicode_ci NOT NULL, + `IdFacturaExternoFinal` text COLLATE utf8mb3_unicode_ci NOT NULL, + `IdFacturaExternoInicial` text COLLATE utf8mb3_unicode_ci NOT NULL, + `IdFacturaExternoOriginal` text COLLATE utf8mb3_unicode_ci NOT NULL, + `NumFacturasExternoAgrupacion` int(11) NOT NULL, + `CodigoMedioCobro` varchar(1) COLLATE utf8mb3_unicode_ci NOT NULL, + `MedioCobro` varchar(31) COLLATE utf8mb3_unicode_ci NOT NULL, + `IvaDeducible1` smallint(6) NOT NULL DEFAULT 1, + `IvaDeducible2` smallint(6) NOT NULL DEFAULT 1, + `IvaDeducible3` smallint(6) NOT NULL DEFAULT 1, + `IvaDeducible4` smallint(6) NOT NULL DEFAULT 1, + `TipoRectificativa` smallint(6) NOT NULL, + `FechaFacturaOriginal` datetime NOT NULL, + `BaseImponibleOriginal` decimal(28,10) NOT NULL, + `CuotaIvaOriginal` decimal(28,10) NOT NULL, + `ClaseAbonoRectificativas` smallint(6) NOT NULL, + `RecargoEquivalenciaOriginal` decimal(28,10) NOT NULL, + `ObjetoFactura` text COLLATE utf8mb3_unicode_ci NOT NULL, + `enlazadoSage` tinyint(1) NOT NULL DEFAULT 0, + PRIMARY KEY (`OrdenMovimientos`,`LibreN1`), + KEY `ix_movconta2` (`IdProcesoIME`), + KEY `CodigoCuenta` (`CodigoCuenta`), + KEY `movConta_Asiento` (`Asiento`), + KEY `ix_movconta` (`enlazadoSage`,`IdProcesoIME`), + KEY `movConta_IdProcesoIME` (`IdProcesoIME`), + KEY `movConta_Asiento2` (`Asiento`,`IdProcesoIME`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + -- -- Table structure for table `movContaIVA` -- diff --git a/db/export-structure.sh b/db/export-structure.sh index f52819c54..a4fd4a8c6 100755 --- a/db/export-structure.sh +++ b/db/export-structure.sh @@ -84,7 +84,6 @@ IGNORETABLES=( --ignore-table=vn.warehouseJoined --ignore-table=vn.workerTeam__ --ignore-table=vn.XDiario__ - --ignore-table=sage.movConta --ignore-table=sage.movContaCopia ) mysqldump \ diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 9dab10673..f6a299011 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -311,10 +311,12 @@ export default { firstMandateText: 'vn-client-mandate vn-card vn-table vn-tbody > vn-tr' }, clientLog: { - lastModificationPreviousValue: 'vn-client-log vn-table vn-td.before', - lastModificationCurrentValue: 'vn-client-log vn-table vn-td.after', - penultimateModificationPreviousValue: 'vn-client-log vn-table vn-tr:nth-child(2) vn-td.before', - penultimateModificationCurrentValue: 'vn-client-log vn-table vn-tr:nth-child(2) vn-td.after' + lastModificationPreviousValue: 'vn-client-log vn-tr table tr td.before', + lastModificationCurrentValue: 'vn-client-log vn-tr table tr td.after', + namePreviousValue: 'vn-client-log vn-tr table tr:nth-child(1) td.before', + nameCurrentValue: 'vn-client-log vn-tr table tr:nth-child(1) td.after', + activePreviousValue: 'vn-client-log vn-tr:nth-child(2) table tr:nth-child(2) td.before', + activeCurrentValue: 'vn-client-log vn-tr:nth-child(2) table tr:nth-child(2) td.after' }, clientBalance: { @@ -518,7 +520,7 @@ export default { }, itemLog: { anyLineCreated: 'vn-item-log > vn-log vn-tbody > vn-tr', - fifthLineCreatedProperty: 'vn-item-log > vn-log vn-tbody > vn-tr:nth-child(5) > vn-td > vn-one:nth-child(3) > div span:nth-child(2)', + fifthLineCreatedProperty: 'vn-item-log > vn-log vn-tbody > vn-tr:nth-child(5) table tr:nth-child(3) td.after', }, ticketSummary: { header: 'vn-ticket-summary > vn-card > h5', @@ -711,9 +713,10 @@ export default { ticketLog: { firstTD: 'vn-ticket-log vn-table vn-td:nth-child(1)', logButton: 'vn-left-menu a[ui-sref="ticket.card.log"]', - firstLogEntry: 'vn-ticket-log vn-data-viewer vn-tbody vn-tr', - changes: 'vn-ticket-log vn-data-viewer vn-tbody > vn-tr > vn-td:nth-child(7)', - id: 'vn-ticket-log vn-tr:nth-child(1) vn-one:nth-child(1) span' + user: 'vn-ticket-log vn-tbody vn-tr vn-td:nth-child(2)', + action: 'vn-ticket-log vn-tbody vn-tr vn-td:nth-child(4)', + changes: 'vn-ticket-log vn-data-viewer vn-tbody vn-tr table tr:nth-child(2) td.after', + id: 'vn-ticket-log vn-tr:nth-child(1) table tr:nth-child(1) td.before' }, ticketService: { addServiceButton: 'vn-ticket-service vn-icon-button[vn-tooltip="Add service"] > button', @@ -1123,7 +1126,7 @@ export default { undoChanges: 'vn-travel-basic-data vn-button[label="Undo changes"]' }, travelLog: { - firstLogFirstTD: 'vn-travel-log vn-tbody > vn-tr > vn-td:nth-child(1) > div' + firstLogFirstTD: 'vn-travel-log vn-tbody > vn-tr > vn-td:nth-child(5)' }, travelThermograph: { add: 'vn-travel-thermograph-index vn-float-button[icon="add"]', diff --git a/e2e/paths/02-client/07_edit_web_access.spec.js b/e2e/paths/02-client/07_edit_web_access.spec.js index 3d9ccee62..29b39f788 100644 --- a/e2e/paths/02-client/07_edit_web_access.spec.js +++ b/e2e/paths/02-client/07_edit_web_access.spec.js @@ -67,22 +67,22 @@ describe('Client Edit web access path', () => { }); it(`should confirm the last log shows the updated client name and no modifications on active checkbox`, async() => { - let lastModificationPreviousValue = await page - .waitToGetProperty(selectors.clientLog.lastModificationPreviousValue, 'innerText'); - let lastModificationCurrentValue = await page - .waitToGetProperty(selectors.clientLog.lastModificationCurrentValue, 'innerText'); + let namePreviousValue = await page + .waitToGetProperty(selectors.clientLog.namePreviousValue, 'innerText'); + let nameCurrentValue = await page + .waitToGetProperty(selectors.clientLog.nameCurrentValue, 'innerText'); - expect(lastModificationPreviousValue).toEqual('name MaxEisenhardt active false'); - expect(lastModificationCurrentValue).toEqual('name Legion active false'); + expect(namePreviousValue).toEqual('MaxEisenhardt'); + expect(nameCurrentValue).toEqual('Legion'); }); it(`should confirm the penultimate log shows the updated active and no modifications on client name`, async() => { - let penultimateModificationPreviousValue = await page - .waitToGetProperty(selectors.clientLog.penultimateModificationPreviousValue, 'innerText'); - let penultimateModificationCurrentValue = await page - .waitToGetProperty(selectors.clientLog.penultimateModificationCurrentValue, 'innerText'); + let activePreviousValue = await page + .waitToGetProperty(selectors.clientLog.activePreviousValue, 'innerText'); + let activeCurrentValue = await page + .waitToGetProperty(selectors.clientLog.activeCurrentValue, 'innerText'); - expect(penultimateModificationPreviousValue).toEqual('name MaxEisenhardt active true'); - expect(penultimateModificationCurrentValue).toEqual('name MaxEisenhardt active false'); + expect(activePreviousValue).toEqual('✓'); + expect(activeCurrentValue).toEqual('✗'); }); }); diff --git a/e2e/paths/02-client/13_log.spec.js b/e2e/paths/02-client/13_log.spec.js index 9b047a47c..8f186d842 100644 --- a/e2e/paths/02-client/13_log.spec.js +++ b/e2e/paths/02-client/13_log.spec.js @@ -43,7 +43,7 @@ describe('Client log path', () => { let lastModificationCurrentValue = await page. waitToGetProperty(selectors.clientLog.lastModificationCurrentValue, 'innerText'); - expect(lastModificationPreviousValue).toEqual('name DavidCharlesHaller'); - expect(lastModificationCurrentValue).toEqual('name this is a test'); + expect(lastModificationPreviousValue).toEqual('DavidCharlesHaller'); + expect(lastModificationCurrentValue).toEqual('this is a test'); }); }); 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 f970247e5..ae5e2fb0c 100644 --- a/e2e/paths/05-ticket/02_expeditions_and_log.spec.js +++ b/e2e/paths/05-ticket/02_expeditions_and_log.spec.js @@ -32,14 +32,17 @@ describe('Ticket expeditions and log path', () => { it(`should confirm the expedition deleted is shown now in the ticket log`, async() => { await page.accessToSection('ticket.card.log'); - const firstLogEntry = await page - .waitToGetProperty(selectors.ticketLog.firstLogEntry, 'innerText'); + const user = await page + .waitToGetProperty(selectors.ticketLog.user, 'innerText'); + + const action = await page + .waitToGetProperty(selectors.ticketLog.action, 'innerText'); const id = await page .waitToGetProperty(selectors.ticketLog.id, 'innerText'); - expect(firstLogEntry).toContain('production'); - expect(firstLogEntry).toContain('Deletes'); + expect(user).toContain('production'); + expect(action).toContain('Deletes'); expect(id).toEqual('2'); }); }); diff --git a/e2e/paths/05-ticket/17_log.spec.js b/e2e/paths/05-ticket/17_log.spec.js index 399eb647b..32829ee74 100644 --- a/e2e/paths/05-ticket/17_log.spec.js +++ b/e2e/paths/05-ticket/17_log.spec.js @@ -55,6 +55,6 @@ describe('Ticket log path', () => { const result = await page.waitToGetProperty(selectors.ticketLog.firstTD, 'innerText'); - expect(result.length).toBeGreaterThan('20'); + expect(result.length).toBeGreaterThan('15'); }); }); diff --git a/e2e/paths/13-supplier/02_basic_data.spec.js b/e2e/paths/13-supplier/02_basic_data.spec.js index 4f3c49512..9d86e11d4 100644 --- a/e2e/paths/13-supplier/02_basic_data.spec.js +++ b/e2e/paths/13-supplier/02_basic_data.spec.js @@ -70,8 +70,8 @@ describe('Supplier basic data path', () => { }); it('should check the changes have been recorded', async() => { - const result = await page.waitToGetProperty('#newInstance:nth-child(3)', 'innerText'); + const result = await page.waitToGetProperty('vn-tr table tr:nth-child(3) td.after', 'innerText'); - expect(result).toEqual('note Some notes'); + expect(result).toEqual('Some notes'); }); }); diff --git a/front/core/components/crud-model/crud-model.js b/front/core/components/crud-model/crud-model.js index 1095985dc..16b837d6a 100644 --- a/front/core/components/crud-model/crud-model.js +++ b/front/core/components/crud-model/crud-model.js @@ -14,6 +14,7 @@ export default class CrudModel extends ModelProxy { this.$q = $q; this.primaryKey = 'id'; this.autoLoad = false; + this.page = 1; } $onInit() { @@ -125,13 +126,20 @@ export default class CrudModel extends ModelProxy { } } - loadMore() { + loadMore(append) { if (!this.moreRows) return this.$q.resolve(); - let filter = Object.assign({}, this.currentFilter); - filter.skip = this.orgData ? this.orgData.length : 0; - return this.sendRequest(filter, true); + const filter = Object.assign({}, this.currentFilter); + if (append) + filter.skip = this.orgData ? this.orgData.length : 0; + + if (!append) { + this.page += 1; + filter.limit = this.page * this.limit; + } + + return this.sendRequest(filter, append); } clear() { diff --git a/front/core/components/crud-model/index.spec.js b/front/core/components/crud-model/index.spec.js index 8673e947f..7eab80405 100644 --- a/front/core/components/crud-model/index.spec.js +++ b/front/core/components/crud-model/index.spec.js @@ -148,7 +148,7 @@ describe('Component vnCrudModel', () => { controller.moreRows = true; - controller.loadMore(); + controller.loadMore(true); expect(controller.sendRequest).toHaveBeenCalledWith({'skip': 2}, true); }); diff --git a/front/core/components/drop-down/index.js b/front/core/components/drop-down/index.js index 08d0da6d0..302c1c6b5 100644 --- a/front/core/components/drop-down/index.js +++ b/front/core/components/drop-down/index.js @@ -212,12 +212,12 @@ export default class DropDown extends Popover { && !this.model.isLoading; if (shouldLoad) - this.model.loadMore(); + this.model.loadMore(true); } onLoadMoreClick(event) { if (event.defaultPrevented) return; - this.model.loadMore(); + this.model.loadMore(true); } onContainerClick(event) { diff --git a/front/core/components/model-proxy/model-proxy.js b/front/core/components/model-proxy/model-proxy.js index 26c28c803..0b8d7ebf9 100644 --- a/front/core/components/model-proxy/model-proxy.js +++ b/front/core/components/model-proxy/model-proxy.js @@ -374,9 +374,10 @@ export class Paginable { /** * When limit is enabled, loads the next set of rows. * + * @param {Boolean} append - Whether should append new data * @return {Promise} The request promise */ - loadMore() { - return Promise.resolve(); + loadMore(append) { + return Promise.resolve(append); } } diff --git a/front/core/components/pagination/pagination.js b/front/core/components/pagination/pagination.js index 9979be368..e7127734c 100644 --- a/front/core/components/pagination/pagination.js +++ b/front/core/components/pagination/pagination.js @@ -73,7 +73,7 @@ class Pagination extends Component { if (shouldLoad) { this.nLoads++; - this.model.loadMore(); + this.model.loadMore(false); this.$.$apply(); } } @@ -82,7 +82,7 @@ class Pagination extends Component { if (this.maxLoads > 0 && this.nLoads == this.maxLoads) this.nLoads = 0; - this.model.loadMore(); + this.model.loadMore(false); } $onDestroy() { diff --git a/front/package-lock.json b/front/package-lock.json index 6922cae53..256797df0 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -3,6 +3,13 @@ "version": "1.0.0", "lockfileVersion": 1, "requires": true, +<<<<<<< HEAD + "dependencies": { + "@uirouter/angularjs": { + "version": "1.0.29", + "resolved": "https://registry.npmjs.org/@uirouter/angularjs/-/angularjs-1.0.29.tgz", + "integrity": "sha512-RImWnBarNixkMto0o8stEaGwZmvhv5cnuOLXyMU2pY8MP2rgEF74ZNJTLeJCW14LR7XDUxVH8Mk8bPI6lxedmQ==", +======= "packages": { "": { "name": "salix-front", @@ -174,11 +181,22 @@ "version": "1.0.30", "resolved": "https://registry.npmjs.org/@uirouter/angularjs/-/angularjs-1.0.30.tgz", "integrity": "sha512-qkc3RFZc91S5K0gc/QVAXc9LGDPXjR04vDgG/11j8+yyZEuQojXxKxdLhKIepiPzqLmGRVqzBmBc27gtqaEeZg==", +>>>>>>> dev "requires": { "@uirouter/core": "6.0.8" } }, "@uirouter/core": { +<<<<<<< HEAD + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/@uirouter/core/-/core-6.0.7.tgz", + "integrity": "sha512-KUTJxL+6q0PiBnFx4/Z+Hsyg0pSGiaW5yZQeJmUxknecjpTbnXkLU8H2EqRn9N2B+qDRa7Jg8RcgeNDPY72O1w==" + }, + "angular": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/angular/-/angular-1.8.2.tgz", + "integrity": "sha512-IauMOej2xEe7/7Ennahkbb5qd/HFADiNuLSESz9Q27inmi32zB0lnAsFeLEWcox3Gd1F6YhNd1CP7/9IukJ0Gw==" +======= "version": "6.0.8", "resolved": "https://registry.npmjs.org/@uirouter/core/-/core-6.0.8.tgz", "integrity": "sha512-Gc/BAW47i4L54p8dqYCJJZuv2s3tqlXQ0fvl6Zp2xrblELPVfxmjnc0eurx3XwfQdaqm3T6uls6tQKkof/4QMw==" @@ -187,6 +205,7 @@ "version": "1.8.3", "resolved": "https://registry.npmjs.org/angular/-/angular-1.8.3.tgz", "integrity": "sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw==" +>>>>>>> dev }, "angular-animate": { "version": "1.8.2", @@ -202,17 +221,29 @@ } }, "angular-translate": { +<<<<<<< HEAD + "version": "2.18.4", + "resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.18.4.tgz", + "integrity": "sha512-KohNrkH6J9PK+VW0L/nsRTcg5Fw70Ajwwe3Jbfm54Pf9u9Fd+wuingoKv+h45mKf38eT+Ouu51FPua8VmZNoCw==", +======= "version": "2.19.0", "resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.19.0.tgz", "integrity": "sha512-Z/Fip5uUT2N85dPQ0sMEe1JdF5AehcDe4tg/9mWXNDVU531emHCg53ZND9Oe0dyNiGX5rWcJKmsL1Fujus1vGQ==", +>>>>>>> dev "requires": { "angular": "^1.8.0" } }, "angular-translate-loader-partial": { +<<<<<<< HEAD + "version": "2.18.4", + "resolved": "https://registry.npmjs.org/angular-translate-loader-partial/-/angular-translate-loader-partial-2.18.4.tgz", + "integrity": "sha512-bsjR+FbB0sdA2528E/ugwKdlPPQhA1looxLxI3otayBTFXBpED33besfSZhYAISLgNMSL038vSssfRUen9qD8w==", +======= "version": "2.19.0", "resolved": "https://registry.npmjs.org/angular-translate-loader-partial/-/angular-translate-loader-partial-2.19.0.tgz", "integrity": "sha512-NnMw13LMV4bPQmJK7/pZOZAnPxe0M5OtUHchADs5Gye7V7feonuEnrZ8e1CKhBlv9a7IQyWoqcBa4Lnhg8gk5w==", +>>>>>>> dev "requires": { "angular-translate": "~2.19.0" } @@ -253,9 +284,15 @@ } }, "moment": { +<<<<<<< HEAD + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" +======= "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" +>>>>>>> dev }, "oclazyload": { "version": "0.6.3", diff --git a/front/salix/components/log/index.html b/front/salix/components/log/index.html index 0a0449038..79dfcef8c 100644 --- a/front/salix/components/log/index.html +++ b/front/salix/components/log/index.html @@ -1,10 +1,12 @@ - @@ -13,81 +15,53 @@ Date - Author - Model - Action - Name - Before - After + User + Model + Action + Name + Changes {{::log.creationDate | date:'dd/MM/yyyy HH:mm'}} -
-
- Changed by: - {{::log.user.name || 'System' | translate}} - -
-
- Model: - {{::log.changedModel | dashIfEmpty}} -
-
- Action: - {{::$ctrl.actionsText[log.action] | dashIfEmpty}} -
-
- Name: - {{::log.changedModelValue | dashIfEmpty}} -
-
- + {{::log.user.name || 'System' | translate}} - + {{::log.changedModel}} - + {{::$ctrl.actionsText[log.action]}} - + {{::log.changedModelValue}} - - -
- - -
-
-
- - -
- - -
-
- -
- {{::log.description}} -
-
+ + + + + + + + + + + + + + + + +
FieldBeforeAfter
{{prop.name}}{{prop.old}}{{prop.new}}
+
+ {{::log.description}} +
@@ -96,4 +70,4 @@
- \ No newline at end of file + diff --git a/front/salix/components/log/index.js b/front/salix/components/log/index.js index f30878b9f..1c54aa9b8 100644 --- a/front/salix/components/log/index.js +++ b/front/salix/components/log/index.js @@ -2,15 +2,17 @@ import ngModule from '../../module'; import Section from '../section'; import './style.scss'; +const validDate = /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$/; + export default class Controller extends Section { constructor($element, $) { super($element, $); this.actionsText = { - 'insert': 'Creates', - 'update': 'Updates', - 'delete': 'Deletes', - 'select': 'Views' - }; ``; + insert: 'Creates', + update: 'Updates', + delete: 'Deletes', + select: 'Views' + }; this.filter = { include: [{ relation: 'user', @@ -33,32 +35,57 @@ export default class Controller extends Section { set logs(value) { this._logs = value; - if (!value) return; - + if (!this.logs) return; + const empty = {}; const validations = window.validations; - value.forEach(log => { - const locale = validations[log.changedModel] && validations[log.changedModel].locale ? validations[log.changedModel].locale : {}; + for (const log of value) { + const oldValues = log.oldInstance || empty; + const newValues = log.newInstance || empty; + const locale = validations[log.changedModel]?.locale || empty; - log.oldProperties = this.getInstance(log.oldInstance, locale); - log.newProperties = this.getInstance(log.newInstance, locale); - }); + let props = Object.keys(oldValues).concat(Object.keys(newValues)); + props = [...new Set(props)]; + + log.props = []; + for (const prop of props) { + log.props.push({ + name: locale[prop] || prop, + old: this.formatValue(oldValues[prop]), + new: this.formatValue(newValues[prop]) + }); + } + } } - getInstance(instance, locale) { - const properties = []; - let validDate = /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$/; + get showModelName() { + return !(this.changedModel && this.changedModelId); + } - if (typeof instance == 'object' && instance != null) { - Object.keys(instance).forEach(property => { - if (validDate.test(instance[property])) - instance[property] = new Date(instance[property]).toLocaleString('es-ES'); + formatValue(value) { + let type = typeof value; - const key = locale[property] || property; - properties.push({key, value: instance[property]}); - }); - return properties; + if (type === 'string' && validDate.test(value)) { + value = new Date(value); + type = typeof value; + } + + switch (type) { + case 'boolean': + return value ? '✓' : '✗'; + case 'object': + if (value instanceof Date) { + const hasZeroTime = + value.getHours() === 0 && + value.getMinutes() === 0 && + value.getSeconds() === 0; + const format = hasZeroTime ? 'dd/MM/yyyy' : 'dd/MM/yyyy HH:mm:ss'; + return this.$filter('date')(value, format); + } + else + return value; + default: + return value; } - return null; } showWorkerDescriptor(event, workerId) { @@ -73,6 +100,8 @@ ngModule.vnComponent('vnLog', { bindings: { model: '<', originId: '<', + changedModel: ' td { + padding: 2px; + } + & > td.field, + & > th.field { + width: 20%; + color: gray; + } + & > td.before, + & > th.before, + & > td.after, + & > th.after { + width: 40%; + } + } + } } .ellipsis { white-space: nowrap; @@ -40,4 +62,4 @@ vn-log { .alignSpan { overflow: hidden; display: inline-block; -} \ No newline at end of file +} diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 4885e34ec..a406b55a5 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -143,6 +143,7 @@ "Email verify": "Email verify", "Ticket merged": "Ticket [{{originId}}]({{{originFullPath}}}) ({{{originDated}}}) merged with [{{destinationId}}]({{{destinationFullPath}}}) ({{{destinationDated}}})", "Sale(s) blocked, please contact production": "Sale(s) blocked, please contact production", + "App locked": "App locked by user {{userId}}", "The sales of the receiver ticket can't be modified": "The sales of the receiver ticket can't be modified", "Receipt's bank was not found": "Receipt's bank was not found", "This receipt was not compensated": "This receipt was not compensated", diff --git a/loopback/locale/es.json b/loopback/locale/es.json index a500ff550..ea83b36c4 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -246,9 +246,11 @@ "Already has this status": "Ya tiene este estado", "There aren't records for this week": "No existen registros para esta semana", "Empty data source": "Origen de datos vacio", + "App locked": "Aplicación bloqueada por el usuario {{userId}}", "Email verify": "Correo de verificación", "Landing cannot be lesser than shipment": "Landing cannot be lesser than shipment", - "Receipt's bank was not found": "No se encontró el banco del recibo", - "This receipt was not compensated": "Este recibo no ha sido compensado", - "Client's email was not found": "No se encontró el email del cliente" -} + "Receipt's bank was not found": "No se encontró el banco del recibo", + "This receipt was not compensated": "Este recibo no ha sido compensado", + "Client's email was not found": "No se encontró el email del cliente", + "Aplicación bloqueada por el usuario 9": "Aplicación bloqueada por el usuario 9" +} \ No newline at end of file diff --git a/modules/mdb/back/methods/mdbApp/lock.js b/modules/mdb/back/methods/mdbApp/lock.js new file mode 100644 index 000000000..98e61fb53 --- /dev/null +++ b/modules/mdb/back/methods/mdbApp/lock.js @@ -0,0 +1,66 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('lock', { + description: 'Lock an app for the user', + accessType: 'WRITE', + accepts: [ + { + arg: 'appName', + type: 'string', + required: true, + description: 'The app name', + http: {source: 'path'} + + } + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/:appName/lock`, + verb: 'POST' + } + }); + + Self.lock = async(ctx, appName, options) => { + const models = Self.app.models; + const userId = ctx.req.accessToken.userId; + const myOptions = {}; + const $t = ctx.req.__; // $translate + + let tx; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const mdbApp = await models.MdbApp.findById(appName, {fields: ['app', 'locked', 'userFk']}, myOptions); + + if (mdbApp.locked) { + throw new UserError($t('App locked', { + userId: mdbApp.userFk + })); + } + + const updatedMdbApp = await mdbApp.updateAttributes({ + userFk: userId, + locked: new Date() + }, myOptions); + + if (tx) await tx.commit(); + + return updatedMdbApp; + } catch (e) { + if (tx) await tx.rollback(); + + throw e; + } + }; +}; diff --git a/modules/mdb/back/methods/mdbApp/specs/lock.spec.js b/modules/mdb/back/methods/mdbApp/specs/lock.spec.js new file mode 100644 index 000000000..162d0490a --- /dev/null +++ b/modules/mdb/back/methods/mdbApp/specs/lock.spec.js @@ -0,0 +1,51 @@ +const models = require('vn-loopback/server/server').models; + +describe('MdbApp lock()', () => { + it('should throw an error if the app is already locked', async() => { + const tx = await models.MdbApp.beginTransaction({}); + let error; + + try { + const options = {transaction: tx}; + const appName = 'bar'; + const developerId = 9; + const ctx = { + req: { + accessToken: {userId: developerId}, + __: () => {} + } + }; + + const result = await models.MdbApp.lock(ctx, appName, options); + + expect(result.locked).not.toBeNull(); + + await tx.rollback(); + } catch (e) { + error = e; + + await tx.rollback(); + } + + expect(error).toBeDefined(); + }); + + it(`should lock a mdb `, async() => { + const tx = await models.MdbApp.beginTransaction({}); + + try { + const options = {transaction: tx}; + const appName = 'foo'; + const developerId = 9; + const ctx = {req: {accessToken: {userId: developerId}}}; + + const result = await models.MdbApp.lock(ctx, appName, options); + + expect(result.locked).not.toBeNull(); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + } + }); +}); diff --git a/modules/mdb/back/methods/mdbApp/specs/unlock.spec.js b/modules/mdb/back/methods/mdbApp/specs/unlock.spec.js new file mode 100644 index 000000000..9f1678372 --- /dev/null +++ b/modules/mdb/back/methods/mdbApp/specs/unlock.spec.js @@ -0,0 +1,22 @@ +const models = require('vn-loopback/server/server').models; + +describe('MdbApp unlock()', () => { + it(`should unlock a mdb `, async() => { + const tx = await models.MdbApp.beginTransaction({}); + + try { + const options = {transaction: tx}; + const appName = 'bar'; + const developerId = 9; + const ctx = {req: {accessToken: {userId: developerId}}}; + + const result = await models.MdbApp.unlock(ctx, appName, options); + + expect(result.locked).toBeNull(); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + } + }); +}); diff --git a/modules/mdb/back/methods/mdbApp/unlock.js b/modules/mdb/back/methods/mdbApp/unlock.js new file mode 100644 index 000000000..6bf67ddf4 --- /dev/null +++ b/modules/mdb/back/methods/mdbApp/unlock.js @@ -0,0 +1,40 @@ +module.exports = Self => { + Self.remoteMethodCtx('unlock', { + description: 'Unlock an app for the user', + accessType: 'WRITE', + accepts: [ + { + arg: 'appName', + type: 'string', + required: true, + description: 'The app name', + http: {source: 'path'} + + } + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/:appName/unlock`, + verb: 'POST' + } + }); + + Self.unlock = async(ctx, appName, options) => { + const models = Self.app.models; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const mdbApp = await models.MdbApp.findById(appName, null, myOptions); + const updatedMdbApp = await mdbApp.updateAttributes({ + userFk: null, + locked: null + }, myOptions); + + return updatedMdbApp; + }; +}; diff --git a/modules/mdb/back/methods/mdbVersion/upload.js b/modules/mdb/back/methods/mdbVersion/upload.js index 57df35ce7..5dfe5d3ef 100644 --- a/modules/mdb/back/methods/mdbVersion/upload.js +++ b/modules/mdb/back/methods/mdbVersion/upload.js @@ -23,6 +23,12 @@ module.exports = Self => { type: 'string', required: true, description: `The branch name` + }, + { + arg: 'unlock', + type: 'boolean', + required: false, + description: `It allows unlock the app` } ], returns: { @@ -35,9 +41,11 @@ module.exports = Self => { } }); - Self.upload = async(ctx, appName, newVersion, branch, options) => { + Self.upload = async(ctx, appName, newVersion, branch, unlock, options) => { const models = Self.app.models; + const userId = ctx.req.accessToken.userId; const myOptions = {}; + const $t = ctx.req.__; // $translate const TempContainer = models.TempContainer; const AccessContainer = models.AccessContainer; @@ -55,6 +63,14 @@ module.exports = Self => { let srcFile; try { + const mdbApp = await models.MdbApp.findById(appName, null, myOptions); + + if (mdbApp.locked && mdbApp.userFk != userId) { + throw new UserError($t('App locked', { + userId: mdbApp.userFk + })); + } + const tempContainer = await TempContainer.container('access'); const uploaded = await TempContainer.upload(tempContainer.name, ctx.req, ctx.result, fileOptions); const files = Object.values(uploaded.files).map(file => { @@ -79,7 +95,7 @@ module.exports = Self => { const existBranch = await models.MdbBranch.findOne({ where: {name: branch} - }); + }, myOptions); if (!existBranch) throw new UserError('Not exist this branch'); @@ -108,7 +124,9 @@ module.exports = Self => { app: appName, branchFk: branch, version: newVersion - }); + }, myOptions); + + if (unlock) await models.MdbApp.unlock(ctx, appName, myOptions); if (tx) await tx.commit(); } catch (e) { diff --git a/modules/mdb/back/model-config.json b/modules/mdb/back/model-config.json index d5be8de87..6107f8790 100644 --- a/modules/mdb/back/model-config.json +++ b/modules/mdb/back/model-config.json @@ -1,4 +1,7 @@ { + "MdbApp": { + "dataSource": "vn" + }, "MdbBranch": { "dataSource": "vn" }, diff --git a/modules/mdb/back/models/mdbApp.js b/modules/mdb/back/models/mdbApp.js new file mode 100644 index 000000000..dce715573 --- /dev/null +++ b/modules/mdb/back/models/mdbApp.js @@ -0,0 +1,4 @@ +module.exports = Self => { + require('../methods/mdbApp/lock')(Self); + require('../methods/mdbApp/unlock')(Self); +}; diff --git a/modules/mdb/back/models/mdbApp.json b/modules/mdb/back/models/mdbApp.json new file mode 100644 index 000000000..868f8c1d0 --- /dev/null +++ b/modules/mdb/back/models/mdbApp.json @@ -0,0 +1,31 @@ +{ + "name": "MdbApp", + "base": "VnModel", + "options": { + "mysql": { + "table": "mdbApp" + } + }, + "properties": { + "app": { + "type": "string", + "description": "The app name", + "id": true + }, + "locked": { + "type": "date" + } + }, + "relations": { + "branch": { + "type": "belongsTo", + "model": "MdbBranch", + "foreignKey": "baselineBranchFk" + }, + "user": { + "type": "belongsTo", + "model": "MdbBranch", + "foreignKey": "userFk" + } + } +} diff --git a/modules/monitor/back/methods/sales-monitor/clientsFilter.js b/modules/monitor/back/methods/sales-monitor/clientsFilter.js index 3756a706b..09ea24eb1 100644 --- a/modules/monitor/back/methods/sales-monitor/clientsFilter.js +++ b/modules/monitor/back/methods/sales-monitor/clientsFilter.js @@ -38,6 +38,7 @@ module.exports = Self => { date.setHours(0, 0, 0, 0); const stmt = new ParameterizedSQL(` SELECT + v.id, u.name AS salesPerson, IFNULL(sc.workerSubstitute, c.salesPersonFk) AS salesPersonFk, c.id AS clientFk, diff --git a/modules/monitor/front/index/clients/index.html b/modules/monitor/front/index/clients/index.html index 381c0e1ae..c0e3d1b14 100644 --- a/modules/monitor/front/index/clients/index.html +++ b/modules/monitor/front/index/clients/index.html @@ -61,7 +61,7 @@ - + {{::visit.dated | date:'dd/MM/yy'}} diff --git a/modules/monitor/front/index/orders/index.html b/modules/monitor/front/index/orders/index.html index 6126e23ef..74e80e40e 100644 --- a/modules/monitor/front/index/orders/index.html +++ b/modules/monitor/front/index/orders/index.html @@ -41,7 +41,7 @@ SalesPerson - diff --git a/modules/monitor/front/index/tickets/index.html b/modules/monitor/front/index/tickets/index.html index 138788ed6..2f7c34e2d 100644 --- a/modules/monitor/front/index/tickets/index.html +++ b/modules/monitor/front/index/tickets/index.html @@ -78,7 +78,7 @@ - +
+
+

{{ $t('title') }}

+

+
+
+ diff --git a/print/templates/email/book-entries-imported-incorrectly/book-entries-imported-incorrectly.js b/print/templates/email/book-entries-imported-incorrectly/book-entries-imported-incorrectly.js new file mode 100755 index 000000000..c8a427d30 --- /dev/null +++ b/print/templates/email/book-entries-imported-incorrectly/book-entries-imported-incorrectly.js @@ -0,0 +1,15 @@ +const Component = require(`vn-print/core/component`); +const emailBody = new Component('email-body'); + +module.exports = { + name: 'book-entries-imported-incorrectly', + components: { + 'email-body': emailBody.build(), + }, + props: { + bookEntries: { + type: String, + required: true + } + } +}; diff --git a/print/templates/email/book-entries-imported-incorrectly/locale/en.yml b/print/templates/email/book-entries-imported-incorrectly/locale/en.yml new file mode 100644 index 000000000..30c5dd292 --- /dev/null +++ b/print/templates/email/book-entries-imported-incorrectly/locale/en.yml @@ -0,0 +1,5 @@ +subject: Book entries imported incorrectly +title: Book entries imported incorrectly +description: There are book entries that differ between the info from XDiario and the one that has been imported into Sage.

+ Book entries nº {0}

+ If you consider that it is due to a computer error, forward this email to cau@verdnatura.es diff --git a/print/templates/email/book-entries-imported-incorrectly/locale/es.yml b/print/templates/email/book-entries-imported-incorrectly/locale/es.yml new file mode 100644 index 000000000..f48ad0a3b --- /dev/null +++ b/print/templates/email/book-entries-imported-incorrectly/locale/es.yml @@ -0,0 +1,5 @@ +subject: Asientos contables importados incorrectamente +title: Asientos contables importados incorrectamente +description: Existen asientos que difieren entre la info. de XDiario y la que se ha importado a Sage.

+ Asientos nº {0}

+ Si considera que es debido a un error informático, reenvíe este correo a cau@verdnatura.es diff --git a/print/templates/reports/expedition-pallet-label/assets/css/style.css b/print/templates/reports/expedition-pallet-label/assets/css/style.css index 68e91fb47..bd7366486 100644 --- a/print/templates/reports/expedition-pallet-label/assets/css/style.css +++ b/print/templates/reports/expedition-pallet-label/assets/css/style.css @@ -17,7 +17,6 @@ html { } .mainTable { width: 100%; - height: 100%; border: 10px solid; border-radius: 20px; -moz-border-radius: 20px; @@ -54,8 +53,17 @@ html { } #QR { padding: 25px; - padding-left: 40px; + margin-left: 35px; + float:left; +} +#barcode{ + text-align: center; +} +#right { + float: right; margin-top: 20px; + width: 250; + max-width: 250px; } #additionalInfo { padding-top: 20px; diff --git a/print/templates/reports/expedition-pallet-label/expedition-pallet-label.html b/print/templates/reports/expedition-pallet-label/expedition-pallet-label.html index 45c6ab463..e4360c79d 100644 --- a/print/templates/reports/expedition-pallet-label/expedition-pallet-label.html +++ b/print/templates/reports/expedition-pallet-label/expedition-pallet-label.html @@ -4,10 +4,11 @@ - + -
{{labelData.truck || '---'}}{{labelData.truck || '---'}}
+ +
@@ -25,11 +26,13 @@ - - diff --git a/print/templates/reports/expedition-pallet-label/expedition-pallet-label.js b/print/templates/reports/expedition-pallet-label/expedition-pallet-label.js index 3613da08f..0e8c297e8 100644 --- a/print/templates/reports/expedition-pallet-label/expedition-pallet-label.js +++ b/print/templates/reports/expedition-pallet-label/expedition-pallet-label.js @@ -1,5 +1,7 @@ const Component = require(`vn-print/core/component`); const reportBody = new Component('report-body'); +const jsBarcode = require('jsbarcode'); +const {DOMImplementation, XMLSerializer} = require('xmldom'); const UserError = require('vn-loopback/util/user-error'); const qrcode = require('qrcode'); @@ -39,6 +41,20 @@ module.exports = { const data = String(id); return qrcode.toDataURL(data, {margin: 0}); }, + getBarcode(id) { + const xmlSerializer = new XMLSerializer(); + const document = new DOMImplementation().createDocument('http://www.w3.org/1999/xhtml', 'html', null); + const svgNode = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + + jsBarcode(svgNode, id, { + xmlDocument: document, + format: 'code128', + displayValue: false, + width: 6, + height: 90, + }); + return xmlSerializer.serializeToString(svgNode); + }, }, components: { 'report-body': reportBody.build() diff --git a/print/templates/reports/expedition-pallet-label/options.json b/print/templates/reports/expedition-pallet-label/options.json index 269e058f0..5814b2c0f 100644 --- a/print/templates/reports/expedition-pallet-label/options.json +++ b/print/templates/reports/expedition-pallet-label/options.json @@ -2,8 +2,8 @@ "width": "10cm", "height": "15cm", "margin": { - "top": "0.5cm", - "right": "0.2cm", + "top": "0.3cm", + "right": "0.07cm", "bottom": "0cm", "left": "0cm" },
-
Pallet: {{id}}
-
User: {{username.name || '---'}}
-
Day: {{labelData.dayName.toUpperCase() || '---'}}
+
+ +