Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 3302-logFormat

This commit is contained in:
Alexandre Riera 2022-12-23 08:01:52 +01:00
commit eefc5ab80d
116 changed files with 3106 additions and 883 deletions

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

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

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
@ -2690,7 +2692,7 @@ INSERT INTO `util`.`notificationConfig`
INSERT INTO `util`.`notification` (`id`, `name`, `description`)
VALUES
(1, 'print-email', 'notification fixture one'),
(3, 'supplier-pay-method-update', 'A supplier pay method has been updated');
(4, 'supplier-pay-method-update', 'A supplier pay method has been updated');
INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`)
VALUES
@ -2705,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
@ -2731,10 +2734,22 @@ 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);

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

@ -738,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"]',
@ -758,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)'
},

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

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

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

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

@ -19,3 +19,4 @@ import './user-popover';
import './upload-photo';
import './bank-entity';
import './log';
import './sendSms';

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

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

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

View File

@ -0,0 +1,4 @@
module.exports = Self => {
require('../methods/mdbApp/lock')(Self);
require('../methods/mdbApp/unlock')(Self);
};

View File

@ -0,0 +1,31 @@
{
"name": "MdbApp",
"base": "VnModel",
"options": {
"mysql": {
"table": "mdbApp"
}
},
"properties": {
"app": {
"type": "string",
"description": "The app name",
"id": true
},
"locked": {
"type": "date"
}
},
"relations": {
"branch": {
"type": "belongsTo",
"model": "MdbBranch",
"foreignKey": "baselineBranchFk"
},
"user": {
"type": "belongsTo",
"model": "MdbBranch",
"foreignKey": "userFk"
}
}
}

View File

@ -38,6 +38,7 @@ module.exports = Self => {
date.setHours(0, 0, 0, 0);
const stmt = new ParameterizedSQL(`
SELECT
v.id,
u.name AS salesPerson,
IFNULL(sc.workerSubstitute, c.salesPersonFk) AS salesPersonFk,
c.id AS clientFk,

View File

@ -61,7 +61,7 @@
</tr>
</thead>
<tbody>
<tr ng-repeat="visit in model.data">
<tr ng-repeat="visit in model.data track by visit.id">
<td shrink-date>
<span class="chip">
{{::visit.dated | date:'dd/MM/yy'}}

View File

@ -41,7 +41,7 @@
<vn-th field="salesPersonFk" shrink>SalesPerson</vn-th>
</vn-tr>
</vn-thead>
<a ng-repeat="order in model.data"
<a ng-repeat="order in model.data track by order.id"
class="clickable vn-tbody"
ui-sref="order.card.summary({id: {{::order.id}}})" target="_blank">
<vn-tr>

View File

@ -78,7 +78,7 @@
</tr>
</thead>
<tbody>
<tr ng-repeat="ticket in model.data"
<tr ng-repeat="ticket in model.data track by ticket.id"
vn-anchor="::{
state: 'ticket.card.summary',
params: {id: ticket.id},

View File

@ -20,3 +20,4 @@ routeFk: route
companyFk: company
agencyModeFk: agency
ticketFk: ticket
mergedTicket: merged ticket

View File

@ -20,3 +20,4 @@ routeFk: ruta
companyFk: empresa
agencyModeFk: agencia
ticketFk: ticket
mergedTicket: ticket fusionado

View File

@ -0,0 +1,64 @@
module.exports = Self => {
Self.remoteMethodCtx('getChanges', {
description: 'Get changues in the sales of a ticket',
accessType: 'READ',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'the ticket id',
http: {source: 'path'}
}],
returns: {
type: 'object',
root: true
},
http: {
path: `/:id/getChanges`,
verb: 'get'
}
});
Self.getChanges = async(ctx, id, options) => {
const models = Self.app.models;
const myOptions = {};
const $t = ctx.req.__; // $translate
if (typeof options == 'object')
Object.assign(myOptions, options);
const ticketLogs = await models.TicketLog.find(
{
where: {
and: [
{originFk: id},
{action: 'update'},
{changedModel: 'Sale'}
]
},
fields: [
'oldInstance',
'newInstance',
'changedModelId'
],
}, myOptions);
const changes = [];
for (const ticketLog of ticketLogs) {
const oldQuantity = ticketLog.oldInstance.quantity;
const newQuantity = ticketLog.newInstance.quantity;
if (oldQuantity || newQuantity) {
const sale = await models.Sale.findById(ticketLog.changedModelId, null, myOptions);
const message = $t('Change quantity', {
concept: sale.concept,
oldQuantity: oldQuantity || 0,
newQuantity: newQuantity || 0,
});
changes.push(message);
}
}
return changes.join('\n');
};
};

View File

@ -0,0 +1,16 @@
const models = require('vn-loopback/server/server').models;
describe('ticketLog getChanges()', () => {
const ctx = {req: {}};
ctx.req.__ = value => {
return value;
};
it('should return the changes in the sales of a ticket', async() => {
const ticketId = 7;
const changues = await models.TicketLog.getChanges(ctx, ticketId);
expect(changues).toContain(`Change quantity`);
});
});

View File

@ -0,0 +1,134 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
const buildFilter = require('vn-loopback/util/filter').buildFilter;
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
module.exports = Self => {
Self.remoteMethodCtx('getTicketsAdvance', {
description: 'Find all tickets that can be moved to the present',
accessType: 'READ',
accepts: [
{
arg: 'warehouseFk',
type: 'number',
description: 'Warehouse identifier',
required: true
},
{
arg: 'dateFuture',
type: 'date',
description: 'Date of the tickets that you want to advance',
required: true
},
{
arg: 'dateToAdvance',
type: 'date',
description: 'Date to when you want to advance',
required: true
},
{
arg: 'ipt',
type: 'string',
description: 'Origin Item Packaging Type',
required: false
},
{
arg: 'futureIpt',
type: 'string',
description: 'Destination Item Packaging Type',
required: false
},
{
arg: 'id',
type: 'number',
description: 'Origin id',
required: false
},
{
arg: 'futureId',
type: 'number',
description: 'Destination id',
required: false
},
{
arg: 'state',
type: 'string',
description: 'Origin state',
required: false
},
{
arg: 'futureState',
type: 'string',
description: 'Destination state',
required: false
},
{
arg: 'filter',
type: 'object',
description: `Filter defining where, order, offset, and limit - must be a JSON-encoded string`
}
],
returns: {
type: ['object'],
root: true
},
http: {
path: `/getTicketsAdvance`,
verb: 'GET'
}
});
Self.getTicketsAdvance = async(ctx, options) => {
const args = ctx.args;
const conn = Self.dataSource.connector;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const where = buildFilter(ctx.args, (param, value) => {
switch (param) {
case 'id':
return {'f.id': value};
case 'futureId':
return {'f.futureId': value};
case 'ipt':
return {'f.ipt': value};
case 'futureIpt':
return {'f.futureIpt': value};
case 'state':
return {'f.stateCode': {like: `%${value}%`}};
case 'futureState':
return {'f.futureStateCode': {like: `%${value}%`}};
}
});
let filter = mergeFilters(ctx.args.filter, {where});
const stmts = [];
let stmt;
stmt = new ParameterizedSQL(
`CALL vn.ticket_canAdvance(?,?,?)`,
[args.dateFuture, args.dateToAdvance, args.warehouseFk]);
stmts.push(stmt);
stmt = new ParameterizedSQL(`
SELECT f.*
FROM tmp.filter f`);
stmt.merge(conn.makeWhere(filter.where));
stmt.merge(conn.makeOrderBy(filter.order));
stmt.merge(conn.makeLimit(filter));
const ticketsIndex = stmts.push(stmt) - 1;
stmts.push(
`DROP TEMPORARY TABLE
tmp.filter`);
const sql = ParameterizedSQL.join(stmts, ';');
const result = await conn.executeStmt(sql, myOptions);
return result[ticketsIndex];
};
};

