Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 4858-chat_realTime

This commit is contained in:
Alex Moreno 2023-01-04 09:48:46 +01:00
commit 8dfc588a7e
175 changed files with 3811 additions and 1395 deletions

17
CHANGELOG.md Normal file
View File

@ -0,0 +1,17 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2302.01] - 2023-01-12
### Added
- [General](Inicio) Permite recuperar la contraseña
### Changed
### Removed
- [Tickets](Control clientes) Eliminada sección

View File

@ -29,8 +29,12 @@ module.exports = Self => {
filter = mergeFilters(filter, {where});
const stmt = new ParameterizedSQL(
`SELECT * FROM campaign`);
`SELECT * FROM (`);
stmt.merge('SELECT * FROM campaign');
stmt.merge(conn.makeWhere(filter.where));
stmt.merge('ORDER BY dated ASC');
stmt.merge('LIMIT 10000000000000000000');
stmt.merge(') sub');
stmt.merge('GROUP BY code');
stmt.merge(conn.makePagination(filter));

View File

@ -26,11 +26,30 @@ module.exports = Self => {
Self.setSaleQuantity = async(saleId, quantity) => {
const models = Self.app.models;
const myOptions = {};
let tx;
const sale = await models.Sale.findById(saleId);
return await sale.updateAttributes({
originalQuantity: sale.quantity,
quantity: quantity
});
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const sale = await models.Sale.findById(saleId, null, myOptions);
const saleUpdated = await sale.updateAttributes({
originalQuantity: sale.quantity,
quantity: quantity
}, myOptions);
if (tx) await tx.commit();
return saleUpdated;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -2,15 +2,26 @@ const models = require('vn-loopback/server/server').models;
describe('setSaleQuantity()', () => {
it('should change quantity sale', async() => {
const saleId = 30;
const newQuantity = 10;
const tx = await models.Ticket.beginTransaction({});
const originalSale = await models.Sale.findById(saleId);
try {
const options = {transaction: tx};
await models.Collection.setSaleQuantity(saleId, newQuantity);
const updateSale = await models.Sale.findById(saleId);
const saleId = 30;
const newQuantity = 10;
expect(updateSale.originalQuantity).toEqual(originalSale.quantity);
expect(updateSale.quantity).toEqual(newQuantity);
const originalSale = await models.Sale.findById(saleId, null, options);
await models.Collection.setSaleQuantity(saleId, newQuantity, options);
const updateSale = await models.Sale.findById(saleId, null, options);
expect(updateSale.originalQuantity).toEqual(originalSale.quantity);
expect(updateSale.quantity).toEqual(newQuantity);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -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;
}
};
};

View File

@ -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;

View File

@ -12,14 +12,9 @@ BEGIN
* @param vAuthorFk The notification author or %NULL if there is no author
* @return The notification id
*/
DECLARE vNotificationFk INT;
SELECT id INTO vNotificationFk
FROM `notification`
WHERE `name` = vNotificationName;
INSERT INTO notificationQueue
SET notificationFk = vNotificationFk,
SET notificationFk = vNotificationName,
params = vParams,
authorFk = vAuthorFk;

View File

View File

@ -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');

View File

@ -0,0 +1 @@
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`) VALUES ('Dms','saveSign','*','ALLOW','employee');

View File

@ -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 ;

View File

@ -0,0 +1,3 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('TicketLog', 'getChanges', 'READ', 'ALLOW', 'ROLE', 'employee');

View File

@ -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

View File

@ -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 ;

View File

@ -0,0 +1,46 @@
DROP TRIGGER IF EXISTS `vn`.`supplier_beforeUpdate`;
USE `vn`;
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` TRIGGER `vn`.`supplier_beforeUpdate`
BEFORE UPDATE ON `supplier`
FOR EACH ROW
BEGIN
DECLARE vHasChange BOOL;
DECLARE vPayMethodChanged BOOL;
DECLARE vPayMethodHasVerified BOOL;
DECLARE vParams JSON;
DECLARE vOldPayMethodName VARCHAR(20);
DECLARE vNewPayMethodName VARCHAR(20);
SELECT hasVerified INTO vPayMethodHasVerified
FROM payMethod
WHERE id = NEW.payMethodFk;
SET vPayMethodChanged = NOT(NEW.payMethodFk <=> OLD.payMethodFk);
IF vPayMethodChanged THEN
SELECT name INTO vOldPayMethodName
FROM payMethod
WHERE id = OLD.payMethodFk;
SELECT name INTO vNewPayMethodName
FROM payMethod
WHERE id = NEW.payMethodFk;
SET vParams = JSON_OBJECT(
'name', NEW.name,
'oldPayMethod', vOldPayMethodName,
'newPayMethod', vNewPayMethodName
);
SELECT util.notification_send('supplier-pay-method-update', vParams, NULL) INTO @id;
END IF;
SET vHasChange = NOT(NEW.payDemFk <=> OLD.payDemFk AND NEW.payDay <=> OLD.payDay) OR vPayMethodChanged;
IF vHasChange AND vPayMethodHasVerified THEN
SET NEW.isPayMethodChecked = FALSE;
END IF;
END$$
DELIMITER ;

View File

@ -0,0 +1,8 @@
CREATE TABLE `vn`.`ticketSms` (
`smsFk` mediumint(8) unsigned NOT NULL,
`ticketFk` int(11) DEFAULT NULL,
PRIMARY KEY (`smsFk`),
KEY `ticketSms_FK_1` (`ticketFk`),
CONSTRAINT `ticketSms_FK` FOREIGN KEY (`smsFk`) REFERENCES `sms` (`id`) ON UPDATE CASCADE,
CONSTRAINT `ticketSms_FK_1` FOREIGN KEY (`ticketFk`) REFERENCES `ticket` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci

View File

@ -0,0 +1,104 @@
DROP PROCEDURE IF EXISTS `vn`.`ticket_canAdvance`;
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_canAdvance`(vDateFuture DATE, vDateToAdvance DATE, vWarehouseFk INT)
BEGIN
/**
* Devuelve los tickets y la cantidad de lineas de venta que se pueden adelantar.
*
* @param vDateFuture Fecha de los tickets que se quieren adelantar.
* @param vDateToAdvance Fecha a cuando se quiere adelantar.
* @param vWarehouseFk Almacén
*/
DECLARE vDateInventory DATE;
SELECT inventoried INTO vDateInventory FROM vn.config;
DROP TEMPORARY TABLE IF EXISTS tmp.stock;
CREATE TEMPORARY TABLE tmp.stock
(itemFk INT PRIMARY KEY,
amount INT)
ENGINE = MEMORY;
INSERT INTO tmp.stock(itemFk, amount)
SELECT itemFk, SUM(quantity) amount FROM
(
SELECT itemFk, quantity
FROM vn.itemTicketOut
WHERE shipped >= vDateInventory
AND shipped < vDateFuture
AND warehouseFk = vWarehouseFk
UNION ALL
SELECT itemFk, quantity
FROM vn.itemEntryIn
WHERE landed >= vDateInventory
AND landed < vDateFuture
AND isVirtualStock = FALSE
AND warehouseInFk = vWarehouseFk
UNION ALL
SELECT itemFk, quantity
FROM vn.itemEntryOut
WHERE shipped >= vDateInventory
AND shipped < vDateFuture
AND warehouseOutFk = vWarehouseFk
) t
GROUP BY itemFk HAVING amount != 0;
DROP TEMPORARY TABLE IF EXISTS tmp.filter;
CREATE TEMPORARY TABLE tmp.filter
(INDEX (id))
SELECT s.ticketFk futureId,
t2.ticketFk id,
sum((s.quantity <= IFNULL(st.amount,0))) hasStock,
count(DISTINCT s.id) saleCount,
t2.state,
t2.stateCode,
st.name futureState,
st.code futureStateCode,
GROUP_CONCAT(DISTINCT ipt.code ORDER BY ipt.code) futureIpt,
t2.ipt,
t.workerFk,
CAST(sum(litros) AS DECIMAL(10,0)) liters,
CAST(count(*) AS DECIMAL(10,0)) `lines`,
t2.shipped,
t.shipped futureShipped,
t2.totalWithVat,
t.totalWithVat futureTotalWithVat
FROM vn.ticket t
JOIN vn.ticketState ts ON ts.ticketFk = t.id
JOIN vn.state st ON st.id = ts.stateFk
JOIN vn.saleVolume sv ON t.id = sv.ticketFk
JOIN (SELECT
t2.id ticketFk,
t2.addressFk,
st.name state,
st.code stateCode,
GROUP_CONCAT(DISTINCT ipt.code ORDER BY ipt.code) ipt,
t2.shipped,
t2.totalWithVat
FROM vn.ticket t2
JOIN vn.sale s ON s.ticketFk = t2.id
JOIN vn.item i ON i.id = s.itemFk
JOIN vn.ticketState ts ON ts.ticketFk = t2.id
JOIN vn.state st ON st.id = ts.stateFk
LEFT JOIN vn.itemPackingType ipt ON ipt.code = i.itemPackingTypeFk
WHERE t2.shipped BETWEEN vDateToAdvance AND util.dayend(vDateToAdvance)
AND t2.warehouseFk = vWarehouseFk
GROUP BY t2.id) t2 ON t2.addressFk = t.addressFk
JOIN vn.sale s ON s.ticketFk = t.id
JOIN vn.item i ON i.id = s.itemFk
LEFT JOIN vn.itemPackingType ipt ON ipt.code = i.itemPackingTypeFk
LEFT JOIN tmp.stock st ON st.itemFk = s.itemFk
WHERE t.shipped BETWEEN vDateFuture AND util.dayend(vDateFuture)
AND t.warehouseFk = vWarehouseFk
GROUP BY t.id;
DROP TEMPORARY TABLE tmp.stock;
END$$
DELIMITER ;
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('Ticket', 'getTicketsAdvance', 'READ', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1,73 @@
DROP PROCEDURE IF EXISTS `vn`.`ticket_canbePostponed`;
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_canbePostponed`(vOriginDated DATE, vFutureDated DATE, vWarehouseFk INT)
BEGIN
/**
* Devuelve un listado de tickets susceptibles de fusionarse con otros tickets en el futuro
*
* @param vOriginDated Fecha en cuestión
* @param vFutureDated Fecha en el futuro a sondear
* @param vWarehouseFk Identificador de vn.warehouse
*/
DROP TEMPORARY TABLE IF EXISTS tmp.filter;
CREATE TEMPORARY TABLE tmp.filter
(INDEX (id))
SELECT sv.ticketFk id,
sub2.id futureId,
GROUP_CONCAT(DISTINCT i.itemPackingTypeFk ORDER BY i.itemPackingTypeFk) ipt,
CAST(sum(litros) AS DECIMAL(10,0)) liters,
CAST(count(*) AS DECIMAL(10,0)) `lines`,
st.name state,
sub2.iptd futureIpt,
sub2.state futureState,
t.clientFk,
t.warehouseFk,
ts.alertLevel,
t.shipped,
sub2.shipped futureShipped,
t.workerFk,
st.code stateCode,
sub2.code futureStateCode
FROM vn.saleVolume sv
JOIN vn.sale s ON s.id = sv.saleFk
JOIN vn.item i ON i.id = s.itemFk
JOIN vn.ticket t ON t.id = sv.ticketFk
JOIN vn.address a ON a.id = t.addressFk
JOIN vn.province p ON p.id = a.provinceFk
JOIN vn.country c ON c.id = p.countryFk
JOIN vn.ticketState ts ON ts.ticketFk = t.id
JOIN vn.state st ON st.id = ts.stateFk
JOIN vn.alertLevel al ON al.id = ts.alertLevel
LEFT JOIN vn.ticketParking tp ON tp.ticketFk = t.id
LEFT JOIN (
SELECT *
FROM (
SELECT
t.addressFk,
t.id,
t.shipped,
st.name state,
st.code code,
GROUP_CONCAT(DISTINCT i.itemPackingTypeFk ORDER BY i.itemPackingTypeFk) iptd
FROM vn.ticket t
JOIN vn.ticketState ts ON ts.ticketFk = t.id
JOIN vn.state st ON st.id = ts.stateFk
JOIN vn.sale s ON s.ticketFk = t.id
JOIN vn.item i ON i.id = s.itemFk
WHERE t.shipped BETWEEN vFutureDated
AND util.dayend(vFutureDated)
AND t.warehouseFk = vWarehouseFk
GROUP BY t.id
) sub
GROUP BY sub.addressFk
) sub2 ON sub2.addressFk = t.addressFk AND t.id != sub2.id
WHERE t.shipped BETWEEN vOriginDated AND util.dayend(vOriginDated)
AND t.warehouseFk = vWarehouseFk
AND al.code = 'FREE'
AND tp.ticketFk IS NULL
GROUP BY sv.ticketFk
HAVING futureId;
END$$
DELIMITER ;

View File

@ -0,0 +1,2 @@
DROP PROCEDURE IF EXISTS `ticket_split`;
DROP PROCEDURE IF EXISTS `ticket_merge`;

View File

@ -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 IGNORE INTO `util`.`notificationSubscription` (notificationFk, userFk) VALUES(3, 19663);

View File

@ -0,0 +1,7 @@
CREATE TABLE `vn`.`stateI18n` (
`stateFk` tinyint(3) unsigned NOT NULL,
`lang` char(2) NOT NULL,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`stateFk`, `lang`),
CONSTRAINT `stateI18n_state_id` FOREIGN KEY (`stateFk`) REFERENCES `vn`.`state` (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;

View File

@ -0,0 +1,73 @@
INSERT INTO
`vn`.`stateI18n` (`stateFk`, `lang`, `name`)
VALUES
(1, 'en', 'Fix'),
(1, 'es', 'Arreglar'),
(2, 'en', 'Free'),
(2, 'es', 'Libre'),
(3, 'en', 'OK'),
(3, 'es', 'OK'),
(4, 'en', 'Printed'),
(4, 'es', 'Impreso'),
(5, 'en', 'Preparation'),
(5, 'es', 'Preparación'),
(6, 'en', 'In Review'),
(6, 'es', 'En Revisión'),
(7, 'en', 'Unfinished'),
(7, 'es', 'Sin Acabar'),
(8, 'en', 'Reviewed'),
(8, 'es', 'Revisado'),
(9, 'en', 'Fitting'),
(9, 'es', 'Encajando'),
(10, 'en', 'Fitted'),
(10, 'es', 'Encajado'),
(11, 'en', 'Billed'),
(11, 'es', 'Facturado'),
(12, 'en', 'Blocked'),
(12, 'es', 'Bloqueado'),
(13, 'en', 'In Delivery'),
(13, 'es', 'En Reparto'),
(14, 'en', 'Prepared'),
(14, 'es', 'Preparado'),
(15, 'en', 'Pending Collection'),
(15, 'es', 'Pendiente de Recogida'),
(16, 'en', 'Delivered'),
(16, 'es', 'Entregado'),
(20, 'en', 'Assigned'),
(20, 'es', 'Asignado'),
(21, 'en', 'Returned'),
(21, 'es', 'Retornado'),
(22, 'en', 'Pending to extend'),
(22, 'es', 'Pendiente ampliar'),
(23, 'en', 'URGENT'),
(23, 'es', 'URGENTE'),
(24, 'en', 'Chained'),
(24, 'es', 'Encadenado'),
(25, 'en', 'Shipping'),
(25, 'es', 'Embarcando'),
(26, 'en', 'Preparation'),
(26, 'es', 'Preparación previa'),
(27, 'en', 'Assisted preparation'),
(27, 'es', 'Preparación asistida'),
(28, 'en', 'Preparation OK'),
(28, 'es', 'Previa OK'),
(29, 'en', 'Preparation Printed'),
(29, 'es', 'Previa Impreso'),
(30, 'en', 'Shipped'),
(30, 'es', 'Embarcado'),
(31, 'en', 'Stowaway printed'),
(31, 'es', 'Polizón Impreso'),
(32, 'en', 'Stowaway OK'),
(32, 'es', 'Polizón OK'),
(33, 'en', 'Auto_Printed'),
(33, 'es', 'Auto_Impreso'),
(34, 'en', 'Pending payment'),
(34, 'es', 'Pendiente de pago'),
(35, 'en', 'Half-Embedded'),
(35, 'es', 'Semi-Encajado'),
(36, 'en', 'Preparation Reviewing'),
(36, 'es', 'Previa Revisando'),
(37, 'en', 'Preparation Reviewed'),
(37, 'es', 'Previa Revisado'),
(38, 'en', 'Preparation Chamber'),
(38, 'es', 'Preparación Cámara');

View File

@ -0,0 +1,2 @@
DELETE FROM `salix`.`ACL` WHERE model="SaleChecked";
DROP TABLE IF EXISTS `vn`.`saleChecked`;

View File

@ -690,7 +690,8 @@ INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeF
(27 ,NULL, 8, 1, NULL, util.VN_CURDATE(), util.VN_CURDATE(), 1101, 'Wolverine', 1, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
(28, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
(29, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
(30, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE());
(30, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
(31, 1, 8, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL + 2 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE());
INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `description`)
VALUES
@ -991,7 +992,8 @@ INSERT INTO `vn`.`sale`(`id`, `itemFk`, `ticketFk`, `concept`, `quantity`, `pric
(33, 5, 14, 'Ranged weapon pistol 9mm', 50, 1.79, 0, 0, 0, util.VN_CURDATE()),
(34, 4, 28, 'Melee weapon heavy shield 1x0.5m', 20, 1.72, 0, 0, 0, util.VN_CURDATE()),
(35, 4, 29, 'Melee weapon heavy shield 1x0.5m', 20, 1.72, 0, 0, 0, util.VN_CURDATE()),
(36, 4, 30, 'Melee weapon heavy shield 1x0.5m', 20, 1.72, 0, 0, 0, util.VN_CURDATE());
(36, 4, 30, 'Melee weapon heavy shield 1x0.5m', 20, 1.72, 0, 0, 0, util.VN_CURDATE()),
(37, 4, 31, 'Melee weapon heavy shield 1x0.5m', 20, 1.72, 0, 0, 0, util.VN_CURDATE());
INSERT INTO `vn`.`saleChecked`(`saleFk`, `isChecked`)
VALUES
@ -1333,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
@ -2650,7 +2652,7 @@ INSERT INTO `vn`.`mdbVersion` (`app`, `branchFk`, `version`)
INSERT INTO `vn`.`accountingConfig` (`id`, `minDate`, `maxDate`)
VALUES
(1, '2022-01-01', '2023-01-01');
(1, CONCAT(YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL -1 YEAR)), '-01-01'), CONCAT(YEAR(DATE_ADD(util.VN_CURDATE(), INTERVAL +1 YEAR)), '-01-01'));
INSERT INTO `vn`.`saleGroup` (`userFk`, `parkingFk`, `sectorFk`)
@ -2689,7 +2691,8 @@ INSERT INTO `util`.`notificationConfig`
INSERT INTO `util`.`notification` (`id`, `name`, `description`)
VALUES
(1, 'print-email', 'notification fixture one');
(1, 'print-email', 'notification fixture one'),
(4, 'supplier-pay-method-update', 'A supplier pay method has been updated');
INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`)
VALUES
@ -2704,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
@ -2730,14 +2734,27 @@ UPDATE `account`.`user`
SET `hasGrant` = 1
WHERE `id` = 66;
INSERT INTO `vn`.`ticketLog` (`originFk`, userFk, `action`, changedModel, oldInstance, newInstance, changedModelId, `description`)
VALUES
(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'");
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', '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/#/'),
@ -2746,3 +2763,4 @@ INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
INSERT INTO `vn`.`payDemDetail` (`id`, `detail`)
VALUES
(1, 1);

View File

@ -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`
--

View File

@ -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 \

View File

@ -30,7 +30,7 @@ export default {
firstModuleRemovePinIcon: 'vn-home a:nth-child(1) vn-icon[icon="remove_circle"]'
},
recoverPassword: {
recoverPasswordButton: 'vn-login a[ui-sref="recoverPassword"]',
recoverPasswordButton: 'vn-login a[ui-sref="recover-password"]',
email: 'vn-recover-password vn-textfield[ng-model="$ctrl.email"]',
sendEmailButton: 'vn-recover-password vn-submit',
},
@ -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',
@ -735,18 +738,16 @@ export default {
},
ticketFuture: {
openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]',
originDated: 'vn-date-picker[label="Origin ETD"]',
futureDated: 'vn-date-picker[label="Destination ETD"]',
shipped: 'vn-date-picker[label="Origin date"]',
tfShipped: 'vn-date-picker[label="Destination date"]',
originDated: 'vn-date-picker[label="Origin date"]',
futureDated: 'vn-date-picker[label="Destination date"]',
linesMax: 'vn-textfield[label="Max Lines"]',
litersMax: 'vn-textfield[label="Max Liters"]',
ipt: 'vn-autocomplete[label="Origin IPT"]',
tfIpt: 'vn-autocomplete[label="Destination IPT"]',
futureIpt: 'vn-autocomplete[label="Destination IPT"]',
tableIpt: 'vn-autocomplete[name="ipt"]',
tableTfIpt: 'vn-autocomplete[name="tfIpt"]',
tableFutureIpt: 'vn-autocomplete[name="futureIpt"]',
state: 'vn-autocomplete[label="Origin Grouped State"]',
tfState: 'vn-autocomplete[label="Destination Grouped State"]',
futureState: 'vn-autocomplete[label="Destination Grouped State"]',
warehouseFk: 'vn-autocomplete[label="Warehouse"]',
problems: 'vn-check[label="With problems"]',
tableButtonSearch: 'vn-button[vn-tooltip="Search"]',
@ -755,9 +756,34 @@ export default {
firstCheck: 'tbody > tr:nth-child(1) > td > vn-check',
multiCheck: 'vn-multi-check',
tableId: 'vn-textfield[name="id"]',
tableTfId: 'vn-textfield[name="ticketFuture"]',
tableLiters: 'vn-textfield[name="litersMax"]',
tableLines: 'vn-textfield[name="linesMax"]',
tableFutureId: 'vn-textfield[name="futureId"]',
tableLiters: 'vn-textfield[name="liters"]',
tableLines: 'vn-textfield[name="lines"]',
submit: 'vn-submit[label="Search"]',
table: 'tbody > tr:not(.empty-rows)'
},
ticketAdvance: {
openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]',
dateFuture: 'vn-date-picker[label="Origin date"]',
dateToAdvance: 'vn-date-picker[label="Destination date"]',
linesMax: 'vn-textfield[label="Max Lines"]',
litersMax: 'vn-textfield[label="Max Liters"]',
futureIpt: 'vn-autocomplete[label="Origin IPT"]',
ipt: 'vn-autocomplete[label="Destination IPT"]',
tableIpt: 'vn-autocomplete[name="ipt"]',
tableFutureIpt: 'vn-autocomplete[name="futureIpt"]',
futureState: 'vn-autocomplete[label="Origin Grouped State"]',
state: 'vn-autocomplete[label="Destination Grouped State"]',
warehouseFk: 'vn-autocomplete[label="Warehouse"]',
tableButtonSearch: 'vn-button[vn-tooltip="Search"]',
moveButton: 'vn-button[vn-tooltip="Advance tickets"]',
acceptButton: '.vn-confirm.shown button[response="accept"]',
multiCheck: 'vn-multi-check',
tableId: 'vn-textfield[name="id"]',
tableFutureId: 'vn-textfield[name="futureId"]',
tableLiters: 'vn-textfield[name="liters"]',
tableLines: 'vn-textfield[name="lines"]',
tableStock: 'vn-textfield[name="hasStock"]',
submit: 'vn-submit[label="Search"]',
table: 'tbody > tr:not(.empty-rows)'
},
@ -1100,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"]',

View File

@ -1,7 +1,7 @@
import selectors from '../../helpers/selectors';
import getBrowser from '../../helpers/puppeteer';
describe('Login path', async() => {
describe('RecoverPassword path', async() => {
let browser;
let page;
@ -10,7 +10,7 @@ describe('Login path', async() => {
page = browser.page;
await page.waitToClick(selectors.recoverPassword.recoverPasswordButton);
await page.waitForState('recoverPassword');
await page.waitForState('recover-password');
});
afterAll(async() => {

View File

@ -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('✗');
});
});

View File

@ -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');
});
});

View File

@ -58,6 +58,7 @@ describe('Worker time control path', () => {
});
it(`should return error when insert 'out' of first entry`, async() => {
pending('https://redmine.verdnatura.es/issues/4707');
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, eightAm);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
@ -68,6 +69,7 @@ describe('Worker time control path', () => {
});
it(`should insert 'in' monday`, async() => {
pending('https://redmine.verdnatura.es/issues/4707');
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, eightAm);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
@ -78,6 +80,7 @@ describe('Worker time control path', () => {
});
it(`should insert 'out' monday`, async() => {
pending('https://redmine.verdnatura.es/issues/4707');
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, fourPm);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
@ -88,11 +91,13 @@ describe('Worker time control path', () => {
});
it(`should check Hank Pym worked 8:20 hours`, async() => {
pending('https://redmine.verdnatura.es/issues/4707');
await page.waitForTextInElement(selectors.workerTimeControl.mondayWorkedHours, '08:20 h.');
await page.waitForTextInElement(selectors.workerTimeControl.weekWorkedHours, '08:20 h.');
});
it('should remove first entry of monday', async() => {
pending('https://redmine.verdnatura.es/issues/4707');
await page.waitForTextInElement(selectors.workerTimeControl.firstEntryOfMonday, eightAm);
await page.waitForTextInElement(selectors.workerTimeControl.secondEntryOfMonday, fourPm);
await page.waitToClick(selectors.workerTimeControl.firstEntryOfMondayDelete);
@ -103,6 +108,7 @@ describe('Worker time control path', () => {
});
it(`should be the 'out' the first entry of monday`, async() => {
pending('https://redmine.verdnatura.es/issues/4707');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText');
expect(result).toEqual(fourPm);

View File

@ -127,8 +127,8 @@ describe('Item regularize path', () => {
await page.waitForState('ticket.index');
});
it('should search for the ticket with id 31 once again', async() => {
await page.accessToSearchResult('31');
it('should search for the ticket missing once again', async() => {
await page.accessToSearchResult('Missing');
await page.waitForState('ticket.card.summary');
});

View File

@ -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');
});
});

View File

@ -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');
});
});

View File

@ -16,9 +16,6 @@ describe('Ticket Future path', () => {
await browser.close();
});
const now = new Date();
const tomorrow = new Date(now.getDate() + 1);
it('should show errors snackbar because of the required data', async() => {
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.clearInput(selectors.ticketFuture.warehouseFk);
@ -27,20 +24,6 @@ describe('Ticket Future path', () => {
expect(message.text).toContain('warehouseFk is a required argument');
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.clearInput(selectors.ticketFuture.litersMax);
await page.waitToClick(selectors.ticketFuture.submit);
message = await page.waitForSnackbar();
expect(message.text).toContain('litersMax is a required argument');
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.clearInput(selectors.ticketFuture.linesMax);
await page.waitToClick(selectors.ticketFuture.submit);
message = await page.waitForSnackbar();
expect(message.text).toContain('linesMax is a required argument');
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.clearInput(selectors.ticketFuture.futureDated);
await page.waitToClick(selectors.ticketFuture.submit);
@ -62,44 +45,13 @@ describe('Ticket Future path', () => {
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
});
it('should search with the origin shipped today', async() => {
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.pickDate(selectors.ticketFuture.shipped, now);
await page.waitToClick(selectors.ticketFuture.submit);
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
});
it('should search with the origin shipped tomorrow', async() => {
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.pickDate(selectors.ticketFuture.shipped, tomorrow);
await page.waitToClick(selectors.ticketFuture.submit);
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
});
it('should search with the destination shipped today', async() => {
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.clearInput(selectors.ticketFuture.shipped);
await page.pickDate(selectors.ticketFuture.tfShipped, now);
await page.waitToClick(selectors.ticketFuture.submit);
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
});
it('should search with the destination shipped tomorrow', async() => {
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.pickDate(selectors.ticketFuture.tfShipped, tomorrow);
await page.waitToClick(selectors.ticketFuture.submit);
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
});
it('should search with the origin IPT', async() => {
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.clearInput(selectors.ticketFuture.shipped);
await page.clearInput(selectors.ticketFuture.tfShipped);
await page.clearInput(selectors.ticketFuture.ipt);
await page.clearInput(selectors.ticketFuture.tfIpt);
await page.clearInput(selectors.ticketFuture.futureIpt);
await page.clearInput(selectors.ticketFuture.state);
await page.clearInput(selectors.ticketFuture.tfState);
await page.clearInput(selectors.ticketFuture.futureState);
await page.autocompleteSearch(selectors.ticketFuture.ipt, 'Horizontal');
await page.waitToClick(selectors.ticketFuture.submit);
@ -109,14 +61,12 @@ describe('Ticket Future path', () => {
it('should search with the destination IPT', async() => {
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.clearInput(selectors.ticketFuture.shipped);
await page.clearInput(selectors.ticketFuture.tfShipped);
await page.clearInput(selectors.ticketFuture.ipt);
await page.clearInput(selectors.ticketFuture.tfIpt);
await page.clearInput(selectors.ticketFuture.futureIpt);
await page.clearInput(selectors.ticketFuture.state);
await page.clearInput(selectors.ticketFuture.tfState);
await page.clearInput(selectors.ticketFuture.futureState);
await page.autocompleteSearch(selectors.ticketFuture.tfIpt, 'Horizontal');
await page.autocompleteSearch(selectors.ticketFuture.futureIpt, 'Horizontal');
await page.waitToClick(selectors.ticketFuture.submit);
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
});
@ -124,12 +74,10 @@ describe('Ticket Future path', () => {
it('should search with the origin grouped state', async() => {
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.clearInput(selectors.ticketFuture.shipped);
await page.clearInput(selectors.ticketFuture.tfShipped);
await page.clearInput(selectors.ticketFuture.ipt);
await page.clearInput(selectors.ticketFuture.tfIpt);
await page.clearInput(selectors.ticketFuture.futureIpt);
await page.clearInput(selectors.ticketFuture.state);
await page.clearInput(selectors.ticketFuture.tfState);
await page.clearInput(selectors.ticketFuture.futureState);
await page.autocompleteSearch(selectors.ticketFuture.state, 'Free');
await page.waitToClick(selectors.ticketFuture.submit);
@ -139,24 +87,20 @@ describe('Ticket Future path', () => {
it('should search with the destination grouped state', async() => {
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.clearInput(selectors.ticketFuture.shipped);
await page.clearInput(selectors.ticketFuture.tfShipped);
await page.clearInput(selectors.ticketFuture.ipt);
await page.clearInput(selectors.ticketFuture.tfIpt);
await page.clearInput(selectors.ticketFuture.futureIpt);
await page.clearInput(selectors.ticketFuture.state);
await page.clearInput(selectors.ticketFuture.tfState);
await page.clearInput(selectors.ticketFuture.futureState);
await page.autocompleteSearch(selectors.ticketFuture.tfState, 'Free');
await page.autocompleteSearch(selectors.ticketFuture.futureState, 'Free');
await page.waitToClick(selectors.ticketFuture.submit);
await page.waitForNumberOfElements(selectors.ticketFuture.table, 0);
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.clearInput(selectors.ticketFuture.shipped);
await page.clearInput(selectors.ticketFuture.tfShipped);
await page.clearInput(selectors.ticketFuture.ipt);
await page.clearInput(selectors.ticketFuture.tfIpt);
await page.clearInput(selectors.ticketFuture.futureIpt);
await page.clearInput(selectors.ticketFuture.state);
await page.clearInput(selectors.ticketFuture.tfState);
await page.clearInput(selectors.ticketFuture.futureState);
await page.waitToClick(selectors.ticketFuture.submit);
await page.waitForNumberOfElements(selectors.ticketFuture.table, 4);
@ -176,7 +120,7 @@ describe('Ticket Future path', () => {
it('should search in smart-table with an ID Destination', async() => {
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
await page.write(selectors.ticketFuture.tableTfId, '12');
await page.write(selectors.ticketFuture.tableFutureId, '12');
await page.keyboard.press('Enter');
await page.waitForNumberOfElements(selectors.ticketFuture.table, 5);
@ -199,7 +143,7 @@ describe('Ticket Future path', () => {
it('should search in smart-table with an IPT Destination', async() => {
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);
await page.autocompleteSearch(selectors.ticketFuture.tableTfIpt, 'Vertical');
await page.autocompleteSearch(selectors.ticketFuture.tableFutureIpt, 'Vertical');
await page.waitForNumberOfElements(selectors.ticketFuture.table, 1);
await page.waitToClick(selectors.ticketFuture.tableButtonSearch);

View File

@ -0,0 +1,162 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Ticket Advance path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('employee', 'ticket');
await page.accessToSection('ticket.advance');
});
afterAll(async() => {
await browser.close();
});
it('should show errors snackbar because of the required data', async() => {
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
await page.clearInput(selectors.ticketAdvance.warehouseFk);
await page.waitToClick(selectors.ticketAdvance.submit);
let message = await page.waitForSnackbar();
expect(message.text).toContain('warehouseFk is a required argument');
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
await page.clearInput(selectors.ticketAdvance.dateToAdvance);
await page.waitToClick(selectors.ticketAdvance.submit);
message = await page.waitForSnackbar();
expect(message.text).toContain('dateToAdvance is a required argument');
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
await page.clearInput(selectors.ticketAdvance.dateFuture);
await page.waitToClick(selectors.ticketAdvance.submit);
message = await page.waitForSnackbar();
expect(message.text).toContain('dateFuture is a required argument');
});
it('should search with the required data', async() => {
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
await page.waitToClick(selectors.ticketAdvance.submit);
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
});
it('should search with the origin IPT', async() => {
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
await page.autocompleteSearch(selectors.ticketAdvance.ipt, 'Horizontal');
await page.waitToClick(selectors.ticketAdvance.submit);
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 0);
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
await page.clearInput(selectors.ticketAdvance.ipt);
await page.waitToClick(selectors.ticketAdvance.submit);
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
});
it('should search with the destination IPT', async() => {
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
await page.autocompleteSearch(selectors.ticketAdvance.futureIpt, 'Horizontal');
await page.waitToClick(selectors.ticketAdvance.submit);
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 0);
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
await page.clearInput(selectors.ticketAdvance.futureIpt);
await page.waitToClick(selectors.ticketAdvance.submit);
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
});
it('should search with the origin grouped state', async() => {
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
await page.autocompleteSearch(selectors.ticketAdvance.futureState, 'Free');
await page.waitToClick(selectors.ticketAdvance.submit);
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
await page.clearInput(selectors.ticketAdvance.futureState);
await page.waitToClick(selectors.ticketAdvance.submit);
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
});
it('should search with the destination grouped state', async() => {
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
await page.autocompleteSearch(selectors.ticketAdvance.state, 'Free');
await page.waitToClick(selectors.ticketAdvance.submit);
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 0);
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
await page.clearInput(selectors.ticketAdvance.state);
await page.waitToClick(selectors.ticketAdvance.submit);
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
});
it('should search in smart-table with an IPT Origin', async() => {
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
await page.autocompleteSearch(selectors.ticketAdvance.tableFutureIpt, 'Vertical');
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
await page.waitToClick(selectors.ticketAdvance.submit);
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
});
it('should search in smart-table with an IPT Destination', async() => {
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
await page.autocompleteSearch(selectors.ticketAdvance.tableIpt, 'Vertical');
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
await page.waitToClick(selectors.ticketAdvance.submit);
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
});
it('should search in smart-table with stock', async() => {
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
await page.write(selectors.ticketAdvance.tableStock, '5');
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 2);
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
await page.waitToClick(selectors.ticketAdvance.submit);
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
});
it('should search in smart-table with especified Lines', async() => {
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
await page.write(selectors.ticketAdvance.tableLines, '0');
await page.keyboard.press('Enter');
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
await page.waitToClick(selectors.ticketAdvance.submit);
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
});
it('should search in smart-table with especified Liters', async() => {
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
await page.write(selectors.ticketAdvance.tableLiters, '0');
await page.keyboard.press('Enter');
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
await page.waitToClick(selectors.ticketAdvance.submit);
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
});
it('should check the three last tickets and move to the future', async() => {
await page.waitToClick(selectors.ticketAdvance.multiCheck);
await page.waitToClick(selectors.ticketAdvance.moveButton);
await page.waitToClick(selectors.ticketAdvance.acceptButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Tickets moved successfully!');
});
});

View File

@ -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');
});
});

View File

@ -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() {

View File

@ -148,7 +148,7 @@ describe('Component vnCrudModel', () => {
controller.moreRows = true;
controller.loadMore();
controller.loadMore(true);
expect(controller.sendRequest).toHaveBeenCalledWith({'skip': 2}, true);
});

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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() {

View File

@ -147,7 +147,7 @@ export default class SmartTable extends Component {
for (const column of this.columns) {
if (viewConfig.configuration[column.field] == false) {
const baseSelector = `smart-table[view-config-id="${this.viewConfigId}"] table`;
selectors.push(`${baseSelector} thead > tr > th:nth-child(${column.index + 1})`);
selectors.push(`${baseSelector} thead > tr:not([second-header]) > th:nth-child(${column.index + 1})`);
selectors.push(`${baseSelector} tbody > tr > td:nth-child(${column.index + 1})`);
}
}
@ -235,7 +235,7 @@ export default class SmartTable extends Component {
}
registerColumns() {
const header = this.element.querySelector('thead > tr');
const header = this.element.querySelector('thead > tr:not([second-header])');
if (!header) return;
const columns = header.querySelectorAll('th');
@ -254,7 +254,7 @@ export default class SmartTable extends Component {
}
emptyDataRows() {
const header = this.element.querySelector('thead > tr');
const header = this.element.querySelector('thead > tr:not([second-header])');
const columns = header.querySelectorAll('th');
const tbody = this.element.querySelector('tbody');
if (tbody) {
@ -333,7 +333,7 @@ export default class SmartTable extends Component {
}
displaySearch() {
const header = this.element.querySelector('thead > tr');
const header = this.element.querySelector('thead > tr:not([second-header])');
if (!header) return;
const tbody = this.element.querySelector('tbody');

View File

@ -8,6 +8,16 @@ smart-table table {
& > thead {
border-bottom: $border;
& > tr[second-header] {
& > th
{
text-align: center;
border-bottom-style: groove;
font-weight: bold;
text-transform: uppercase;
}
}
& > * > th {
font-weight: normal;
}
@ -60,6 +70,9 @@ smart-table table {
vertical-align: middle;
}
}
&[separator]{
border-left-style: groove;
}
vn-icon.bright, i.bright {
color: #f7931e;
}

View File

@ -24,7 +24,7 @@ export default class Auth {
initialize() {
let criteria = {
to: state => {
const outLayout = ['login', 'recoverPassword', 'resetPassword'];
const outLayout = ['login', 'recover-password', 'reset-password'];
return !outLayout.some(ol => ol == state.name);
}
};

View File

@ -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",

View File

@ -1,8 +1,3 @@
<vn-layout
ng-if="$ctrl.showLayout">
</vn-layout>
<vn-out-layout
ng-if="!$ctrl.showLayout">
</vn-out-layout>
<ui-view></ui-view>
<vn-snackbar vn-id="snackbar"></vn-snackbar>
<vn-debug-info></vn-debug-info>

View File

@ -19,12 +19,6 @@ export default class App extends Component {
this.vnApp.logger = this;
}
get showLayout() {
const state = this.$state.current.name || this.$location.$$path.substring(1).replace('/', '.');
const outLayout = ['login', 'recoverPassword', 'resetPassword'];
return state && !outLayout.some(ol => ol == state);
}
$onDestroy() {
this.deregisterCallback();
this.vnApp.logger = null;

View File

@ -5,10 +5,10 @@ import './descriptor-popover';
import './home/home';
import './layout';
import './left-menu/left-menu';
import './login/index';
import './login/login';
import './login/recover-password';
import './login/reset-password';
import './login';
import './outLayout';
import './recover-password';
import './reset-password';
import './module-card';
import './module-main';
import './side-menu/side-menu';
@ -19,3 +19,4 @@ import './user-popover';
import './upload-photo';
import './bank-entity';
import './log';
import './sendSms';

View File

@ -3,6 +3,8 @@
url="{{$ctrl.url}}"
filter="$ctrl.filter"
link="{originFk: $ctrl.originId}"
where="{changedModel: $ctrl.changedModel,
changedModelId: $ctrl.changedModelId}"
data="$ctrl.logs"
limit="20"
auto-load="true">
@ -13,81 +15,53 @@
<vn-thead>
<vn-tr>
<vn-th field="creationDate">Date</vn-th>
<vn-th field="userFk" class="expendable" shrink>Author</vn-th>
<vn-th field="changedModel" class="expendable">Model</vn-th>
<vn-th field="action" class="expendable" shrink>Action</vn-th>
<vn-th field="changedModelValue" class="expendable">Name</vn-th>
<vn-th expand>Before</vn-th>
<vn-th expand>After</vn-th>
<vn-th field="userFk" shrink>User</vn-th>
<vn-th field="changedModel" ng-if="$ctrl.showModelName" shrink>Model</vn-th>
<vn-th field="action" shrink>Action</vn-th>
<vn-th field="changedModelValue" ng-if="$ctrl.showModelName">Name</vn-th>
<vn-th expand>Changes</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="log in $ctrl.logs">
<vn-td shrink-datetime>
{{::log.creationDate | date:'dd/MM/yyyy HH:mm'}}
<div class="changes">
<div>
<span translate class="label">Changed by</span><span class="label">: </span>
<span ng-class="{'link': log.user.worker.id, 'value': !log.user.worker.id}"
ng-click="$ctrl.showWorkerDescriptor($event, log.user.worker.id)"
translate>{{::log.user.name || 'System' | translate}}
</span>
</div>
<div>
<span translate class="label">Model</span><span class="label">: </span>
<span translate class="value">{{::log.changedModel | dashIfEmpty}}</span>
</div>
<div>
<span translate class="label">Action</span><span class="label">: </span>
<span translate class="value">{{::$ctrl.actionsText[log.action] | dashIfEmpty}}</span>
</div>
<div>
<span translate class="label">Name</span><span class="label">: </span>
<span translate class="value">{{::log.changedModelValue | dashIfEmpty}}</span>
</div>
</div>
</vn-td>
<vn-td class="expendable">
<vn-td>
<span ng-class="{'link': log.user.worker.id, 'value': !log.user.worker.id}"
ng-click="$ctrl.showWorkerDescriptor($event, log.user.worker.id)"
translate>{{::log.user.name || 'System' | translate}}
</span>
</vn-td>
<vn-td class="expendable">
<vn-td ng-if="$ctrl.showModelName">
{{::log.changedModel}}
</vn-td>
<vn-td translate class="expendable">
<vn-td shrink translate>
{{::$ctrl.actionsText[log.action]}}
</vn-td>
<vn-td class="expendable" expand>
<vn-td ng-if="$ctrl.showModelName">
{{::log.changedModelValue}}
</vn-td>
<vn-td expand class="before">
<vn-one ng-repeat="old in log.oldProperties">
<div>
<vn-label-value
no-ellipsize
label="{{::old.key}}"
value="{{::old.value}}">
</vn-label-value>
</div>
</vn-one>
</vn-td>
<vn-td expand class="after">
<vn-one ng-repeat="new in log.newProperties" ng-if="!log.description" id="newInstance">
<div>
<vn-label-value
no-ellipsize
label="{{::new.key}}"
value="{{::new.value}}">
</vn-label-value>
</div>
</vn-one>
<vn-one ng-if="!log.newProperties" id="description">
<div>
<span no-ellipsize>{{::log.description}}</span>
</div>
</vn-one>
<vn-td expand>
<table class="attributes">
<thead>
<tr>
<th translate class="field">Field</th>
<th translate>Before</th>
<th translate>After</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="prop in ::log.props">
<td class="field">{{prop.name}}</td>
<td class="before">{{prop.old}}</td>
<td class="after">{{prop.new}}</td>
</tr>
</tbody>
</table>
<div ng-if="log.description != null">
{{::log.description}}
</div>
</vn-td>
</vn-tr>
</vn-tbody>

View File

@ -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: '<?',
changedModelId: '<?',
url: '@'
}
});

View File

@ -12,3 +12,4 @@ Deletes: Elimina
Views: Visualiza
System: Sistema
note: nota
Changes: Cambios

View File

@ -23,6 +23,28 @@ vn-log {
display: block;
}
}
.attributes {
width: 100%;
tr {
height: 10px;
& > 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;

View File

@ -1,6 +1,27 @@
<div class="box">
<img src="./logo.svg"/>
<form name="form">
<ui-view></ui-view>
</form>
<vn-textfield
label="User"
ng-model="$ctrl.user"
vn-id="userField"
vn-focus>
</vn-textfield>
<vn-textfield
label="Password"
ng-model="$ctrl.password"
type="password">
</vn-textfield>
<vn-check
label="Do not close session"
ng-model="$ctrl.remember"
name="remember">
</vn-check>
<div class="footer">
<vn-submit label="Enter" ng-click="$ctrl.submit()"></vn-submit>
<div class="spinner-wrapper">
<vn-spinner enable="$ctrl.loading"></vn-spinner>
</div>
<div class="vn-pt-lg">
<a ui-sref="recover-password" translate>
I do not remember my password
</a>
</div>
</div>

View File

@ -1,16 +1,43 @@
import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss';
export default class OutLayout extends Component {
constructor($element, $scope) {
super($element, $scope);
/**
* A simple login form.
*/
export default class Controller {
constructor($, $element, vnAuth) {
Object.assign(this, {
$,
$element,
vnAuth,
user: localStorage.getItem('lastUser'),
remember: true
});
}
submit() {
this.loading = true;
this.vnAuth.login(this.user, this.password, this.remember)
.then(() => {
localStorage.setItem('lastUser', this.user);
this.loading = false;
})
.catch(err => {
this.loading = false;
this.password = '';
this.focusUser();
throw err;
});
}
focusUser() {
this.$.userField.select();
this.$.userField.focus();
}
}
Controller.$inject = ['$scope', '$element', 'vnAuth'];
OutLayout.$inject = ['$element', '$scope'];
ngModule.vnComponent('vnOutLayout', {
ngModule.vnComponent('vnLogin', {
template: require('./index.html'),
controller: OutLayout
controller: Controller
});

View File

@ -1,16 +1,5 @@
User: Usuario
Password: Contraseña
Email: Correo electrónico
Do not close session: No cerrar sesión
Enter: Entrar
I do not remember my password: No recuerdo mi contraseña
Recover password: Recuperar contraseña
We will sent you an email to recover your password: Te enviaremos un correo para restablecer tu contraseña
Notification sent!: ¡Notificación enviada!
Reset password: Restrablecer contraseña
New password: Nueva contraseña
Repeat password: Repetir contraseña
Password requirements: >
La contraseña debe tener al menos {{ length }} caracteres de longitud,
{{nAlpha}} caracteres alfabéticos, {{nUpper}} letras mayúsculas, {{nDigits}}
dígitos y {{nPunct}} símbolos (Ej: $%&.)

View File

@ -1,27 +0,0 @@
<vn-textfield
label="User"
ng-model="$ctrl.user"
vn-id="userField"
vn-focus>
</vn-textfield>
<vn-textfield
label="Password"
ng-model="$ctrl.password"
type="password">
</vn-textfield>
<vn-check
label="Do not close session"
ng-model="$ctrl.remember"
name="remember">
</vn-check>
<div class="footer">
<vn-submit label="Enter" ng-click="$ctrl.submit()"></vn-submit>
<div class="spinner-wrapper">
<vn-spinner enable="$ctrl.loading"></vn-spinner>
</div>
<div class="vn-pt-lg">
<a ui-sref="recoverPassword" translate>
I do not remember my password
</a>
</div>
</div>

View File

@ -1,43 +0,0 @@
import ngModule from '../../module';
import './style.scss';
/**
* A simple login form.
*/
export default class Controller {
constructor($, $element, vnAuth) {
Object.assign(this, {
$,
$element,
vnAuth,
user: localStorage.getItem('lastUser'),
remember: true
});
}
submit() {
this.loading = true;
this.vnAuth.login(this.user, this.password, this.remember)
.then(() => {
localStorage.setItem('lastUser', this.user);
this.loading = false;
})
.catch(err => {
this.loading = false;
this.password = '';
this.focusUser();
throw err;
});
}
focusUser() {
this.$.userField.select();
this.$.userField.focus();
}
}
Controller.$inject = ['$scope', '$element', 'vnAuth'];
ngModule.vnComponent('vnLogin', {
template: require('./login.html'),
controller: Controller
});

View File

@ -1,8 +1,6 @@
@import "variables";
vn-login,
vn-reset-password,
vn-recover-password{
vn-login{
.footer {
margin-top: 32px;
text-align: center;
@ -24,69 +22,3 @@ vn-recover-password{
}
}
}
vn-out-layout{
position: absolute;
height: 100%;
width: 100%;
margin: 0;
padding: 0;
color: $color-font;
font-size: 1.1rem;
font-weight: normal;
background-color: $color-bg-dark;
display: flex;
justify-content: center;
align-items: center;
overflow: auto;
& > .box {
box-sizing: border-box;
position: absolute;
max-width: 304px;
min-width: 240px;
padding: 48px;
background-color: $color-bg-panel;
box-shadow: 0 0 16px 0 rgba(0, 0, 0, .6);
border-radius: 8px;
& > img {
width: 100%;
padding-bottom: 16px;
}
& > form {
& > .vn-textfield {
width: 100%;
}
& > .vn-check {
display: block;
.md-label {
white-space: inherit;
}
}
}
h5{
color: $color-primary;
}
.text-secondary{
text-align: center;
padding-bottom: 16px;
}
}
@media screen and (max-width: 600px) {
background-color: $color-bg-panel;
& > .box {
padding: 16px;
box-shadow: none;
}
}
a{
color: $color-primary;
}
}

View File

@ -0,0 +1,6 @@
<div class="box">
<img src="./logo.svg"/>
<form name="form">
<ui-view></ui-view>
</form>
</div>

View File

@ -0,0 +1,16 @@
import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss';
export default class OutLayout extends Component {
constructor($element, $scope) {
super($element, $scope);
}
}
OutLayout.$inject = ['$element', '$scope'];
ngModule.vnComponent('vnOutLayout', {
template: require('./index.html'),
controller: OutLayout
});

View File

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -0,0 +1,67 @@
@import "variables";
vn-out-layout{
position: absolute;
height: 100%;
width: 100%;
margin: 0;
padding: 0;
color: $color-font;
font-size: 1.1rem;
font-weight: normal;
background-color: $color-bg-dark;
display: flex;
justify-content: center;
align-items: center;
overflow: auto;
& > .box {
box-sizing: border-box;
position: absolute;
max-width: 304px;
min-width: 240px;
padding: 48px;
background-color: $color-bg-panel;
box-shadow: 0 0 16px 0 rgba(0, 0, 0, .6);
border-radius: 8px;
& > img {
width: 100%;
padding-bottom: 16px;
}
& > form {
& > .vn-textfield {
width: 100%;
}
& > .vn-check {
display: block;
.md-label {
white-space: inherit;
}
}
}
h5{
color: $color-primary;
}
.text-secondary{
text-align: center;
padding-bottom: 16px;
}
}
@media screen and (max-width: 600px) {
background-color: $color-bg-panel;
& > .box {
padding: 16px;
box-shadow: none;
}
}
a{
color: $color-primary;
}
}

View File

@ -32,6 +32,6 @@ export default class Controller {
Controller.$inject = ['$scope', '$element', '$http', 'vnApp', '$translate', '$state'];
ngModule.vnComponent('vnRecoverPassword', {
template: require('./recover-password.html'),
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,3 @@
Recover password: Recuperar contraseña
We will sent you an email to recover your password: Te enviaremos un correo para restablecer tu contraseña
Notification sent!: ¡Notificación enviada!

View File

@ -0,0 +1,24 @@
@import "variables";
vn-recover-password{
.footer {
margin-top: 32px;
text-align: center;
position: relative;
& > .vn-submit {
display: block;
& > input {
display: block;
width: 100%;
}
}
& > .spinner-wrapper {
position: absolute;
width: 0;
top: 3px;
right: -8px;
overflow: visible;
}
}
}

View File

@ -43,6 +43,6 @@ export default class Controller {
Controller.$inject = ['$scope', '$element', '$http', 'vnApp', '$translate', '$state', '$location'];
ngModule.vnComponent('vnResetPassword', {
template: require('./reset-password.html'),
template: require('./index.html'),
controller: Controller
});

View File

@ -1,7 +1,3 @@
User: User
Password: Password
Do not close session: Do not close session
Enter: Enter
Password requirements: >
The password must have at least {{ length }} length characters,
{{nAlpha}} alphabetic characters, {{nUpper}} capital letters, {{nDigits}}

View File

@ -0,0 +1,8 @@
Reset password: Restrablecer contraseña
New password: Nueva contraseña
Repeat password: Repetir contraseñaç
Password changed!: ¡Contraseña cambiada!
Password requirements: >
La contraseña debe tener al menos {{ length }} caracteres de longitud,
{{nAlpha}} caracteres alfabéticos, {{nUpper}} letras mayúsculas, {{nDigits}}
dígitos y {{nPunct}} símbolos (Ej: $%&.)

View File

@ -0,0 +1,24 @@
@import "variables";
vn-reset-password{
.footer {
margin-top: 32px;
text-align: center;
position: relative;
& > .vn-submit {
display: block;
& > input {
display: block;
width: 100%;
}
}
& > .spinner-wrapper {
position: absolute;
width: 0;
top: 3px;
right: -8px;
overflow: visible;
}
}
}

View File

@ -1,19 +1,26 @@
import ngModule from '../module';
import Component from 'core/lib/component';
import ngModule from '../../module';
import './style.scss';
import Dialog from '../../../core/components/dialog';
export default class sendSmsDialog extends Dialog {
constructor($element, $scope, $http, $translate, vnApp) {
super($element, $scope, $http, $translate, vnApp);
new CustomEvent('openSmsDialog', {
detail: {
this: this
}
});
}
class Controller extends Component {
open() {
this.$.SMSDialog.show();
}
charactersRemaining() {
const element = this.$.message;
const value = element.input.value;
const element = this.sms.message;
const maxLength = 160;
const textAreaLength = new Blob([value]).size;
return maxLength - textAreaLength;
return maxLength - element.length;
}
onResponse() {
@ -25,23 +32,19 @@ class Controller extends Component {
if (this.charactersRemaining() < 0)
throw new Error(`The message it's too long`);
this.$http.post(`Tickets/${this.sms.ticketId}/sendSms`, this.sms).then(res => {
this.vnApp.showMessage(this.$t('SMS sent!'));
if (res.data) this.emit('send', {response: res.data});
});
return this.onSend({$sms: this.sms});
} catch (e) {
this.vnApp.showError(this.$t(e.message));
return false;
}
return true;
}
}
ngModule.vnComponent('vnTicketSms', {
ngModule.vnComponent('vnSmsDialog', {
template: require('./index.html'),
controller: Controller,
controller: sendSmsDialog,
bindings: {
sms: '<',
onSend: '&',
}
});

View File

@ -3,25 +3,38 @@ import getMainRoute from 'core/lib/get-main-route';
config.$inject = ['$stateProvider', '$urlRouterProvider'];
function config($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$urlRouterProvider
.otherwise('/');
$stateProvider
.state('layout', {
abstract: true,
template: '<vn-layout></vn-layout>',
})
.state('outLayout', {
abstract: true,
template: '<vn-out-layout></vn-out-layout>',
})
.state('login', {
parent: 'outLayout',
url: '/login?continue',
description: 'Login',
template: '<vn-login></vn-login>'
})
.state('recoverPassword', {
.state('recover-password', {
parent: 'outLayout',
url: '/recover-password',
description: 'Recover-password',
template: '<vn-recover-password>asd</vn-recover-password>'
description: 'Recover password',
template: '<vn-recover-password></vn-recover-password>'
})
.state('resetPassword', {
.state('reset-password', {
parent: 'outLayout',
url: '/reset-password',
description: 'Reset-password',
description: 'Reset password',
template: '<vn-reset-password></vn-reset-password>'
})
.state('home', {
parent: 'layout',
url: '/',
description: 'Home',
template: '<vn-home></vn-home>'
@ -52,6 +65,10 @@ function config($stateProvider, $urlRouterProvider) {
};
if (route.abstract)
configRoute.abstract = true;
if (!route.state.includes('.'))
configRoute.parent = 'layout';
if (route.routeParams)
configRoute.params = route.routeParams;

View File

@ -66,9 +66,10 @@
"MESSAGE_INSURANCE_CHANGE": "I have changed the insurence credit of client [{{clientName}} ({{clientId}})]({{{url}}}) to *{{credit}} €*",
"Changed client paymethod": "I have changed the pay method for client [{{clientName}} ({{clientId}})]({{{url}}})",
"Sent units from ticket": "I sent *{{quantity}}* units of [{{concept}} ({{itemId}})]({{{itemUrl}}}) to *\"{{nickname}}\"* coming from ticket id [{{ticketId}}]({{{ticketUrl}}})",
"Claim will be picked": "The product from the claim [{{claimId}}]({{{claimUrl}}}) from the client *{{clientName}}* will be picked",
"Claim state has changed to incomplete": "The state of the claim [{{claimId}}]({{{claimUrl}}}) from client *{{clientName}}* has changed to *incomplete*",
"Claim state has changed to canceled": "The state of the claim [{{claimId}}]({{{claimUrl}}}) from client *{{clientName}}* has changed to *canceled*",
"Change quantity": "{{concept}} change of {{oldQuantity}} to {{newQuantity}}",
"Claim will be picked": "The product from the claim [({{claimId}})]({{{claimUrl}}}) from the client *{{clientName}}* will be picked",
"Claim state has changed to incomplete": "The state of the claim [({{claimId}})]({{{claimUrl}}}) from client *{{clientName}}* has changed to *incomplete*",
"Claim state has changed to canceled": "The state of the claim [({{claimId}})]({{{claimUrl}}}) from client *{{clientName}}* has changed to *canceled*",
"Customs agent is required for a non UEE member": "Customs agent is required for a non UEE member",
"Incoterms is required for a non UEE member": "Incoterms is required for a non UEE member",
"Client checked as validated despite of duplication": "Client checked as validated despite of duplication from client id {{clientId}}",
@ -140,8 +141,10 @@
"You don't have grant privilege": "You don't have grant privilege",
"You don't own the role and you can't assign it to another user": "You don't own the role and you can't assign it to another user",
"Email verify": "Email verify",
"Ticket merged": "Ticket [{{id}}]({{{fullPath}}}) ({{{originDated}}}) merged with [{{tfId}}]({{{fullPathFuture}}}) ({{{futureDated}}})",
"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",
"Client's email was not found": "Client's email was not found"

View File

@ -134,9 +134,10 @@
"MESSAGE_INSURANCE_CHANGE": "He cambiado el crédito asegurado del cliente [{{clientName}} ({{clientId}})]({{{url}}}) a *{{credit}} €*",
"Changed client paymethod": "He cambiado la forma de pago del cliente [{{clientName}} ({{clientId}})]({{{url}}})",
"Sent units from ticket": "Envio *{{quantity}}* unidades de [{{concept}} ({{itemId}})]({{{itemUrl}}}) a *\"{{nickname}}\"* provenientes del ticket id [{{ticketId}}]({{{ticketUrl}}})",
"Claim will be picked": "Se recogerá el género de la reclamación [{{claimId}}]({{{claimUrl}}}) del cliente *{{clientName}}*",
"Claim state has changed to incomplete": "Se ha cambiado el estado de la reclamación [{{claimId}}]({{{claimUrl}}}) del cliente *{{clientName}}* a *incompleta*",
"Claim state has changed to canceled": "Se ha cambiado el estado de la reclamación [{{claimId}}]({{{claimUrl}}}) del cliente *{{clientName}}* a *anulado*",
"Change quantity": "{{concept}} cambia de {{oldQuantity}} a {{newQuantity}}",
"Claim will be picked": "Se recogerá el género de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}*",
"Claim state has changed to incomplete": "Se ha cambiado el estado de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}* a *incompleta*",
"Claim state has changed to canceled": "Se ha cambiado el estado de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}* a *anulado*",
"Client checked as validated despite of duplication": "Cliente comprobado a pesar de que existe el cliente id {{clientId}}",
"ORDER_ROW_UNAVAILABLE": "No hay disponibilidad de este producto",
"Distance must be lesser than 1000": "La distancia debe ser inferior a 1000",
@ -241,13 +242,15 @@
"Claim pickup order sent": "Reclamación Orden de recogida enviada [{{claimId}}]({{{claimUrl}}}) al cliente *{{clientName}}*",
"You don't have grant privilege": "No tienes privilegios para dar privilegios",
"You don't own the role and you can't assign it to another user": "No eres el propietario del rol y no puedes asignarlo a otro usuario",
"Ticket merged": "Ticket [{{id}}]({{{fullPath}}}) ({{{originDated}}}) fusionado con [{{tfId}}]({{{fullPathFuture}}}) ({{{futureDated}}})",
"Ticket merged": "Ticket [{{originId}}]({{{originFullPath}}}) ({{{originDated}}}) fusionado con [{{destinationId}}]({{{destinationFullPath}}}) ({{{destinationDated}}})",
"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"
}

View File

@ -104,6 +104,9 @@
"SageTransactionType": {
"dataSource": "vn"
},
"TicketSms": {
"dataSource": "vn"
},
"TpvError": {
"dataSource": "vn"
},

View File

@ -0,0 +1,28 @@
{
"name": "TicketSms",
"base": "VnModel",
"options": {
"mysql": {
"table": "ticketSms"
}
},
"properties": {
"smsFk": {
"type": "number",
"id": true,
"description": "Identifier"
}
},
"relations": {
"ticket": {
"type": "belongsTo",
"model": "Ticket",
"foreignKey": "ticketFk"
},
"sms": {
"type": "belongsTo",
"model": "Sms",
"foreignKey": "smsFk"
}
}
}

View File

@ -113,10 +113,11 @@
</div>
</slot-body>
</vn-descriptor-content>
<vn-client-sms
<vn-sms-dialog
vn-id="sms"
on-send="$ctrl.onSmsSend($sms)"
sms="$ctrl.newSMS">
</vn-client-sms>
</vn-sms-dialog>
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>

View File

@ -39,6 +39,11 @@ class Controller extends Descriptor {
};
this.$.sms.open();
}
onSmsSend(sms) {
return this.$http.post(`Clients/${this.id}/sendSms`, sms)
.then(() => this.vnApp.showSuccess(this.$t('SMS sent')));
}
}
ngModule.vnComponent('vnClientDescriptor', {

View File

@ -35,7 +35,6 @@ import './sample/index';
import './sample/create';
import './web-payment';
import './log';
import './sms';
import './postcode';
import './postcode/province';
import './postcode/city';

View File

@ -1,49 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
open() {
this.$.SMSDialog.show();
}
charactersRemaining() {
const element = this.$.message;
const value = element.input.value;
const maxLength = 160;
const textAreaLength = new Blob([value]).size;
return maxLength - textAreaLength;
}
onResponse() {
try {
if (!this.sms.destination)
throw new Error(`The destination can't be empty`);
if (!this.sms.message)
throw new Error(`The message can't be empty`);
if (this.charactersRemaining() < 0)
throw new Error(`The message it's too long`);
this.$http.post(`Clients/${this.$params.id}/sendSms`, this.sms).then(res => {
this.vnApp.showMessage(this.$t('SMS sent!'));
if (res.data) this.emit('send', {response: res.data});
});
} catch (e) {
this.vnApp.showError(this.$t(e.message));
return false;
}
return true;
}
}
Controller.$inject = ['$element', '$scope', '$http', '$translate', 'vnApp'];
ngModule.vnComponent('vnClientSms', {
template: require('./index.html'),
controller: Controller,
bindings: {
sms: '<',
}
});

View File

@ -1,74 +0,0 @@
import './index';
describe('Client', () => {
describe('Component vnClientSms', () => {
let controller;
let $httpBackend;
let $element;
beforeEach(ngModule('client'));
beforeEach(inject(($componentController, $rootScope, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
let $scope = $rootScope.$new();
$element = angular.element('<vn-dialog></vn-dialog>');
controller = $componentController('vnClientSms', {$element, $scope});
controller.client = {id: 1101};
controller.$params = {id: 1101};
controller.$.message = {
input: {
value: 'My SMS'
}
};
}));
describe('onResponse()', () => {
it('should perform a POST query and show a success snackbar', () => {
let params = {destinationFk: 1101, destination: 111111111, message: 'My SMS'};
controller.sms = {destinationFk: 1101, destination: 111111111, message: 'My SMS'};
jest.spyOn(controller.vnApp, 'showMessage');
$httpBackend.expect('POST', `Clients/1101/sendSms`, params).respond(200, params);
controller.onResponse();
$httpBackend.flush();
expect(controller.vnApp.showMessage).toHaveBeenCalledWith('SMS sent!');
});
it('should call onResponse without the destination and show an error snackbar', () => {
controller.sms = {destinationFk: 1101, message: 'My SMS'};
jest.spyOn(controller.vnApp, 'showError');
controller.onResponse('accept');
expect(controller.vnApp.showError).toHaveBeenCalledWith(`The destination can't be empty`);
});
it('should call onResponse without the message and show an error snackbar', () => {
controller.sms = {destinationFk: 1101, destination: 222222222};
jest.spyOn(controller.vnApp, 'showError');
controller.onResponse('accept');
expect(controller.vnApp.showError).toHaveBeenCalledWith(`The message can't be empty`);
});
});
describe('charactersRemaining()', () => {
it('should return the characters remaining in a element', () => {
controller.$.message = {
input: {
value: 'My message 0€'
}
};
let result = controller.charactersRemaining();
expect(result).toEqual(145);
});
});
});
});

View File

@ -73,6 +73,12 @@ export default class Controller extends Section {
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
itemSearchFunc($search) {
return /^\d+$/.test($search)
? {id: $search}
: {name: {like: '%' + $search + '%'}};
}
}
ngModule.vnComponent('vnEntryBuyIndex', {

View File

@ -9,31 +9,43 @@ module.exports = Self => {
accepts: [
{
arg: 'ids',
type: ['number'],
description: 'The invoice ids'
type: 'string',
description: 'The invoices ids',
}
],
returns: [
{
arg: 'body',
type: 'file',
root: true
}, {
arg: 'Content-Type',
type: 'string',
http: {target: 'header'}
}, {
arg: 'Content-Disposition',
type: 'string',
http: {target: 'header'}
}
],
returns: {
arg: 'base64',
type: 'string',
root: true
},
http: {
path: '/downloadZip',
verb: 'POST'
verb: 'GET'
}
});
Self.downloadZip = async function(ctx, ids, options) {
const models = Self.app.models;
const myOptions = {};
const zip = new JSZip();
if (typeof options == 'object')
Object.assign(myOptions, options);
const zip = new JSZip();
let totalSize = 0;
const zipConfig = await models.ZipConfig.findOne(null, myOptions);
let totalSize = 0;
ids = ids.split(',');
for (let id of ids) {
if (zipConfig && totalSize > zipConfig.maxSize) throw new UserError('Files are too large');
const invoiceOutPdf = await models.InvoiceOut.download(ctx, id, myOptions);
@ -44,8 +56,10 @@ module.exports = Self => {
totalSize += sizeInMegabytes;
zip.file(fileName, body);
}
const base64 = await zip.generateAsync({type: 'base64'});
return base64;
const stream = zip.generateNodeStream({streamFiles: true});
return [stream, 'application/zip', `filename="download.zip"`];
};
function extractFileName(str) {

View File

@ -3,7 +3,7 @@ const UserError = require('vn-loopback/util/user-error');
describe('InvoiceOut downloadZip()', () => {
const userId = 9;
const invoiceIds = [1, 2];
const invoiceIds = '1,2';
const ctx = {
req: {

View File

@ -29,13 +29,13 @@ export default class Controller extends Section {
window.open(url, '_blank');
} else {
const invoiceOutIds = this.checked;
const params = {
ids: invoiceOutIds
};
this.$http.post(`InvoiceOuts/downloadZip`, params)
.then(res => {
location.href = 'data:application/zip;base64,' + res.data;
});
const invoicesIds = invoiceOutIds.join(',');
const serializedParams = this.$httpParamSerializer({
access_token: this.vnToken.token,
ids: invoicesIds
});
const url = `api/InvoiceOuts/downloadZip?${serializedParams}`;
window.open(url, '_blank');
}
}
}

View File

@ -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;
}
};
};

View File

@ -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();
}
});
});

View File

@ -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();
}
});
});

View File

@ -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;
};
};

View File

@ -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) {

View File

@ -1,4 +1,7 @@
{
"MdbApp": {
"dataSource": "vn"
},
"MdbBranch": {
"dataSource": "vn"
},

Some files were not shown because too many files have changed in this diff Show More