View File

@ -20,18 +20,6 @@ module.exports = Self => {
description: 'The date to probe',
required: true
},
{
arg: 'litersMax',
type: 'number',
description: 'Maximum volume of tickets to catapult',
required: true
},
{
arg: 'linesMax',
type: 'number',
description: 'Maximum number of lines of tickets to catapult',
required: true
},
{
arg: 'warehouseFk',
type: 'number',
@ -39,15 +27,15 @@ module.exports = Self => {
required: true
},
{
arg: 'shipped',
type: 'date',
description: 'Origin shipped',
arg: 'litersMax',
type: 'number',
description: 'Maximum volume of tickets to catapult',
required: false
},
{
arg: 'tfShipped',
type: 'date',
description: 'Destination shipped',
arg: 'linesMax',
type: 'number',
description: 'Maximum number of lines of tickets to catapult',
required: false
},
{
@ -57,7 +45,7 @@ module.exports = Self => {
required: false
},
{
arg: 'tfIpt',
arg: 'futureIpt',
type: 'string',
description: 'Destination Item Packaging Type',
required: false
@ -69,7 +57,7 @@ module.exports = Self => {
required: false
},
{
arg: 'tfId',
arg: 'futureId',
type: 'number',
description: 'Destination id',
required: false
@ -81,7 +69,7 @@ module.exports = Self => {
required: false
},
{
arg: 'tfState',
arg: 'futureState',
type: 'string',
description: 'Destination state',
required: false
@ -108,7 +96,7 @@ module.exports = Self => {
}
});
Self.getTicketsFuture = async (ctx, options) => {
Self.getTicketsFuture = async(ctx, options) => {
const args = ctx.args;
const conn = Self.dataSource.connector;
const myOptions = {};
@ -118,32 +106,32 @@ module.exports = Self => {
const where = buildFilter(ctx.args, (param, value) => {
switch (param) {
case 'id':
return { 'f.id': value };
case 'tfId':
return { 'f.ticketFuture': value };
case 'shipped':
return { 'f.shipped': value };
case 'tfShipped':
return { 'f.tfShipped': value };
case 'ipt':
return { 'f.ipt': value };
case 'tfIpt':
return { 'f.tfIpt': value };
case 'state':
return { 'f.code': { like: `%${value}%` } };
case 'tfState':
return { 'f.tfCode': { like: `%${value}%` } };
case 'id':
return {'f.id': value};
case 'lines':
return {'f.lines': {lte: value}};
case 'liters':
return {'f.liters': {lte: value}};
case 'futureId':
return {'f.futureId': value};
case 'ipt':
return {'f.ipt': value};
case 'futureIpt':
return {'f.futureIpt': value};
case 'state':
return {'f.stateCode': {like: `%${value}%`}};
case 'futureState':
return {'f.futureStateCode': {like: `%${value}%`}};
}
});
let filter = mergeFilters(ctx.args.filter, { where });
let filter = mergeFilters(ctx.args.filter, {where});
const stmts = [];
let stmt;
stmt = new ParameterizedSQL(
`CALL vn.ticket_canbePostponed(?,?,?,?,?)`,
[args.originDated, args.futureDated, args.litersMax, args.linesMax, args.warehouseFk]);
`CALL vn.ticket_canbePostponed(?,?,?)`,
[args.originDated, args.futureDated, args.warehouseFk]);
stmts.push(stmt);
@ -153,7 +141,7 @@ module.exports = Self => {
CREATE TEMPORARY TABLE tmp.sale_getProblems
(INDEX (ticketFk))
ENGINE = MEMORY
SELECT f.id ticketFk, f.clientFk, f.warehouseFk, f.shipped
SELECT f.id ticketFk, f.clientFk, f.warehouseFk, f.shipped, f.lines, f.liters
FROM tmp.filter f
LEFT JOIN alertLevel al ON al.id = f.alertLevel
WHERE (al.code = 'FREE' OR f.alertLevel IS NULL)`);
@ -174,35 +162,34 @@ module.exports = Self => {
let range;
let hasWhere;
switch (args.problems) {
case true:
condition = `or`;
hasProblem = true;
range = { neq: null };
hasWhere = true;
break;
case true:
condition = `or`;
hasProblem = true;
range = {neq: null};
hasWhere = true;
break;
case false:
condition = `and`;
hasProblem = null;
range = null;
hasWhere = true;
break;
case false:
condition = `and`;
hasProblem = null;
range = null;
hasWhere = true;
break;
}
const problems = {
[condition]: [
{ 'tp.isFreezed': hasProblem },
{ 'tp.risk': hasProblem },
{ 'tp.hasTicketRequest': hasProblem },
{ 'tp.itemShortage': range },
{ 'tp.hasComponentLack': hasProblem },
{ 'tp.isTooLittle': hasProblem }
{'tp.isFreezed': hasProblem},
{'tp.risk': hasProblem},
{'tp.hasTicketRequest': hasProblem},
{'tp.itemShortage': range},
{'tp.hasComponentLack': hasProblem},
{'tp.isTooLittle': hasProblem}
]
};
if (hasWhere) {
filter = mergeFilters(filter, { where: problems });
}
if (hasWhere)
filter = mergeFilters(filter, {where: problems});
stmt.merge(conn.makeWhere(filter.where));
stmt.merge(conn.makeOrderBy(filter.order));

View File

@ -40,19 +40,32 @@ module.exports = Self => {
try {
for (let ticket of tickets) {
const fullPath = `${origin}/#!/ticket/${ticket.id}/summary`;
const fullPathFuture = `${origin}/#!/ticket/${ticket.ticketFuture}/summary`;
const originFullPath = `${origin}/#!/ticket/${ticket.originId}/summary`;
const destinationFullPath = `${origin}/#!/ticket/${ticket.destinationId}/summary`;
const message = $t('Ticket merged', {
originDated: dateUtil.toString(new Date(ticket.originETD)),
futureDated: dateUtil.toString(new Date(ticket.destETD)),
id: ticket.id,
tfId: ticket.ticketFuture,
fullPath,
fullPathFuture
originDated: dateUtil.toString(new Date(ticket.originShipped)),
destinationDated: dateUtil.toString(new Date(ticket.destinationShipped)),
originId: ticket.originId,
destinationId: ticket.destinationId,
originFullPath,
destinationFullPath
});
if (!ticket.id || !ticket.ticketFuture) continue;
await models.Sale.updateAll({ticketFk: ticket.id}, {ticketFk: ticket.ticketFuture}, myOptions);
await models.Ticket.setDeleted(ctx, ticket.id, myOptions);
if (!ticket.originId || !ticket.destinationId) continue;
const ticketDestinationLogRecord = {
originFk: ticket.destinationId,
userFk: ctx.req.accessToken.userId,
action: 'update',
changedModel: 'Ticket',
changedModelId: ticket.destinationId,
changedModelValue: ticket.destinationId,
oldInstance: {},
newInstance: {mergedTicket: ticket.originId}
};
await models.TicketLog.create(ticketDestinationLogRecord, myOptions);
await models.Sale.updateAll({ticketFk: ticket.originId}, {ticketFk: ticket.destinationId}, myOptions);
await models.Ticket.setDeleted(ctx, ticket.originId, myOptions);
await models.Chat.sendCheckingPresence(ctx, ticket.workerFk, message);
}
if (tx)

View File

@ -31,6 +31,7 @@ module.exports = Self => {
});
Self.sendSms = async(ctx, id, destination, message, options) => {
const models = Self.app.models;
const myOptions = {};
let tx;
@ -45,7 +46,14 @@ module.exports = Self => {
const userId = ctx.req.accessToken.userId;
try {
const sms = await Self.app.models.Sms.send(ctx, destination, message);
const sms = await models.Sms.send(ctx, destination, message);
const newTicketSms = {
ticketFk: id,
smsFk: sms.id
};
await models.TicketSms.create(newTicketSms);
const logRecord = {
originFk: id,
userFk: userId,
@ -60,7 +68,7 @@ module.exports = Self => {
}
};
const ticketLog = await Self.app.models.TicketLog.create(logRecord, myOptions);
const ticketLog = await models.TicketLog.create(logRecord, myOptions);
sms.logId = ticketLog.id;

View File

@ -107,8 +107,8 @@ describe('ticket filter()', () => {
const result = await models.Ticket.filter(ctx, filter, options);
const firstRow = result[0];
expect(result.length).toEqual(1);
expect(firstRow.id).toEqual(11);
expect(result.length).toBeGreaterThan(0);
expect(firstRow.id).toBeGreaterThan(10);
await tx.rollback();
} catch (e) {
@ -153,7 +153,7 @@ describe('ticket filter()', () => {
const secondRow = result[1];
const thirdRow = result[2];
expect(result.length).toEqual(17);
expect(result.length).toBeGreaterThan(15);
expect(firstRow.state).toEqual('Entregado');
expect(secondRow.state).toEqual('Entregado');
expect(thirdRow.state).toEqual('Entregado');
@ -175,7 +175,7 @@ describe('ticket filter()', () => {
const filter = {};
const result = await models.Ticket.filter(ctx, filter, options);
expect(result.length).toEqual(26);
expect(result.length).toBeGreaterThan(25);
await tx.rollback();
} catch (e) {
@ -194,7 +194,7 @@ describe('ticket filter()', () => {
const filter = {};
const result = await models.Ticket.filter(ctx, filter, options);
expect(result.length).toEqual(4);
expect(result.length).toBeGreaterThan(0);
await tx.rollback();
} catch (e) {
@ -213,7 +213,7 @@ describe('ticket filter()', () => {
const filter = {};
const result = await models.Ticket.filter(ctx, filter, options);
expect(result.length).toEqual(3);
expect(result.length).toBeGreaterThan(0);
await tx.rollback();
} catch (e) {
@ -251,7 +251,7 @@ describe('ticket filter()', () => {
const filter = {};
const result = await models.Ticket.filter(ctx, filter, options);
expect(result.length).toEqual(5);
expect(result.length).toBeGreaterThan(0);
await tx.rollback();
} catch (e) {
@ -289,7 +289,7 @@ describe('ticket filter()', () => {
const filter = {};
const result = await models.Ticket.filter(ctx, filter, options);
expect(result.length).toEqual(6);
expect(result.length).toBeGreaterThan(0);
await tx.rollback();
} catch (e) {

View File

@ -0,0 +1,131 @@
const models = require('vn-loopback/server/server').models;
describe('TicketFuture getTicketsAdvance()', () => {
const today = new Date();
today.setHours(0, 0, 0, 0);
let tomorrow = new Date();
tomorrow.setDate(today.getDate() + 1);
it('should return the tickets passing the required data', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = {transaction: tx};
const args = {
dateFuture: tomorrow,
dateToAdvance: today,
warehouseFk: 1,
};
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsAdvance(ctx, options);
expect(result.length).toBeGreaterThan(0);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the tickets matching the origin grouped state', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = {transaction: tx};
const args = {
dateFuture: tomorrow,
dateToAdvance: today,
warehouseFk: 1,
state: 'OK'
};
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsAdvance(ctx, options);
expect(result.length).toBeGreaterThan(0);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the tickets matching the destination grouped state', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = {transaction: tx};
const args = {
dateFuture: tomorrow,
dateToAdvance: today,
warehouseFk: 1,
futureState: 'FREE'
};
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsAdvance(ctx, options);
expect(result.length).toBeGreaterThan(0);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the tickets matching the origin IPT', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = {transaction: tx};
const args = {
dateFuture: tomorrow,
dateToAdvance: today,
warehouseFk: 1,
ipt: 'Vertical'
};
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsAdvance(ctx, options);
expect(result.length).toBeLessThan(5);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the tickets matching the destination IPT', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = {transaction: tx};
const args = {
dateFuture: tomorrow,
dateToAdvance: today,
warehouseFk: 1,
tfIpt: 'Vertical'
};
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsAdvance(ctx, options);
expect(result.length).toBeLessThan(5);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -1,25 +1,22 @@
const models = require('vn-loopback/server/server').models;
describe('TicketFuture getTicketsFuture()', () => {
describe('ticket getTicketsFuture()', () => {
const today = new Date();
today.setHours(0, 0, 0, 0);
const tomorrow = new Date(today.getDate() + 1);
it('should return the tickets passing the required data', async () => {
it('should return the tickets passing the required data', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = { transaction: tx };
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
litersMax: 9999,
linesMax: 9999,
warehouseFk: 1,
};
const ctx = { req: { accessToken: { userId: 9 } }, args };
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4);
@ -30,22 +27,20 @@ describe('TicketFuture getTicketsFuture()', () => {
}
});
it('should return the tickets matching the problems on true', async () => {
it('should return the tickets matching the problems on true', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = { transaction: tx };
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
litersMax: 9999,
linesMax: 9999,
warehouseFk: 1,
problems: true
};
const ctx = { req: { accessToken: { userId: 9 } }, args };
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4);
@ -57,22 +52,20 @@ describe('TicketFuture getTicketsFuture()', () => {
}
});
it('should return the tickets matching the problems on false', async () => {
it('should return the tickets matching the problems on false', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = { transaction: tx };
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
litersMax: 9999,
linesMax: 9999,
warehouseFk: 1,
problems: false
};
const ctx = { req: { accessToken: { userId: 9 } }, args };
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(0);
@ -84,22 +77,20 @@ describe('TicketFuture getTicketsFuture()', () => {
}
});
it('should return the tickets matching the problems on null', async () => {
it('should return the tickets matching the problems on null', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = { transaction: tx };
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
litersMax: 9999,
linesMax: 9999,
warehouseFk: 1,
problems: null
};
const ctx = { req: { accessToken: { userId: 9 } }, args };
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4);
@ -111,130 +102,20 @@ describe('TicketFuture getTicketsFuture()', () => {
}
});
it('should return the tickets matching the correct origin shipped', async () => {
it('should return the tickets matching the OK State in origin date', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = { transaction: tx };
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
litersMax: 9999,
linesMax: 9999,
warehouseFk: 1,
shipped: today
state: 'OK'
};
const ctx = { req: { accessToken: { userId: 9 } }, args };
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the tickets matching the an incorrect origin shipped', async () => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = { transaction: tx };
const args = {
originDated: today,
futureDated: today,
litersMax: 9999,
linesMax: 9999,
warehouseFk: 1,
shipped: tomorrow
};
const ctx = { req: { accessToken: { userId: 9 } }, args };
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(0);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the tickets matching the correct destination shipped', async () => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = { transaction: tx };
const args = {
originDated: today,
futureDated: today,
litersMax: 9999,
linesMax: 9999,
warehouseFk: 1,
tfShipped: today
};
const ctx = { req: { accessToken: { userId: 9 } }, args };
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the tickets matching the an incorrect destination shipped', async () => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = { transaction: tx };
const args = {
originDated: today,
futureDated: today,
litersMax: 9999,
linesMax: 9999,
warehouseFk: 1,
tfShipped: tomorrow
};
const ctx = { req: { accessToken: { userId: 9 } }, args };
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(0);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return the tickets matching the OK State in origin date', async () => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = { transaction: tx };
const args = {
originDated: today,
futureDated: today,
litersMax: 9999,
linesMax: 9999,
warehouseFk: 1,
state: "OK"
};
const ctx = { req: { accessToken: { userId: 9 } }, args };
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(1);
@ -246,22 +127,20 @@ describe('TicketFuture getTicketsFuture()', () => {
}
});
it('should return the tickets matching the OK State in destination date', async () => {
it('should return the tickets matching the OK State in destination date', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = { transaction: tx };
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
litersMax: 9999,
linesMax: 9999,
warehouseFk: 1,
tfState: "OK"
futureState: 'OK'
};
const ctx = { req: { accessToken: { userId: 9 } }, args };
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4);
@ -273,22 +152,20 @@ describe('TicketFuture getTicketsFuture()', () => {
}
});
it('should return the tickets matching the correct IPT in origin date', async () => {
it('should return the tickets matching the correct IPT in origin date', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = { transaction: tx };
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
litersMax: 9999,
linesMax: 9999,
warehouseFk: 1,
ipt: null
};
const ctx = { req: { accessToken: { userId: 9 } }, args };
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4);
@ -300,22 +177,20 @@ describe('TicketFuture getTicketsFuture()', () => {
}
});
it('should return the tickets matching the incorrect IPT in origin date', async () => {
it('should return the tickets matching the incorrect IPT in origin date', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = { transaction: tx };
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
litersMax: 9999,
linesMax: 9999,
warehouseFk: 1,
ipt: 0
};
const ctx = { req: { accessToken: { userId: 9 } }, args };
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(0);
@ -327,22 +202,20 @@ describe('TicketFuture getTicketsFuture()', () => {
}
});
it('should return the tickets matching the correct IPT in destination date', async () => {
it('should return the tickets matching the correct IPT in destination date', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = { transaction: tx };
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
litersMax: 9999,
linesMax: 9999,
warehouseFk: 1,
tfIpt: null
futureIpt: null
};
const ctx = { req: { accessToken: { userId: 9 } }, args };
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4);
@ -354,22 +227,20 @@ describe('TicketFuture getTicketsFuture()', () => {
}
});
it('should return the tickets matching the incorrect IPT in destination date', async () => {
it('should return the tickets matching the incorrect IPT in destination date', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = { transaction: tx };
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
litersMax: 9999,
linesMax: 9999,
warehouseFk: 1,
tfIpt: 0
futureIpt: 0
};
const ctx = { req: { accessToken: { userId: 9 } }, args };
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(0);
@ -381,22 +252,20 @@ describe('TicketFuture getTicketsFuture()', () => {
}
});
it('should return the tickets matching the ID in origin date', async () => {
it('should return the tickets matching the ID in origin date', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = { transaction: tx };
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
litersMax: 9999,
linesMax: 9999,
warehouseFk: 1,
id: 13
};
const ctx = { req: { accessToken: { userId: 9 } }, args };
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(1);
@ -408,22 +277,20 @@ describe('TicketFuture getTicketsFuture()', () => {
}
});
it('should return the tickets matching the ID in destination date', async () => {
it('should return the tickets matching the ID in destination date', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = { transaction: tx };
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
litersMax: 9999,
linesMax: 9999,
warehouseFk: 1,
tfId: 12
futureId: 12
};
const ctx = { req: { accessToken: { userId: 9 } }, args };
const ctx = {req: {accessToken: {userId: 9}}, args};
const result = await models.Ticket.getTicketsFuture(ctx, options);
expect(result.length).toEqual(4);
@ -434,5 +301,4 @@ describe('TicketFuture getTicketsFuture()', () => {
throw e;
}
});
});

View File

@ -3,15 +3,15 @@ const LoopBackContext = require('loopback-context');
describe('ticket merge()', () => {
const tickets = [{
id: 13,
ticketFuture: 12,
workerFk: 1,
originETD: new Date(),
destETD: new Date()
originId: 13,
destinationId: 12,
originShipped: new Date(),
destinationShipped: new Date(),
workerFk: 1
}];
const activeCtx = {
accessToken: { userId: 9 },
accessToken: {userId: 9},
};
beforeEach(() => {
@ -22,26 +22,26 @@ describe('ticket merge()', () => {
const ctx = {
req: {
accessToken: { userId: 9 },
headers: { origin: 'http://localhost:5000' },
accessToken: {userId: 9},
headers: {origin: 'http://localhost:5000'},
}
};
ctx.req.__ = value => {
return value;
};
it('should merge two tickets', async () => {
it('should merge two tickets', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = { transaction: tx };
const options = {transaction: tx};
const chatNotificationBeforeMerge = await models.Chat.find();
await models.Ticket.merge(ctx, tickets, options);
const createdTicketLog = await models.TicketLog.find({ where: { originFk: tickets[0].id } }, options);
const deletedTicket = await models.Ticket.findOne({ where: { id: tickets[0].id } }, options);
const salesTicketFuture = await models.Sale.find({ where: { ticketFk: tickets[0].ticketFuture } }, options);
const createdTicketLog = await models.TicketLog.find({where: {originFk: tickets[0].originId}}, options);
const deletedTicket = await models.Ticket.findOne({where: {id: tickets[0].originId}}, options);
const salesTicketFuture = await models.Sale.find({where: {ticketFk: tickets[0].destinationId}}, options);
const chatNotificationAfterMerge = await models.Chat.find();
expect(createdTicketLog.length).toEqual(1);

View File

@ -87,7 +87,7 @@ describe('sale priceDifference()', () => {
const secondtItem = result.items[1];
expect(firstItem.movable).toEqual(410);
expect(secondtItem.movable).toEqual(1810);
expect(secondtItem.movable).toEqual(1790);
await tx.rollback();
} catch (e) {

View File

@ -15,9 +15,16 @@ describe('ticket sendSms()', () => {
const sms = await models.Ticket.sendSms(ctx, id, destination, message, options);
const createdLog = await models.TicketLog.findById(sms.logId, null, options);
const filter = {
ticketFk: createdLog.originFk
};
const ticketSms = await models.TicketSms.findOne(filter, options);
const json = JSON.parse(JSON.stringify(createdLog.newInstance));
expect(json.message).toEqual(message);
expect(ticketSms.ticketFk).toEqual(createdLog.originFk);
await tx.rollback();
} catch (e) {

View File

@ -44,7 +44,7 @@
"SaleTracking": {
"dataSource": "vn"
},
"State":{
"State": {
"dataSource": "vn"
},
"Ticket": {
@ -71,16 +71,16 @@
"TicketRequest": {
"dataSource": "vn"
},
"TicketState":{
"TicketState": {
"dataSource": "vn"
},
"TicketLastState":{
"TicketLastState": {
"dataSource": "vn"
},
"TicketService":{
"TicketService": {
"dataSource": "vn"
},
"TicketServiceType":{
"TicketServiceType": {
"dataSource": "vn"
},
"TicketTracking": {
@ -94,8 +94,5 @@
},
"TicketConfig": {
"dataSource": "vn"
},
"TicketFuture": {
"dataSource": "vn"
}
}

View File

@ -1,12 +0,0 @@
{
"name": "TicketFuture",
"base": "PersistedModel",
"acls": [
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "employee",
"permission": "ALLOW"
}
]
}

View File

@ -0,0 +1,3 @@
module.exports = function(Self) {
require('../methods/ticket-log/getChanges')(Self);
};

View File

@ -33,8 +33,9 @@ module.exports = function(Self) {
require('../methods/ticket/closeByTicket')(Self);
require('../methods/ticket/closeByAgency')(Self);
require('../methods/ticket/closeByRoute')(Self);
require('../methods/ticket-future/getTicketsFuture')(Self);
require('../methods/ticket/getTicketsFuture')(Self);
require('../methods/ticket/merge')(Self);
require('../methods/ticket/getTicketsAdvance')(Self);
require('../methods/ticket/isRoleAdvanced')(Self);
require('../methods/ticket/collectionLabel')(Self);
require('../methods/ticket/expeditionPalletLabel')(Self);

View File

@ -0,0 +1,76 @@
<div class="search-panel">
<form id="manifold-form" ng-submit="$ctrl.onSearch()">
<vn-horizontal class="vn-px-lg vn-pt-lg">
<vn-date-picker
vn-one
label="Origin date"
ng-model="filter.dateFuture"
required="true">
</vn-date-picker>
<vn-date-picker
vn-one
label="Destination date"
ng-model="filter.dateToAdvance"
required="true">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal class="vn-px-lg">
<vn-autocomplete vn-one
data="$ctrl.itemPackingTypes"
label="Origin IPT"
value-field="code"
show-field="description"
ng-model="filter.futureIpt"
info="IPT">
<tpl-item>
{{description}}
</tpl-item>
</vn-autocomplete>
<vn-autocomplete vn-one
data="$ctrl.itemPackingTypes"
label="Destination IPT"
value-field="code"
show-field="description"
ng-model="filter.ipt"
info="IPT">
<tpl-item>
{{description}}
</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal class="vn-px-lg">
<vn-autocomplete vn-one
data="$ctrl.groupedStates"
label="Origin Grouped State"
value-field="code"
show-field="name"
ng-model="filter.futureState">
<tpl-item>
{{name}}
</tpl-item>
</vn-autocomplete>
<vn-autocomplete vn-one
data="$ctrl.groupedStates"
label="Destination Grouped State"
value-field="code"
show-field="name"
ng-model="filter.state">
<tpl-item>
{{name}}
</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal class="vn-px-lg">
<vn-autocomplete
vn-one
label="Warehouse"
ng-model="filter.warehouseFk"
url="Warehouses"
required="true">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal class="vn-px-lg vn-pb-lg vn-mt-lg">
<vn-submit label="Search"></vn-submit>
</vn-horizontal>
</form>
</div>

View File

@ -0,0 +1,43 @@
import ngModule from '../module';
import SearchPanel from 'core/components/searchbar/search-panel';
class Controller extends SearchPanel {
constructor($, $element) {
super($, $element);
this.filter = this.$.filter;
this.getGroupedStates();
this.getItemPackingTypes();
}
getGroupedStates() {
let groupedStates = [];
this.$http.get('AlertLevels').then(res => {
for (let state of res.data) {
groupedStates.push({
id: state.id,
code: state.code,
name: this.$t(state.code)
});
}
this.groupedStates = groupedStates;
});
}
getItemPackingTypes() {
let itemPackingTypes = [];
this.$http.get('ItemPackingTypes').then(res => {
for (let ipt of res.data) {
itemPackingTypes.push({
code: ipt.code,
description: this.$t(ipt.description)
});
}
this.itemPackingTypes = itemPackingTypes;
});
}
}
ngModule.vnComponent('vnAdvanceTicketSearchPanel', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1 @@
Advance tickets: Advance tickets

View File

@ -0,0 +1 @@
Advance tickets: Adelantar tickets

View File

@ -0,0 +1,160 @@
<vn-crud-model
vn-id="model"
url="Tickets/getTicketsAdvance"
auto-load="false">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
vn-focus
panel="vn-advance-ticket-search-panel"
placeholder="Search tickets"
info="Search advance tickets by date"
suggested-filter="$ctrl.filterParams"
auto-state="false"
model="model">
</vn-searchbar>
</vn-portal>
<vn-card>
<smart-table
model="model"
options="$ctrl.smartTableOptions"
expr-builder="$ctrl.exprBuilder(param, value)"
>
<slot-actions>
<vn-button disabled="$ctrl.checked.length === 0"
icon="keyboard_double_arrow_left"
ng-click="moveTicketsAdvance.show($event)"
vn-tooltip="Advance tickets">
</vn-button>
</slot-actions>
<slot-table>
<table>
<thead>
<tr second-header>
<td></td>
<th colspan="5" translate>Origin</th>
<th colspan="8" translate>Destination</th>
</tr>
<tr>
<th shrink>
<vn-multi-check
model="model"
checked="$ctrl.checkAll"
check-field="checked">
</vn-multi-check>
</th>
<th field="futureId">
<span translate>ID</span>
</th>
<th field="futureShipped">
<span translate>Date</span>
</th>
<th field="futureIpt" title="Item Packing Type">
<span>IPT</span>
</th>
<th field="futureState">
<span translate>State</span>
</th>
<th field="totalWithVat">
<span translate>Import</span>
</th>
<th separator field="id">
<span translate>ID</span>
</th>
<th field="shipped">
<span translate>Date</span>
</th>
<th field="ipt" title="Item Packing Type">
<span>IPT</span>
</th>
<th field="state">
<span translate>State</span>
</th>
<th field="liters">
<span translate>Liters</span>
</th>
<th field="hasStock">
<span>Stock</span>
</th>
<th field="lines">
<span translate>Lines</span>
</th>
<th field="futureTotalWithVat">
<span translate>Import</span>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="ticket in model.data">
<td>
<vn-check
ng-model="ticket.checked"
vn-click-stop>
</vn-check>
</td>
<td>
<span
ng-click="ticketDescriptor.show($event, ticket.futureId)"
class="link">
{{::ticket.futureId | dashIfEmpty}}
</span>
</td>
<td shrink-date>
<span class="chip {{$ctrl.compareDate(ticket.futureShipped)}}">
{{::ticket.futureShipped | date: 'dd/MM/yyyy'}}
</span>
</td>
<td>{{::ticket.futureIpt | dashIfEmpty}}</td>
<td>
<span
class="chip {{$ctrl.stateColor(ticket.futureState)}}">
{{::ticket.futureState | dashIfEmpty}}
</span>
</td>
<td>
<span class="chip {{$ctrl.totalPriceColor(ticket.futureTotalWithVat)}}">
{{::(ticket.futureTotalWithVat ? ticket.futureTotalWithVat : 0) | currency: 'EUR': 2}}
</span>
</td>
<td>
<span
ng-click="ticketDescriptor.show($event, ticket.id)"
class="link">
{{::ticket.id | dashIfEmpty}}
</span>
</td>
<td shrink-date>
<span class="chip {{$ctrl.compareDate(ticket.shipped)}}">
{{::ticket.shipped | date: 'dd/MM/yyyy'}}
</span>
</td>
<td>{{::ticket.ipt | dashIfEmpty}}</td>
<td>
<span
class="chip {{$ctrl.stateColor(ticket.state)}}">
{{::ticket.state | dashIfEmpty}}
</span>
</td>
<td>{{::ticket.liters | dashIfEmpty}}</td>
<td>{{::ticket.hasStock | dashIfEmpty}}</td>
<td>{{::ticket.lines | dashIfEmpty}}</td>
<td>
<span class="chip {{$ctrl.totalPriceColor(ticket.totalWithVat)}}">
{{::(ticket.totalWithVat ? ticket.totalWithVat : 0) | currency: 'EUR': 2}}
</span>
</td>
</tr>
</tbody>
</table>
</slot-table>
</smart-table>
</vn-card>
<vn-confirm
vn-id="moveTicketsAdvance"
on-accept="$ctrl.moveTicketsAdvance()"
question="{{$ctrl.confirmationMessage}}"
message="Advance tickets">
</vn-confirm>
<vn-ticket-descriptor-popover
vn-id="ticketDescriptor">
</vn-ticket-descriptor-popover>

View File

@ -0,0 +1,177 @@
import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.$checkAll = false;
this.smartTableOptions = {
activeButtons: {
search: true,
},
columns: [
{
field: 'state',
searchable: false
},
{
field: 'futureState',
searchable: false
},
{
field: 'totalWithVat',
searchable: false
},
{
field: 'futureTotalWithVat',
searchable: false
},
{
field: 'shipped',
searchable: false
},
{
field: 'futureShipped',
searchable: false
},
{
field: 'ipt',
autocomplete: {
url: 'ItemPackingTypes',
showField: 'description',
valueField: 'code'
}
},
{
field: 'futureIpt',
autocomplete: {
url: 'ItemPackingTypes',
showField: 'description',
valueField: 'code'
}
},
]
};
}
$postLink() {
this.setDefaultFilter();
}
setDefaultFilter() {
let today = new Date();
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
this.filterParams = {
dateFuture: tomorrow,
dateToAdvance: today,
warehouseFk: this.vnConfig.warehouseFk
};
this.$.model.applyFilter(null, this.filterParams);
}
compareDate(date) {
let today = new Date();
today.setHours(0, 0, 0, 0);
let timeTicket = new Date(date);
timeTicket.setHours(0, 0, 0, 0);
let comparation = today - timeTicket;
if (comparation == 0)
return 'warning';
if (comparation < 0)
return 'success';
}
get checked() {
const tickets = this.$.model.data || [];
const checkedLines = [];
for (let ticket of tickets) {
if (ticket.checked)
checkedLines.push(ticket);
}
return checkedLines;
}
stateColor(state) {
if (state === 'OK')
return 'success';
else if (state === 'Libre')
return 'notice';
}
dateRange(value) {
const minHour = new Date(value);
minHour.setHours(0, 0, 0, 0);
const maxHour = new Date(value);
maxHour.setHours(23, 59, 59, 59);
return [minHour, maxHour];
}
totalPriceColor(totalWithVat) {
const total = parseInt(totalWithVat);
if (total > 0 && total < 50)
return 'warning';
}
get confirmationMessage() {
if (!this.$.model) return 0;
return this.$t(`Advance confirmation`, {
checked: this.checked.length
});
}
moveTicketsAdvance() {
let ticketsToMove = [];
this.checked.forEach(ticket => {
ticketsToMove.push({
originId: ticket.futureId,
destinationId: ticket.id,
originShipped: ticket.futureShipped,
destinationShipped: ticket.shipped,
workerFk: ticket.workerFk
});
});
const params = {tickets: ticketsToMove};
return this.$http.post('Tickets/merge', params)
.then(() => {
this.$.model.refresh();
this.vnApp.showSuccess(this.$t('Success'));
});
}
exprBuilder(param, value) {
switch (param) {
case 'id':
return {'id': value};
case 'futureId':
return {'futureId': value};
case 'liters':
return {'liters': value};
case 'lines':
return {'lines': value};
case 'ipt':
return {'ipt': value};
case 'futureIpt':
return {'futureIpt': value};
case 'totalWithVat':
return {'totalWithVat': value};
case 'futureTotalWithVat':
return {'futureTotalWithVat': value};
case 'hasStock':
return {'hasStock': value};
}
}
}
Controller.$inject = ['$element', '$scope'];
ngModule.vnComponent('vnTicketAdvance', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,113 @@
import './index.js';
import crudModel from 'core/mocks/crud-model';
describe('Component vnTicketAdvance', () => {
let controller;
let $httpBackend;
beforeEach(ngModule('ticket')
);
beforeEach(inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
const $element = angular.element('<vn-ticket-advance></vn-ticket-advance>');
controller = $componentController('vnTicketAdvance', {$element});
controller.$.model = crudModel;
controller.$.model.data = [{
id: 1,
checked: true,
state: 'OK'
}, {
id: 2,
checked: true,
state: 'Libre'
}];
}));
describe('compareDate()', () => {
it('should return warning when the date is the present', () => {
let today = new Date();
let result = controller.compareDate(today);
expect(result).toEqual('warning');
});
it('should return sucess when the date is in the future', () => {
let futureDate = new Date();
futureDate = futureDate.setDate(futureDate.getDate() + 10);
let result = controller.compareDate(futureDate);
expect(result).toEqual('success');
});
it('should return undefined when the date is in the past', () => {
let pastDate = new Date();
pastDate = pastDate.setDate(pastDate.getDate() - 10);
let result = controller.compareDate(pastDate);
expect(result).toEqual(undefined);
});
});
describe('checked()', () => {
it('should return an array of checked tickets', () => {
const result = controller.checked;
const firstRow = result[0];
const secondRow = result[1];
expect(result.length).toEqual(2);
expect(firstRow.id).toEqual(1);
expect(secondRow.id).toEqual(2);
});
});
describe('stateColor()', () => {
it('should return success to the OK tickets', () => {
const ok = controller.stateColor(controller.$.model.data[0].state);
const notOk = controller.stateColor(controller.$.model.data[1].state);
expect(ok).toEqual('success');
expect(notOk).not.toEqual('success');
});
it('should return success to the FREE tickets', () => {
const notFree = controller.stateColor(controller.$.model.data[0].state);
const free = controller.stateColor(controller.$.model.data[1].state);
expect(free).toEqual('notice');
expect(notFree).not.toEqual('notice');
});
});
describe('dateRange()', () => {
it('should return two dates with the hours at the start and end of the given date', () => {
const now = new Date();
const today = now.getDate();
const dateRange = controller.dateRange(now);
const start = dateRange[0].toString();
const end = dateRange[1].toString();
expect(start).toContain(today);
expect(start).toContain('00:00:00');
expect(end).toContain(today);
expect(end).toContain('23:59:59');
});
});
describe('moveTicketsAdvance()', () => {
it('should make an HTTP Post query', () => {
jest.spyOn(controller.$.model, 'refresh');
jest.spyOn(controller.vnApp, 'showSuccess');
$httpBackend.expectPOST(`Tickets/merge`).respond();
controller.moveTicketsAdvance();
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
expect(controller.$.model.refresh).toHaveBeenCalledWith();
});
});
});

View File

@ -0,0 +1,2 @@
Advance tickets: Advance tickets
Success: Tickets moved successfully!

View File

@ -0,0 +1,6 @@
Advance tickets: Adelantar tickets
Search advance tickets by date: Busca tickets para adelantar por fecha
Advance confirmation: ¿Desea adelantar {{checked}} tickets?
Success: Tickets movidos correctamente
Lines: Líneas
Liters: Litros

View File

@ -110,6 +110,12 @@
translate>
SMS Minimum import
</vn-item>
<vn-item
ng-click="$ctrl.sendChangesSms()"
name="sendChangesSms"
translate>
SMS Notify changes
</vn-item>
<vn-item
ng-click="makeInvoiceConfirmation.show()"
ng-show="$ctrl.isEditable"
@ -280,10 +286,11 @@
</vn-dialog>
<!-- Send SMS popup -->
<vn-ticket-sms
<vn-sms-dialog
vn-id="sms"
on-send="$ctrl.onSmsSend($sms)"
sms="$ctrl.newSMS">
</vn-ticket-sms>
</vn-sms-dialog>
<!-- Make invoice confirmation dialog -->
<vn-confirm

View File

@ -225,6 +225,18 @@ class Controller extends Section {
});
}
sendChangesSms() {
return this.$http.get(`TicketLogs/${this.id}/getChanges`)
.then(res => {
const params = {
ticketId: this.id,
created: this.ticket.updated,
changes: res.data
};
this.showSMSDialog({message: this.$t('Send changes', params)});
});
}
showSMSDialog(params) {
const address = this.ticket.address;
const client = this.ticket.client;
@ -239,6 +251,7 @@ class Controller extends Section {
destinationFk: this.ticket.clientFk,
destination: phone
}, params);
this.$.sms.open();
}
@ -294,6 +307,11 @@ class Controller extends Section {
this.$state.go('ticket.card.sale', {id: refundTicket.id});
});
}
onSmsSend(sms) {
return this.$http.post(`Tickets/${this.id}/sendSms`, sms)
.then(() => this.vnApp.showSuccess(this.$t('SMS sent')));
}
}
Controller.$inject = ['$element', '$scope', 'vnReport', 'vnEmail'];

View File

@ -258,14 +258,24 @@ describe('Ticket Component vnTicketDescriptorMenu', () => {
});
});
describe('showSMSDialog()', () => {
it('should set the destionationFk and destination properties and then call the sms open() method', () => {
describe('sendChangesSms()', () => {
it('should make a query and open the sms dialog', () => {
controller.$.sms = {open: () => {}};
jest.spyOn(controller.$.sms, 'open');
controller.showSMSDialog();
$httpBackend.expectGET(`TicketLogs/${ticket.id}/getChanges`).respond();
controller.sendChangesSms();
$httpBackend.flush();
expect(controller.$.sms.open).toHaveBeenCalledWith();
});
});
describe('showSMSDialog()', () => {
it('should set the destionationFk and destination properties and then call the sms open() method', () => {
controller.$.sms = {open: () => {}};
controller.showSMSDialog();
expect(controller.newSMS).toEqual({
destinationFk: ticket.clientFk,
destination: ticket.address.mobile,

View File

@ -12,3 +12,4 @@ Refund all: Abonar todo
Invoice sent: Factura enviada
The following refund ticket have been created: "Se ha creado siguiente ticket de abono: {{ticketId}}"
Transfer client: Transferir cliente
SMS Notify changes: SMS Notificar cambios

View File

@ -1,2 +1,3 @@
Make a payment: "Verdnatura communicates:\rYour order is pending of payment.\rPlease, enter the web page and make the payment with card.\rThank you."
Minimum is needed: "Verdnatura communicates:\rA minimum import of 50€ (Without BAT) is needed for your order {{ticketId}} from date {{created | date: 'dd/MM/yyyy'}} to receive it with no extra fees."
Send changes: "Verdnatura communicates:\rOrder {{ticketId}} date {{created | date: 'dd/MM/yyyy'}}\r{{changes}}"

View File

@ -23,3 +23,4 @@ Restore ticket: Restaurar ticket
You are going to restore this ticket: Vas a restaurar este ticket
Are you sure you want to restore this ticket?: ¿Seguro que quieres restaurar el ticket?
Are you sure you want to refund all?: ¿Seguro que quieres abonar todo?
Send changes: "Verdnatura le recuerda:\rPedido {{ticketId}} día {{created | date: 'dd/MM/yyyy'}}\r{{changes}}"

View File

@ -4,43 +4,26 @@
<vn-date-picker
vn-one
label="Origin date"
ng-model="filter.shipped"
on-change="$ctrl.from = value">
ng-model="filter.originDated"
required="true">
</vn-date-picker>
<vn-date-picker
vn-one
label="Destination date"
ng-model="filter.tfShipped">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal class="vn-px-lg">
<vn-date-picker
vn-one
label="Origin ETD"
ng-model="filter.originDated"
required="true"
info="ETD">
</vn-date-picker>
<vn-date-picker
vn-one
label="Destination ETD"
ng-model="filter.futureDated"
required="true"
info="ETD">
required="true">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal class="vn-px-lg">
<vn-textfield
vn-one
label="Max Lines"
ng-model="filter.linesMax"
required="true">
</vn-textfield>
<vn-textfield
vn-one
label="Max Liters"
ng-model="filter.litersMax"
required="true">
ng-model="filter.litersMax">
</vn-textfield>
<vn-textfield
vn-one
label="Max Lines"
ng-model="filter.linesMax">
</vn-textfield>
</vn-horizontal>
<vn-horizontal class="vn-px-lg">
@ -48,22 +31,22 @@
data="$ctrl.itemPackingTypes"
label="Origin IPT"
value-field="code"
show-field="name"
show-field="description"
ng-model="filter.ipt"
info="IPT">
<tpl-item>
{{name}}
{{description}}
</tpl-item>
</vn-autocomplete>
<vn-autocomplete vn-one
data="$ctrl.itemPackingTypes"
label="Destination IPT"
value-field="code"
show-field="name"
ng-model="filter.tfIpt"
show-field="description"
ng-model="filter.futureIpt"
info="IPT">
<tpl-item>
{{name}}
{{description}}
</tpl-item>
</vn-autocomplete>
</vn-horizontal>
@ -83,7 +66,7 @@
label="Destination Grouped State"
value-field="code"
show-field="name"
ng-model="filter.tfState">
ng-model="filter.futureState">
<tpl-item>
{{name}}
</tpl-item>

View File

@ -28,9 +28,8 @@ class Controller extends SearchPanel {
this.$http.get('ItemPackingTypes').then(res => {
for (let ipt of res.data) {
itemPackingTypes.push({
id: ipt.id,
description: this.$t(ipt.description),
code: ipt.code,
name: this.$t(ipt.code)
});
}
this.itemPackingTypes = itemPackingTypes;

View File

@ -1,9 +1 @@
Future tickets: Tickets a futuro
FREE: Free
DELIVERED: Delivered
ON_PREPARATION: On preparation
PACKED: Packed
F: Fruits and vegetables
V: Vertical
H: Horizontal
P: Feed

View File

@ -11,13 +11,4 @@ With problems: Con problemas
Warehouse: Almacén
Origin Grouped State: Estado agrupado origen
Destination Grouped State: Estado agrupado destino
FREE: Libre
DELIVERED: Servido
ON_PREPARATION: En preparacion
PACKED: Encajado
F: Frutas y verduras
V: Vertical
H: Horizontal
P: Pienso
ETD: Tiempo estimado de entrega
IPT: Encajado

View File

@ -1,7 +1,7 @@
<vn-crud-model
vn-id="model"
url="Tickets/getTicketsFuture"
limit="20">
auto-load="false">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
@ -30,6 +30,11 @@
<slot-table>
<table>
<thead>
<tr second-header>
<td></td>
<th colspan="7" translate>Origin</th>
<th colspan="4" translate>Destination</th>
</tr>
<tr>
<th shrink>
<vn-multi-check
@ -38,39 +43,39 @@
check-field="checked">
</vn-multi-check>
</th>
<th field="problems">
<th field="totalProblems">
<span translate>Problems</span>
</th>
<th field="id">
<span translate>Origin ID</span>
<span translate>ID</span>
</th>
<th field="originETD">
<span translate>Origin ETD</span>
<th field="shipped">
<span translate>Date</span>
</th>
<th field="ipt" title="Item Packing Type">
<span>IPT</span>
</th>
<th field="state">
<span translate>Origin State</span>
<span translate>State</span>
</th>
<th field="ipt">
<span>IPT</span>
</th>
<th field="litersMax">
<th field="liters">
<span translate>Liters</span>
</th>
<th field="linesMax">
<th shrink field="lines">
<span translate>Available Lines</span>
</th>
<th field="ticketFuture">
<span translate>Destination ID</span>
<th field="futureId" separator>
<span translate>ID</span>
</th>
<th field="destETD">
<span translate>Destination ETD</span>
<th field="futureShipped">
<span translate>Date</span>
</th>
<th field="tfState">
<span translate>Destination State</span>
</th>
<th field="tfIpt">
<th field="futureIpt" title="Item Packing Type">
<span>IPT</span>
</th>
<th shrink field="futureState">
<span translate>State</span>
</th>
</tr>
</thead>
<tbody>
@ -125,38 +130,38 @@
{{::ticket.id}}
</span></td>
<td shrink-date>
<span class="chip {{$ctrl.compareDate(ticket.originETD)}}">
{{::ticket.originETD | date: 'dd/MM/yyyy'}}
<span class="chip {{$ctrl.compareDate(ticket.shipped)}}">
{{::ticket.shipped | date: 'dd/MM/yyyy'}}
</span>
</td>
<td>{{::ticket.ipt}}</td>
<td>
<span
class="chip {{$ctrl.stateColor(ticket.state)}}">
{{::ticket.state}}
</span>
</td>
<td>{{::ticket.ipt}}</td>
<td>{{::ticket.liters}}</td>
<td>{{::ticket.lines}}</td>
<td>
<span
ng-click="ticketDescriptor.show($event, ticket.ticketFuture)"
ng-click="ticketDescriptor.show($event, ticket.futureId)"
class="link">
{{::ticket.ticketFuture}}
{{::ticket.futureId}}
</span>
</td>
<td shrink-date>
<span class="chip {{$ctrl.compareDate(ticket.destETD)}}">
{{::ticket.destETD | date: 'dd/MM/yyyy'}}
<span class="chip {{$ctrl.compareDate(ticket.futureShipped)}}">
{{::ticket.futureShipped | date: 'dd/MM/yyyy'}}
</span>
</td>
<td>{{::ticket.futureIpt}}</td>
<td>
<span
class="chip {{$ctrl.stateColor(ticket.tfState)}}">
{{::ticket.tfState}}
class="chip {{$ctrl.stateColor(ticket.futureState)}}">
{{::ticket.futureState}}
</span>
</td>
<td>{{::ticket.tfIpt}}</td>
</tr>
</tbody>
</table>

View File

@ -11,15 +11,15 @@ export default class Controller extends Section {
search: true,
},
columns: [{
field: 'problems',
field: 'totalProblems',
searchable: false,
},
{
field: 'shipped',
searchable: false
},
{
field: 'originETD',
searchable: false
},
{
field: 'destETD',
field: 'futureShipped',
searchable: false
},
{
@ -27,7 +27,7 @@ export default class Controller extends Section {
searchable: false
},
{
field: 'tfState',
field: 'futureState',
searchable: false
},
{
@ -39,7 +39,7 @@ export default class Controller extends Section {
}
},
{
field: 'tfIpt',
field: 'futureIpt',
autocomplete: {
url: 'ItemPackingTypes',
showField: 'description',
@ -48,6 +48,9 @@ export default class Controller extends Section {
},
]
};
}
$postLink() {
this.setDefaultFilter();
}
@ -57,10 +60,9 @@ export default class Controller extends Section {
this.filterParams = {
originDated: today,
futureDated: today,
linesMax: '9999',
litersMax: '9999',
warehouseFk: 1
warehouseFk: this.vnConfig.warehouseFk
};
this.$.model.applyFilter(null, this.filterParams);
}
compareDate(date) {
@ -113,7 +115,17 @@ export default class Controller extends Section {
}
moveTicketsFuture() {
let params = { tickets: this.checked };
let ticketsToMove = [];
this.checked.forEach(ticket => {
ticketsToMove.push({
originId: ticket.id,
destinationId: ticket.futureId,
originShipped: ticket.shipped,
destinationShipped: ticket.futureShipped,
workerFk: ticket.workerFk
});
});
let params = {tickets: ticketsToMove};
return this.$http.post('Tickets/merge', params)
.then(() => {
this.$.model.refresh();
@ -123,18 +135,18 @@ export default class Controller extends Section {
exprBuilder(param, value) {
switch (param) {
case 'id':
return { 'id': value };
case 'ticketFuture':
return { 'ticketFuture': value };
case 'litersMax':
return { 'liters': value };
case 'linesMax':
return { 'lines': value };
case 'ipt':
return { 'ipt': value };
case 'tfIpt':
return { 'tfIpt': value };
case 'id':
return {'id': value};
case 'futureId':
return {'futureId': value};
case 'liters':
return {'liters': value};
case 'lines':
return {'lines': value};
case 'ipt':
return {'ipt': value};
case 'futureIpt':
return {'futureIpt': value};
}
}
}

View File

@ -2,33 +2,30 @@ import './index.js';
import crudModel from 'core/mocks/crud-model';
describe('Component vnTicketFuture', () => {
const today = new Date();
let controller;
let $httpBackend;
let $window;
beforeEach(ngModule('ticket')
);
beforeEach(ngModule('ticket'));
beforeEach(inject(($componentController, _$window_, _$httpBackend_) => {
beforeEach(inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$window = _$window_;
const $element = angular.element('<vn-ticket-future></vn-ticket-future>');
controller = $componentController('vnTicketFuture', { $element });
controller = $componentController('vnTicketFuture', {$element});
controller.$.model = crudModel;
controller.$.model.data = [{
id: 1,
checked: true,
state: "OK"
state: 'OK'
}, {
id: 2,
checked: true,
state: "Libre"
state: 'Libre'
}];
}));
describe('compareDate()', () => {
it('should return warning when the date is the present', () => {
let today = new Date();
let result = controller.compareDate(today);
expect(result).toEqual('warning');
@ -67,6 +64,7 @@ describe('Component vnTicketFuture', () => {
it('should return success to the OK tickets', () => {
const ok = controller.stateColor(controller.$.model.data[0].state);
const notOk = controller.stateColor(controller.$.model.data[1].state);
expect(ok).toEqual('success');
expect(notOk).not.toEqual('success');
});
@ -74,6 +72,7 @@ describe('Component vnTicketFuture', () => {
it('should return success to the FREE tickets', () => {
const notFree = controller.stateColor(controller.$.model.data[0].state);
const free = controller.stateColor(controller.$.model.data[1].state);
expect(free).toEqual('notice');
expect(notFree).not.toEqual('notice');
});
@ -81,18 +80,14 @@ describe('Component vnTicketFuture', () => {
describe('dateRange()', () => {
it('should return two dates with the hours at the start and end of the given date', () => {
const now = new Date();
const today = now.getDate();
const dateRange = controller.dateRange(now);
const dateRange = controller.dateRange(today);
const start = dateRange[0].toString();
const end = dateRange[1].toString();
expect(start).toContain(today);
expect(start).toContain(today.getDate());
expect(start).toContain('00:00:00');
expect(end).toContain(today);
expect(end).toContain(today.getDate());
expect(end).toContain('23:59:59');
});
});

View File

@ -1,6 +1,2 @@
Move confirmation: Do you want to move {{checked}} tickets to the future?
FREE: Free
DELIVERED: Delivered
ON_PREPARATION: On preparation
PACKED: Packed
Success: Tickets moved successfully!

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