Merge branch 'dev' into 2883-invoiceIn-Booking
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Javi Gallego 2021-11-18 07:36:03 +01:00
commit b936272898
359 changed files with 36628 additions and 12314 deletions

View File

@ -9,7 +9,7 @@ env:
rules:
require-jsdoc: 0
no-undef: 0
max-len: 0
max-len: ["error", {code: 120}]
eqeqeq: 0
operator-linebreak: 0
radix: 0
@ -17,7 +17,7 @@ rules:
camelcase: 0
default-case: 0
no-eq-null: 0
no-console: 0
no-console: ["error"]
no-warning-comments: 0
no-empty: [error, allowEmptyCatch: true]
complexity: 0

8
Jenkinsfile vendored
View File

@ -40,7 +40,7 @@ pipeline {
NODE_ENV = ""
}
steps {
nodejs('node-v12') {
nodejs('node-v14') {
sh 'npm install --no-audit --prefer-offline'
sh 'gulp install --ci'
}
@ -57,14 +57,14 @@ pipeline {
parallel {
stage('Frontend') {
steps {
nodejs('node-v12') {
nodejs('node-v14') {
sh 'jest --ci --reporters=default --reporters=jest-junit --maxWorkers=2'
}
}
}
// stage('Backend') {
// steps {
// nodejs('node-v12') {
// nodejs('node-v14') {
// sh 'gulp launchBackTest --ci'
// }
// }
@ -80,7 +80,7 @@ pipeline {
CREDENTIALS = credentials('docker-registry')
}
steps {
nodejs('node-v12') {
nodejs('node-v14') {
sh 'gulp build'
}

View File

@ -11,7 +11,6 @@ Required applications.
* Node.js = 14.x LTS
* Docker
* Git
* Docker
You will need to install globally the following items.
```

View File

@ -89,6 +89,7 @@ module.exports = Self => {
await uploadNewFile(ctx, dms, myOptions);
if (tx) await tx.commit();
return dms;
} catch (e) {
if (tx) await tx.rollback();

View File

@ -96,6 +96,7 @@ module.exports = Self => {
}
if (tx) await tx.commit();
return addedDms;
} catch (e) {
if (tx) await tx.rollback();

View File

@ -7,8 +7,7 @@ module.exports = function(Self) {
required: true,
description: `Code of the table you ask its configuration`,
http: {source: 'body'}
}
],
}],
returns: {
type: 'object',
root: true
@ -29,6 +28,6 @@ module.exports = function(Self) {
config.userFk = ctx.req.accessToken.userId;
return await Self.app.models.UserConfigView.create(config);
return Self.app.models.UserConfigView.create(config);
};
};

View File

@ -29,6 +29,9 @@
"ChatConfig": {
"dataSource": "vn"
},
"DefaultViewConfig": {
"dataSource": "vn"
},
"Delivery": {
"dataSource": "vn"
},

View File

@ -25,6 +25,9 @@
},
"isAutoConciliated": {
"type": "boolean"
},
"maxAmount": {
"type": "number"
}
},
"acls": [{

View File

@ -0,0 +1,25 @@
{
"name": "DefaultViewConfig",
"base": "VnModel",
"options": {
"mysql": {
"table": "salix.defaultViewConfig"
}
},
"properties": {
"tableCode": {
"id": true,
"type": "string",
"required": true
},
"columns": {
"type": "object"
}
},
"acls": [{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}]
}

View File

@ -23,5 +23,13 @@
"model": "Account",
"foreignKey": "userFk"
}
}
},
"acls": [
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
]
}

View File

@ -151,6 +151,7 @@ module.exports = Self => {
await fs.unlink(srcFilePath);
await tx.commit();
return newImage;
} catch (e) {
await tx.rollback();

View File

@ -1,18 +0,0 @@
CREATE TABLE `vn`.`invoiceInLog` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`originFk` MEDIUMINT UNSIGNED NOT NULL,
`userFk` int(10) unsigned DEFAULT NULL,
`action` set('insert','update','delete') COLLATE utf8_unicode_ci NOT NULL,
`creationDate` timestamp NULL DEFAULT current_timestamp(),
`description` text CHARACTER SET utf8 DEFAULT NULL,
`changedModel` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
`oldInstance` text COLLATE utf8_unicode_ci DEFAULT NULL,
`newInstance` text COLLATE utf8_unicode_ci DEFAULT NULL,
`changedModelId` int(11) DEFAULT NULL,
`changedModelValue` varchar(45) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `originFk` (`originFk`),
KEY `userFk` (`userFk`),
CONSTRAINT `invoiceInLog_ibfk_1` FOREIGN KEY (`originFk`) REFERENCES `vn`.`invoiceIn` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `invoiceInLog_ibfk_2` FOREIGN KEY (`userFk`) REFERENCES `account`.`user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

View File

@ -1,192 +0,0 @@
DROP PROCEDURE IF EXISTS `vn`.`sale_getProblems`;
DELIMITER $$
$$
CREATE
DEFINER = root@`%` PROCEDURE `vn`.`sale_getProblems`(IN vIsTodayRelative TINYINT(1))
BEGIN
/**
* Calcula los problemas de cada venta
* para un conjunto de tickets.
*
* @table tmp.sale_getProblems(ticketFk, clientFk, warehouseFk, shipped) Identificadores de los tickets a calcular
* @return tmp.sale_problems
*/
DECLARE vWarehouse INT;
DECLARE vDate DATE;
DECLARE vAvailableCache INT;
DECLARE vDone INT DEFAULT 0;
DECLARE vComponentCount INT;
DECLARE vCursor CURSOR FOR
SELECT DISTINCT tt.warehouseFk, IF(vIsTodayRelative, CURDATE(), date(tt.shipped))
FROM tmp.sale_getProblems tt
WHERE DATE(tt.shipped) BETWEEN CURDATE()
AND TIMESTAMPADD(DAY, IF(vIsTodayRelative, 9.9, 1.9), CURDATE());
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = 1;
DROP TEMPORARY TABLE IF EXISTS tmp.sale_problems;
CREATE TEMPORARY TABLE tmp.sale_problems (
ticketFk INT(11),
saleFk INT(11),
isFreezed INTEGER(1) DEFAULT 0,
risk DECIMAL(10,2) DEFAULT 0,
hasTicketRequest INTEGER(1) DEFAULT 0,
isAvailable INTEGER(1) DEFAULT 1,
itemShortage VARCHAR(250),
isTaxDataChecked INTEGER(1) DEFAULT 1,
itemDelay VARCHAR(250),
hasComponentLack INTEGER(1),
PRIMARY KEY (ticketFk, saleFk)
) ENGINE = MEMORY;
DROP TEMPORARY TABLE IF EXISTS tmp.ticket_list;
CREATE TEMPORARY TABLE tmp.ticket_list
(PRIMARY KEY (ticketFk))
ENGINE = MEMORY
SELECT tp.ticketFk, c.id clientFk
FROM tmp.sale_getProblems tp
JOIN vn.client c ON c.id = tp.clientFk;
SELECT COUNT(*) INTO vComponentCount
FROM vn.component c
WHERE c.isRequired;
INSERT INTO tmp.sale_problems(ticketFk, hasComponentLack, saleFk)
SELECT tl.ticketFk, (COUNT(DISTINCT s.id) * vComponentCount > COUNT(c.id)), s.id
FROM tmp.ticket_list tl
JOIN vn.sale s ON s.ticketFk = tl.ticketFk
LEFT JOIN vn.saleComponent sc ON sc.saleFk = s.id
LEFT JOIN vn.component c ON c.id = sc.componentFk AND c.isRequired
GROUP BY tl.ticketFk, s.id;
INSERT INTO tmp.sale_problems(ticketFk, isFreezed)
SELECT DISTINCT tl.ticketFk, TRUE
FROM tmp.ticket_list tl
JOIN vn.client c ON c.id = tl.clientFk
WHERE c.isFreezed
ON DUPLICATE KEY UPDATE
isFreezed = c.isFreezed;
DROP TEMPORARY TABLE IF EXISTS tmp.clientGetDebt;
CREATE TEMPORARY TABLE tmp.clientGetDebt
(PRIMARY KEY (clientFk))
ENGINE = MEMORY
SELECT DISTINCT clientFk
FROM tmp.ticket_list;
CALL clientGetDebt(CURDATE());
INSERT INTO tmp.sale_problems(ticketFk, risk)
SELECT DISTINCT tl.ticketFk, r.risk
FROM tmp.ticket_list tl
JOIN vn.ticket t ON t.id = tl.ticketFk
JOIN vn.agencyMode a ON t.agencyModeFk = a.id
JOIN tmp.risk r ON r.clientFk = t.clientFk
JOIN vn.client c ON c.id = t.clientFk
JOIN vn.clientConfig cc
WHERE r.risk > c.credit + 10
AND a.isRiskFree = FALSE
ON DUPLICATE KEY UPDATE
risk = r.risk;
INSERT INTO tmp.sale_problems(ticketFk, hasTicketRequest)
SELECT DISTINCT tl.ticketFk, TRUE
FROM tmp.ticket_list tl
JOIN vn.ticketRequest tr ON tr.ticketFk = tl.ticketFk
WHERE tr.isOK IS NULL
ON DUPLICATE KEY UPDATE
hasTicketRequest = TRUE;
OPEN vCursor;
WHILE NOT vDone
DO
FETCH vCursor INTO vWarehouse, vDate;
CALL cache.available_refresh(vAvailableCache, FALSE, vWarehouse, vDate);
INSERT INTO tmp.sale_problems(ticketFk, isAvailable, saleFk)
SELECT tl.ticketFk, FALSE, s.id
FROM tmp.ticket_list tl
JOIN vn.ticket t ON t.id = tl.ticketFk
JOIN vn.sale s ON s.ticketFk = t.id
JOIN vn.item i ON i.id = s.itemFk
JOIN vn.itemType it on it.id = i.typeFk
LEFT JOIN cache.available av ON av.item_id = i.id
AND av.calc_id = vAvailableCache
WHERE date(t.shipped) = vDate
AND it.categoryFk != 6
AND IFNULL(av.available, 0) < 0
AND s.isPicked = FALSE
AND NOT i.generic
AND vWarehouse = t.warehouseFk
GROUP BY tl.ticketFk
ON DUPLICATE KEY UPDATE
isAvailable = FALSE, saleFk = VALUE(saleFk);
INSERT INTO tmp.sale_problems(ticketFk, itemShortage, saleFk)
SELECT ticketFk, problem, saleFk
FROM (
SELECT tl.ticketFk, CONCAT('F: ',GROUP_CONCAT(i.id, ' ', i.longName, ' ')) problem, s.id AS saleFk
FROM tmp.ticket_list tl
JOIN vn.ticket t ON t.id = tl.ticketFk
JOIN vn.sale s ON s.ticketFk = t.id
JOIN vn.item i ON i.id = s.itemFk
JOIN vn.itemType it on it.id = i.typeFk
LEFT JOIN vn.itemShelvingStock_byWarehouse issw ON issw.itemFk = i.id AND issw.warehouseFk = t.warehouseFk
LEFT JOIN cache.available av ON av.item_id = i.id AND av.calc_id = vAvailableCache
WHERE IFNULL(av.available, 0) < 0
AND s.quantity > IFNULL(issw.visible, 0)
AND s.quantity > 0
AND s.isPicked = FALSE
AND s.reserved = FALSE
AND it.categoryFk != 6
AND IF(vIsTodayRelative, TRUE, date(t.shipped) = vDate)
AND NOT i.generic
AND CURDATE() = vDate
AND t.warehouseFk = vWarehouse
GROUP BY tl.ticketFk LIMIT 1) sub
ON DUPLICATE KEY UPDATE
itemShortage = sub.problem, saleFk = sub.saleFk;
INSERT INTO tmp.sale_problems(ticketFk, itemDelay, saleFk)
SELECT ticketFk, problem, saleFk
FROM (
SELECT tl.ticketFk, GROUP_CONCAT('I: ',i.id, ' ', i.longName, ' ') problem, s.id AS saleFk
FROM tmp.ticket_list tl
JOIN vn.ticket t ON t.id = tl.ticketFk
JOIN vn.sale s ON s.ticketFk = t.id
JOIN vn.item i ON i.id = s.itemFk
JOIN vn.itemType it on it.id = i.typeFk
LEFT JOIN vn.itemShelvingStock_byWarehouse issw ON issw.itemFk = i.id AND issw.warehouseFk = t.warehouseFk
WHERE s.quantity > IFNULL(issw.visible, 0)
AND s.quantity > 0
AND s.isPicked = FALSE
AND s.reserved = FALSE
AND it.categoryFk != 6
AND IF(vIsTodayRelative, TRUE, date(t.shipped) = vDate)
AND NOT i.generic
AND CURDATE() = vDate
AND t.warehouseFk = vWarehouse
GROUP BY tl.ticketFk LIMIT 1) sub
ON DUPLICATE KEY UPDATE
itemDelay = sub.problem, saleFk = sub.saleFk;
END WHILE;
CLOSE vCursor;
INSERT INTO tmp.sale_problems(ticketFk, isTaxDataChecked)
SELECT DISTINCT tl.ticketFk, FALSE
FROM tmp.ticket_list tl
JOIN vn.client c ON c.id = tl.clientFk
WHERE c.isTaxDataChecked = FALSE
ON DUPLICATE KEY UPDATE
isTaxDataChecked = FALSE;
DROP TEMPORARY TABLE
tmp.clientGetDebt,
tmp.ticket_list;
END;;$$
DELIMITER ;

View File

@ -1,95 +0,0 @@
DROP PROCEDURE IF EXISTS `vn`.`ticket_componentMakeUpdate`;
DELIMITER $$
$$
CREATE
DEFINER = root@`%` PROCEDURE `vn`.`ticket_componentMakeUpdate`(IN vTicketFk INT, IN vClientFk INT,
IN vNickname VARCHAR(50), IN vAgencyModeFk INT,
IN vAddressFk INT, IN vZoneFk INT, IN vWarehouseFk TINYINT,
IN vCompanyFk SMALLINT, IN vShipped DATETIME,
IN vLanded DATE, IN vIsDeleted TINYINT(1),
IN vHasToBeUnrouted TINYINT(1), IN vOption INT)
BEGIN
/**
* Modifica en el ticket los campos que se le pasan por parámetro
* y cambia sus componentes
*
* @param vTicketFk Id del ticket a modificar
* @param vClientFk nuevo cliente
* @param vNickname nuevo alias
* @param vAgencyModeFk nueva agencia
* @param vAddressFk nuevo consignatario
* @param vZoneFk nueva zona
* @param vWarehouseFk nuevo almacen
* @param vCompanyFk nueva empresa
* @param vShipped nueva fecha del envio de mercancia
* @param vLanded nueva fecha de recepcion de mercancia
* @param vIsDeleted si se borra el ticket
* @param vHasToBeUnrouted si se le elimina la ruta al ticket
* @param vOption opcion para el case del proc ticketComponentUpdateSale
*/
DECLARE vPrice DECIMAL(10,2);
DECLARE vBonus DECIMAL(10,2);
CALL ticket_componentPreview (vTicketFk, vLanded, vAddressFk, vZoneFk, vWarehouseFk);
IF (SELECT addressFk FROM ticket WHERE id = vTicketFk) <> vAddressFk THEN
UPDATE ticket t
JOIN address a ON a.id = vAddressFk
SET t.nickname = a.nickname
WHERE t.id = vTicketFk;
END IF;
CALL zone_getShippedWarehouse(vlanded, vAddressFk, vAgencyModeFk);
SELECT zoneFk, price, bonus INTO vZoneFk, vPrice, vBonus
FROM tmp.zoneGetShipped
WHERE shipped BETWEEN DATE(vShipped) AND util.dayEnd(vShipped) AND warehouseFk = vWarehouseFk LIMIT 1;
UPDATE ticket t
SET
t.clientFk = vClientFk,
t.nickname = vNickname,
t.agencyModeFk = vAgencyModeFk,
t.addressFk = vAddressFk,
t.zoneFk = vZoneFk,
t.zonePrice = vPrice,
t.zoneBonus = vBonus,
t.warehouseFk = vWarehouseFk,
t.companyFk = vCompanyFk,
t.landed = vLanded,
t.shipped = vShipped,
t.isDeleted = vIsDeleted
WHERE
t.id = vTicketFk;
IF vHasToBeUnrouted THEN
UPDATE ticket t SET t.routeFk = NULL
WHERE t.id = vTicketFk;
END IF;
IF vOption <> 8 THEN
DROP TEMPORARY TABLE IF EXISTS tmp.sale;
CREATE TEMPORARY TABLE tmp.sale
(PRIMARY KEY (saleFk))
ENGINE = MEMORY
SELECT id AS saleFk, vWarehouseFk warehouseFk
FROM sale s WHERE s.ticketFk = vTicketFk;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketComponent;
CREATE TEMPORARY TABLE tmp.ticketComponent
SELECT * FROM tmp.ticketComponentPreview;
CALL ticketComponentUpdateSale (vOption);
DROP TEMPORARY TABLE tmp.sale;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketComponent;
END IF;
DROP TEMPORARY TABLE tmp.zoneGetShipped, tmp.ticketComponentPreview;
END$$
DELIMITER ;

View File

@ -1,15 +0,0 @@
DELETE FROM `salix`.`ACL` WHERE id = 189;
DELETE FROM `salix`.`ACL` WHERE id = 188;
UPDATE `salix`.`ACL` tdms SET tdms.accessType = '*'
WHERE tdms.id = 165;
INSERT INTO `salix`.`ACL` (model, principalId, property, accessType)
VALUES
('InvoiceInTax','administrative', '*', '*'),
('InvoiceInLog','administrative', '*', 'READ');
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES
('InvoiceOut', 'createManualInvoice', 'WRITE', 'ALLOW', 'ROLE', 'invoicing'),
('InvoiceOut', 'globalInvoicing', 'WRITE', 'ALLOW', 'ROLE', 'invoicing');

View File

@ -1,14 +0,0 @@
create table `vn`.`itemMatchProperties`
(
itemFk int not null,
name varchar(80) not null,
producer varchar(80) not null,
size int not null,
constraint itemMatchProperties_pk
primary key (itemFk, name, producer, size),
constraint itemFk___fk
foreign key (itemFk) references item (id)
on update cascade on delete cascade
)
comment 'Propiedades para encontrar articulos equivalentes en verdnatura';

View File

@ -1,21 +0,0 @@
drop procedure `vn`.`invoiceFromClient`;
DELIMITER $$
$$
create
definer = root@`%` procedure `vn`.`invoiceFromClient`(IN vMaxTicketDate datetime, IN vClientFk INT, IN vCompanyFk INT)
BEGIN
DECLARE vMinTicketDate DATE DEFAULT TIMESTAMPADD(YEAR, -3, CURDATE());
SET vMaxTicketDate = util.dayend(vMaxTicketDate);
DROP TEMPORARY TABLE IF EXISTS `ticketToInvoice`;
CREATE TEMPORARY TABLE `ticketToInvoice`
(PRIMARY KEY (`id`))
ENGINE = MEMORY
SELECT id FROM ticket t
WHERE t.clientFk = vClientFk
AND t.refFk IS NULL
AND t.companyFk = vCompanyFk
AND (t.shipped BETWEEN vMinTicketDate AND vMaxTicketDate);
END;;$$
DELIMITER ;

View File

@ -1,45 +0,0 @@
drop procedure `vn`.`invoiceOut_newFromClient`;
DELIMITER $$
$$
create
definer = root@`%` procedure `vn`.`invoiceOut_newFromClient`(IN vClientFk int, IN vSerial char(2), IN vMaxShipped date,
IN vCompanyFk int, IN vTaxArea varchar(25),
IN vRef varchar(25), OUT vInvoiceId int)
BEGIN
/**
* Factura los tickets de un cliente hasta una fecha dada
* @param vClientFk Id del cliente a facturar
* @param vSerial Serie de factura
* @param vMaxShipped Fecha hasta la cual cogera tickets para facturar
* @param vCompanyFk Id de la empresa desde la que se factura
* @param vTaxArea Tipo de iva en relacion a la empresa y al cliente, NULL por defecto
* @param vRef Referencia de la factura en caso que se quiera forzar, NULL por defecto
* @return vInvoiceId factura
*/
DECLARE vIsRefEditable BOOLEAN;
IF vRef IS NOT NULL THEN
SELECT isRefEditable INTO vIsRefEditable
FROM invoiceOutSerial
WHERE code = vSerial;
IF NOT vIsRefEditable THEN
CALL util.throw('serial non editable');
END IF;
END IF;
CALL invoiceFromClient(vMaxShipped, vClientFk, vCompanyFk);
CALL invoiceOut_new(vSerial, CURDATE(), vTaxArea, vInvoiceId);
UPDATE invoiceOut
SET `ref` = vRef
WHERE id = vInvoiceId
AND vRef IS NOT NULL;
IF vSerial <> 'R' AND NOT ISNULL(vInvoiceId) AND vInvoiceId <> 0 THEN
CALL invoiceOutBooking(vInvoiceId);
END IF;
END;;$$
DELIMITER ;

View File

@ -1,38 +0,0 @@
drop procedure `vn`.`invoiceOut_newFromTicket`;
DELIMITER $$
$$
create
definer = root@`%` procedure `vn`.`invoiceOut_newFromTicket`(IN vTicketFk int, IN vSerial char(2), IN vTaxArea varchar(25),
IN vRef varchar(25), OUT vInvoiceId int)
BEGIN
/**
* Factura un ticket
* @param vTicketFk Id del ticket
* @param vSerial Serie de factura
* @param vTaxArea Area de la factura en caso de querer forzarlo,
* en la mayoria de los casos poner NULL
* @return vInvoiceId
*/
DECLARE vIsRefEditable BOOLEAN;
CALL invoiceFromTicket(vTicketFk);
CALL invoiceOut_new(vSerial, CURDATE(), vTaxArea, vInvoiceId);
IF vRef IS NOT NULL THEN
SELECT isRefEditable INTO vIsRefEditable
FROM invoiceOutSerial
WHERE code = vSerial;
IF NOT vIsRefEditable THEN
CALL util.throw('serial non editable');
END IF;
UPDATE invoiceOut
SET `ref` = vRef
WHERE id = vInvoiceId;
END IF;
IF vSerial <> 'R' AND NOT ISNULL(vInvoiceId) AND vInvoiceId <> 0 THEN
CALL invoiceOutBooking(vInvoiceId);
END IF;
END;;$$
DELIMITER ;

View File

@ -1,7 +0,0 @@
ALTER TABLE `vn`.`sample` ADD COLUMN
(`datepickerEnabled` TINYINT(1) NOT NULL DEFAULT 0);
ALTER TABLE `vn`.`sample` MODIFY code VARCHAR(25) charset utf8 NOT NULL;
INSERT INTO `vn`.`sample` (code, description, isVisible, hasCompany, hasPreview, datepickerEnabled)
VALUES ('client-debt-statement', 'Extracto del cliente', 1, 0, 1, 1);

View File

@ -1,109 +0,0 @@
drop procedure `vn`.`ticket_close`;
DELIMITER $$
$$
create
definer = root@`%` procedure `vn`.`ticket_close`()
BEGIN
/**
* Realiza el cierre de todos los
* tickets de la tabla tmp.ticket_close.
*
* @table tmp.ticket_close(ticketFk) Identificadores de los tickets a cerrar
*/
DECLARE vDone BOOL;
DECLARE vClientFk INT;
DECLARE vCurTicketFk INT;
DECLARE vIsTaxDataChecked BOOL;
DECLARE vCompanyFk INT;
DECLARE vShipped DATE;
DECLARE vNewInvoiceId INT;
DECLARE vHasDailyInvoice BOOL;
DECLARE vWithPackage BOOL;
DECLARE vHasToInvoice BOOL;
DECLARE cur CURSOR FOR
SELECT ticketFk FROM tmp.ticket_close;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN
RESIGNAL;
END;
OPEN cur;
proc: LOOP
SET vDone = FALSE;
FETCH cur INTO vCurTicketFk;
IF vDone THEN
LEAVE proc;
END IF;
-- Fetch ticket data
SELECT
c.id,
c.isTaxDataChecked,
t.companyFk,
t.shipped,
co.hasDailyInvoice,
w.isManaged,
c.hasToInvoice
INTO vClientFk,
vIsTaxDataChecked,
vCompanyFk,
vShipped,
vHasDailyInvoice,
vWithPackage,
vHasToInvoice
FROM ticket t
JOIN `client` c ON c.id = t.clientFk
JOIN province p ON p.id = c.provinceFk
JOIN country co ON co.id = p.countryFk
JOIN warehouse w ON w.id = t.warehouseFk
WHERE t.id = vCurTicketFk;
INSERT INTO ticketPackaging (ticketFk, packagingFk, quantity)
(SELECT vCurTicketFk, p.id, COUNT(*)
FROM expedition e
JOIN packaging p ON p.itemFk = e.itemFk
WHERE e.ticketFk = vCurTicketFk AND p.isPackageReturnable
AND vWithPackage
GROUP BY p.itemFk);
-- No retornables o no catalogados
INSERT INTO sale (itemFk, ticketFk, concept, quantity, price, isPriceFixed)
(SELECT e.itemFk, vCurTicketFk, i.name, COUNT(*) AS amount, getSpecialPrice(e.itemFk, vClientFk), 1
FROM expedition e
JOIN item i ON i.id = e.itemFk
LEFT JOIN packaging p ON p.itemFk = i.id
WHERE e.ticketFk = vCurTicketFk AND IFNULL(p.isPackageReturnable, 0) = 0
AND getSpecialPrice(e.itemFk, vClientFk) > 0
GROUP BY e.itemFk);
CALL vn.zonePromo_Make();
IF(vHasDailyInvoice) AND vHasToInvoice THEN
-- Facturacion rapida
CALL ticketTrackingAdd(vCurTicketFk, 'DELIVERED', NULL);
-- Facturar si está contabilizado
IF vIsTaxDataChecked THEN
CALL invoiceOut_newFromClient(
vClientFk,
(SELECT invoiceSerial(vClientFk, vCompanyFk, 'M')),
vShipped,
vCompanyFk,
NULL,
NULL,
vNewInvoiceId);
END IF;
ELSE
CALL ticketTrackingAdd(vCurTicketFk, (SELECT vn.getAlert3State(vCurTicketFk)), NULL);
END IF;
END LOOP;
CLOSE cur;
END;;$$
DELIMITER ;

View File

@ -1,118 +0,0 @@
drop procedure `vn`.`ticket_closeAll`;
DELIMITER $$
$$
create definer = root@`%` procedure `vn`.`ticket_closeAll__`()
BEGIN
/**
* Realiza el cierre de todos los
* tickets de la tabla tmp.ticketClosure.
*
* @param vTicketFk Id del ticket
*/
DECLARE vDone BOOL;
DECLARE vClientFk INT;
DECLARE vCurTicketFk INT;
DECLARE vIsTaxDataChecked BOOL;
DECLARE vCompanyFk INT;
DECLARE vShipped DATE;
DECLARE vNewInvoiceId INT;
DECLARE vHasDailyInvoice BOOL;
DECLARE vWithPackage BOOL;
DECLARE vHasToInvoice BOOL;
DECLARE cur CURSOR FOR
SELECT ticketFk FROM tmp.ticketClosure;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN
RESIGNAL;
END;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketClosure2;
CREATE TEMPORARY TABLE tmp.ticketClosure2
SELECT ticketFk FROM tmp.ticketClosure;
INSERT INTO tmp.ticketClosure
SELECT id FROM stowaway s
JOIN tmp.ticketClosure2 tc ON s.shipFk = tc.ticketFk;
OPEN cur;
proc: LOOP
SET vDone = FALSE;
FETCH cur INTO vCurTicketFk;
IF vDone THEN
LEAVE proc;
END IF;
-- ticketClosure start
SELECT
c.id,
c.isTaxDataChecked,
t.companyFk,
t.shipped,
co.hasDailyInvoice,
w.isManaged,
c.hasToInvoice
INTO vClientFk,
vIsTaxDataChecked,
vCompanyFk,
vShipped,
vHasDailyInvoice,
vWithPackage,
vHasToInvoice
FROM ticket t
JOIN `client` c ON c.id = t.clientFk
JOIN province p ON p.id = c.provinceFk
JOIN country co ON co.id = p.countryFk
JOIN warehouse w ON w.id = t.warehouseFk
WHERE t.id = vCurTicketFk;
INSERT INTO ticketPackaging (ticketFk, packagingFk, quantity)
(SELECT vCurTicketFk, p.id, COUNT(*)
FROM expedition e
JOIN packaging p ON p.itemFk = e.itemFk
WHERE e.ticketFk = vCurTicketFk AND p.isPackageReturnable
AND vWithPackage
GROUP BY p.itemFk);
-- No retornables o no catalogados
INSERT INTO sale (itemFk, ticketFk, concept, quantity, price, isPriceFixed)
(SELECT e.itemFk, vCurTicketFk, i.name, COUNT(*) AS amount, getSpecialPrice(e.itemFk, vClientFk), 1
FROM expedition e
JOIN item i ON i.id = e.itemFk
LEFT JOIN packaging p ON p.itemFk = i.id
WHERE e.ticketFk = vCurTicketFk AND IFNULL(p.isPackageReturnable, 0) = 0
AND getSpecialPrice(e.itemFk, vClientFk) > 0
GROUP BY e.itemFk);
CALL vn.zonePromo_Make();
IF(vHasDailyInvoice) AND vHasToInvoice THEN
-- Facturacion rapida
CALL ticketTrackingAdd(vCurTicketFk, 'DELIVERED', NULL);
-- Facturar si está contabilizado
IF vIsTaxDataChecked THEN
CALL invoiceOut_newFromClient(
vClientFk,
(SELECT invoiceSerial(vClientFk, vCompanyFk, 'M')),
vShipped,
vCompanyFk,
NULL,
NULL,
vNewInvoiceId);
END IF;
ELSE
CALL ticketTrackingAdd(vCurTicketFk, (SELECT vn.getAlert3State(vCurTicketFk)), NULL);
END IF;
END LOOP;
CLOSE cur;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketClosure;
END;;$$
DELIMITER ;

View File

@ -1,34 +0,0 @@
drop procedure `vn`.`ticket_closeByTicket`;
DELIMITER $$
$$
create
definer = root@`%` procedure `vn`.`ticket_closeByTicket`(IN vTicketFk int)
BEGIN
/**
* Inserta el ticket en la tabla temporal
* para ser cerrado.
*
* @param vTicketFk Id del ticket
*/
DROP TEMPORARY TABLE IF EXISTS tmp.ticket_close;
CREATE TEMPORARY TABLE tmp.ticket_close ENGINE = MEMORY (
SELECT
t.id AS ticketFk
FROM expedition e
INNER JOIN ticket t ON t.id = e.ticketFk
LEFT JOIN ticketState ts ON ts.ticketFk = t.id
JOIN alertLevel al ON al.id = ts.alertLevel
WHERE
al.code = 'PACKED'
AND t.id = vTicketFk
AND t.refFk IS NULL
GROUP BY e.ticketFk);
CALL ticket_close();
DROP TEMPORARY TABLE tmp.ticket_close;
END;;$$
DELIMITER ;

View File

@ -1,2 +0,0 @@
INSERT INTO `vn`.`sample` (`code`, `description`, `isVisible`, `hasCompany`, `hasPreview`)
VALUES ('credit-request', 'Solicitud de crédito', 1, 1, 1);

View File

@ -1 +0,0 @@
DROP TRIGGER vn.supplierAccount_AfterInsert;

View File

@ -1,33 +0,0 @@
DROP TRIGGER IF EXISTS vn.ticket_afterUpdate;
DELIMITER $$
$$
CREATE DEFINER=`root`@`%` TRIGGER vn.`ticket_afterUpdate`
AFTER UPDATE ON `ticket`
FOR EACH ROW
BEGIN
IF !(NEW.id <=> OLD.id)
OR !(NEW.warehouseFk <=> OLD.warehouseFk)
OR !(NEW.shipped <=> OLD.shipped) THEN
CALL stock.log_add('ticket', NEW.id, OLD.id);
END IF;
IF !(NEW.clientFk <=> OLD.clientFk)
OR !(NEW.addressFk <=> OLD.addressFk)
OR !(NEW.companyFk <=> OLD.companyFk) THEN
CALL ticket_requestRecalc(NEW.id);
END IF;
IF NEW.clientFk = 2067 AND !(NEW.clientFk <=> OLD.clientFk) THEN
-- Fallo que se insertan no se sabe como tickets en este cliente
INSERT INTO vn.mail SET
`sender` = 'jgallego@verdnatura.es',
`replyTo` = 'jgallego@verdnatura.es',
`subject` = 'Modificado ticket al cliente 2067',
`body` = CONCAT(account.myUserGetName(), ' ha modificado el ticket ',
NEW.id);
END IF;
END$$
DELIMITER ;

View File

@ -0,0 +1 @@
UPDATE salix.ACL t SET t.principalId = 'employee' WHERE t.id = 269;

View File

@ -0,0 +1,3 @@
ALTER TABLE vn.accountingType ADD maxAmount INT DEFAULT NULL NULL;
UPDATE vn.accountingType SET maxAmount = 1000 WHERE code = 'cash';

View File

@ -0,0 +1,144 @@
DROP PROCEDURE IF EXISTS vn.item_getBalance;
DELIMITER $$
$$
CREATE
definer = root@`%` procedure vn.item_getBalance(IN vItemId int, IN vWarehouse int)
BEGIN
DECLARE vDateInventory DATETIME;
DECLARE vCurdate DATE DEFAULT CURDATE();
DECLARE vDayEnd DATETIME DEFAULT util.dayEnd(vCurdate);
SELECT inventoried INTO vDateInventory FROM config;
SET @a = 0;
SET @currentLineFk = 0;
SET @shipped = '';
SELECT DATE(@shipped:= shipped) shipped,
alertLevel,
stateName,
origin,
reference,
clientFk,
name,
`in` AS invalue,
`out`,
@a := @a + IFNULL(`in`,0) - IFNULL(`out`,0) as balance,
@currentLineFk := IF (@shipped < CURDATE()
OR (@shipped = CURDATE() AND (isPicked OR alertLevel >= 2)),
lineFk,@currentLineFk) lastPreparedLineFk,
isTicket,
lineFk,
isPicked,
clientType,
claimFk
FROM
( SELECT tr.landed AS shipped,
b.quantity AS `in`,
NULL AS `out`,
al.id AS alertLevel,
st.name AS stateName,
s.name AS name,
e.ref AS reference,
e.id AS origin,
s.id AS clientFk,
IF(al.id = 3, TRUE, FALSE) isPicked,
FALSE AS isTicket,
b.id lineFk,
NULL `order`,
NULL AS clientType,
NULL AS claimFk
FROM buy b
JOIN entry e ON e.id = b.entryFk
JOIN travel tr ON tr.id = e.travelFk
JOIN supplier s ON s.id = e.supplierFk
JOIN alertLevel al ON al.id =
CASE
WHEN tr.shipped < CURDATE() THEN 3
WHEN tr.shipped = CURDATE() AND tr.isReceived = TRUE THEN 3
ELSE 0
END
JOIN state st ON st.code = al.code
WHERE tr.landed >= vDateInventory
AND vWarehouse = tr.warehouseInFk
AND b.itemFk = vItemId
AND e.isInventory = FALSE
AND e.isRaid = FALSE
UNION ALL
SELECT tr.shipped,
NULL,
b.quantity,
al.id,
st.name,
s.name,
e.ref,
e.id,
s.id,
IF(al.id = 3, TRUE, FALSE),
FALSE,
b.id,
NULL,
NULL,
NULL
FROM buy b
JOIN entry e ON e.id = b.entryFk
JOIN travel tr ON tr.id = e.travelFk
JOIN warehouse w ON w.id = tr.warehouseOutFk
JOIN supplier s ON s.id = e.supplierFk
JOIN alertLevel al ON al.id =
CASE
WHEN tr.shipped < CURDATE() THEN 3
WHEN tr.shipped = CURDATE() AND tr.isReceived = TRUE THEN 3
ELSE 0
END
JOIN state st ON st.code = al.code
WHERE tr.shipped >= vDateInventory
AND vWarehouse =tr.warehouseOutFk
AND s.id <> 4
AND b.itemFk = vItemId
AND e.isInventory = FALSE
AND w.isFeedStock = FALSE
AND e.isRaid = FALSE
UNION ALL
SELECT DATE(t.shipped),
NULL,
s.quantity,
al.id,
st.name,
t.nickname,
t.refFk,
t.id,
t.clientFk,
stk.id,
TRUE,
s.id,
st.`order`,
ct.code,
cl.id
FROM sale s
JOIN ticket t ON t.id = s.ticketFk
LEFT JOIN ticketState ts ON ts.ticket = t.id
LEFT JOIN state st ON st.code = ts.code
JOIN client c ON c.id = t.clientFk
JOIN clientType ct ON ct.id = c.clientTypeFk
JOIN alertLevel al ON al.id =
CASE
WHEN t.shipped < curdate() THEN 3
WHEN t.shipped > util.dayEnd(curdate()) THEN 0
ELSE IFNULL(ts.alertLevel, 0)
END
LEFT JOIN state stPrep ON stPrep.`code` = 'PREPARED'
LEFT JOIN saleTracking stk ON stk.saleFk = s.id AND stk.stateFk = stPrep.id
LEFT JOIN claim cl ON cl.ticketFk = t.id
WHERE t.shipped >= vDateInventory
AND s.itemFk = vItemId
AND vWarehouse =t.warehouseFk
ORDER BY shipped, alertLevel DESC, isTicket, `order` DESC, isPicked DESC, `in` DESC, `out` DESC
) AS itemDiary;
END;
$$
DELIMITER ;

View File

@ -0,0 +1,4 @@
ALTER TABLE vn.payMethod CHANGE ibanRequired ibanRequiredForClients tinyint(3) DEFAULT 0 NULL;
ALTER TABLE vn.payMethod ADD ibanRequiredForSuppliers tinyint(3) DEFAULT 0 NULL;
ALTER TABLE vn.payMethod CHANGE ibanRequiredForSuppliers ibanRequiredForSuppliers tinyint(3) DEFAULT 0 NULL AFTER ibanRequiredForClients;
UPDATE vn.payMethod SET ibanRequiredForSuppliers = 1 WHERE code = 'wireTransfer';

View File

@ -0,0 +1,2 @@
ALTER TABLE vn.silexACL MODIFY module VARCHAR(50) NOT NULL;
ALTER TABLE vn.silexACL MODIFY method VARCHAR(50) NOT NULL;

View File

@ -0,0 +1,48 @@
drop procedure `vn`.`ticket_getProblems`;
DELIMITER $$
$$
create
definer = root@`%` procedure `vn`.`ticket_getProblems`(IN vIsTodayRelative tinyint(1))
BEGIN
/**
* Calcula los problemas para un conjunto de tickets.
* Agrupados por ticket
*
* @table tmp.sale_getProblems(ticketFk, clientFk, warehouseFk, shipped) Identificadores de los tickets a calcular
* @return tmp.ticket_problems
*/
CALL sale_getProblems(vIsTodayRelative);
DROP TEMPORARY TABLE IF EXISTS tmp.ticket_problems;
CREATE TEMPORARY TABLE tmp.ticket_problems
(PRIMARY KEY (ticketFk))
ENGINE = MEMORY
SELECT
ticketFk,
MAX(p.isFreezed) AS isFreezed,
MAX(p.risk) AS risk,
MAX(p.hasHighRisk) AS hasHighRisk,
MAX(p.hasTicketRequest) AS hasTicketRequest,
MIN(p.isAvailable) AS isAvailable,
MAX(p.itemShortage) AS itemShortage,
MIN(p.isTaxDataChecked) AS isTaxDataChecked,
MAX(p.hasComponentLack) AS hasComponentLack,
0 AS totalProblems
FROM tmp.sale_problems p
GROUP BY ticketFk;
UPDATE tmp.ticket_problems tp
SET tp.totalProblems = (
(tp.isFreezed) +
IF(tp.risk, TRUE, FALSE) +
(tp.hasTicketRequest) +
(tp.isAvailable = 0) +
(tp.isTaxDataChecked = 0) +
(tp.hasComponentLack)
);
DROP TEMPORARY TABLE
tmp.sale_problems;
END;;$$
DELIMITER ;

View File

@ -0,0 +1,14 @@
CREATE TABLE `salix`.`defaultViewConfig`
(
tableCode VARCHAR(25) not null,
columns JSON not null
)
comment 'The default configuration of columns for views';
INSERT INTO `salix`.`defaultViewConfig` (tableCode, columns)
VALUES
('itemsIndex', '{"intrastat":false,"stemMultiplier":false,"landed":false}'),
('latestBuys', '{"intrastat":false,"description":false,"density":false,"isActive":false,"freightValue":false,"packageValue":false,"isIgnored":false,"price2":false,"minPrice":true,"ektFk":false,"weight":false,"id":true,"packing":true,"grouping":true,"quantity":true,"size":false,"name":true,"code":true,"origin":true,"family":true,"entryFk":true,"buyingValue":true,"comissionValue":false,"price3":true,"packageFk":true,"packingOut":true}'),
('ticketsMonitor', '{"id":false}');

File diff suppressed because one or more lines are too long

View File

@ -154,16 +154,16 @@ INSERT INTO `vn`.`shelving` (`code`, `parkingFk`, `isPrinted`, `priority`, `park
('GVC', '1', '0', '1', '0', '1106'),
('HEJ', '2', '0', '1', '0', '1106');
INSERT INTO `vn`.`accountingType`(`id`, `description`, `receiptDescription`,`code`)
INSERT INTO `vn`.`accountingType`(`id`, `description`, `receiptDescription`,`code`, `maxAmount`)
VALUES
(1, 'CC y Polizas de crédito', NULL, NULL),
(2, 'Cash', 'Cash', 'cash'),
(3, 'Credit card', 'Credit Card', 'creditCard'),
(4, 'Finalcial lines', NULL, NULL),
(5, 'Other products', NULL, NULL),
(6, 'Loans', NULL, NULL),
(7, 'Leasing', NULL, NULL),
(8, 'Compensations', 'Compensations', 'compensation');
(1, 'CC y Polizas de crédito', NULL, NULL, NULL),
(2, 'Cash', 'Cash', 'cash', 1000),
(3, 'Credit card', 'Credit Card', 'creditCard', NULL),
(4, 'Finalcial lines', NULL, NULL, NULL),
(5, 'Other products', NULL, NULL, NULL),
(6, 'Loans', NULL, NULL, NULL),
(7, 'Leasing', NULL, NULL, NULL),
(8, 'Compensations', 'Compensations', 'compensation', NULL);
INSERT INTO `vn`.`bank`(`id`, `bank`, `account`, `cash`, `entityFk`, `isActive`, `currencyFk`)
VALUES
@ -217,14 +217,14 @@ UPDATE `vn`.`agencyMode` SET `web` = 1, `reportMail` = 'no-reply@gothamcity.com'
UPDATE `vn`.`agencyMode` SET `code` = 'refund' WHERE `id` = 23;
INSERT INTO `vn`.`payMethod`(`id`,`code`, `name`, `graceDays`, `outstandingDebt`, `ibanRequired`)
INSERT INTO `vn`.`payMethod`(`id`,`code`, `name`, `graceDays`, `outstandingDebt`, `ibanRequiredForClients`, `ibanRequiredForSuppliers`)
VALUES
(1, NULL, 'PayMethod one', 0, 001, 0),
(2, NULL, 'PayMethod two', 10, 001, 0),
(3, 'compensation', 'PayMethod three', 0, 001, 0),
(4, NULL, 'PayMethod with IBAN', 0, 001, 1),
(5, NULL, 'PayMethod five', 10, 001, 0),
(8,'wireTransfer', 'WireTransfer', 5, 001, 1);
(1, NULL, 'PayMethod one', 0, 001, 0, 0),
(2, NULL, 'PayMethod two', 10, 001, 0, 0),
(3, 'compensation', 'PayMethod three', 0, 001, 0, 0),
(4, NULL, 'PayMethod with IBAN', 0, 001, 1, 0),
(5, NULL, 'PayMethod five', 10, 001, 0, 0),
(8,'wireTransfer', 'WireTransfer', 5, 001, 1, 1);
INSERT INTO `vn`.`payDem`(`id`, `payDem`)
VALUES
@ -485,15 +485,16 @@ INSERT INTO `vn`.`invoiceOutSerial` (`code`, `description`, `isTaxed`, `taxAreaF
VALUES
('A', 'Global nacional', 1, 'NATIONAL', 0),
('T', 'Española rapida', 1, 'NATIONAL', 0),
('V', 'Intracomunitaria global', 0, 'CEE', 1);
('V', 'Intracomunitaria global', 0, 'CEE', 1),
('M', 'Múltiple nacional', 1, 'NATIONAL', 0);
INSERT INTO `vn`.`invoiceOut`(`id`, `serial`, `amount`, `issued`,`clientFk`, `created`, `companyFk`, `dued`, `booked`, `bankFk`, `hasPdf`)
VALUES
(1, 'T', 1014.24, CURDATE(), 1101, CURDATE(), 442, CURDATE(), CURDATE(), 1, 1),
(2, 'T', 121.36, CURDATE(), 1102, CURDATE(), 442, CURDATE(), CURDATE(), 1, 1),
(3, 'T', 8.88, CURDATE(), 1103, CURDATE(), 442, CURDATE(), CURDATE(), 1, 1),
(4, 'T', 8.88, CURDATE(), 1103, CURDATE(), 442, CURDATE(), CURDATE(), 1, 1),
(5, 'A', 8.88, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1103, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 442, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 1);
(1, 'T', 1014.24, CURDATE(), 1101, CURDATE(), 442, CURDATE(), CURDATE(), 1, 0),
(2, 'T', 121.36, CURDATE(), 1102, CURDATE(), 442, CURDATE(), CURDATE(), 1, 0),
(3, 'T', 8.88, CURDATE(), 1103, CURDATE(), 442, CURDATE(), CURDATE(), 1, 0),
(4, 'T', 8.88, CURDATE(), 1103, CURDATE(), 442, CURDATE(), CURDATE(), 1, 0),
(5, 'A', 8.88, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1103, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 442, DATE_ADD(CURDATE(), INTERVAL -1 MONTH), DATE_ADD(CURDATE(), INTERVAL -1 MONTH), 1, 0);
UPDATE `vn`.`invoiceOut` SET ref = 'T1111111' WHERE id = 1;
UPDATE `vn`.`invoiceOut` SET ref = 'T2222222' WHERE id = 2;
@ -532,21 +533,21 @@ INSERT INTO `vn`.`invoiceOutExpence`(`id`, `invoiceOutFk`, `amount`, `expenceFk`
(6, 4, 8.07, 2000000000, CURDATE()),
(7, 5, 8.07, 2000000000, CURDATE());
INSERT INTO `vn`.`zone` (`id`, `name`, `hour`, `agencyModeFk`, `travelingDays`, `price`, `bonus`, `m3Max`)
INSERT INTO `vn`.`zone` (`id`, `name`, `hour`, `agencyModeFk`, `travelingDays`, `price`, `bonus`, `itemMaxSize`)
VALUES
(1, 'Zone pickup A', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 1, 0, 0, 0, 30.50),
(2, 'Zone pickup B', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 1, 0, 0, 0, 30.50),
(3, 'Zone 247 A', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 7, 1, 2, 0, 40.50),
(4, 'Zone 247 B', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 7, 1, 2, 0, 40.50),
(5, 'Zone expensive A', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 8, 1, 1000, 0, 50.50),
(6, 'Zone expensive B', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 8, 1, 1000, 0, 50.50),
(7, 'Zone refund', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 23, 0, 0, 0, 60.50),
(8, 'Zone others', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 10, 0, 0, 0, 60.50),
(9, 'Zone superMan', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 2, 0, 0, 0, NULL),
(10, 'Zone teleportation', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 3, 0, 0, 0, NULL),
(11, 'Zone pickup C', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 1, 0, 0, 0, NULL),
(12, 'Zone entanglement', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 4, 0, 0, 0, NULL),
(13, 'Zone quantum break', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 5, 0, 0, 0, NULL);
(1, 'Zone pickup A', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 1, 0, 0, 0, 100),
(2, 'Zone pickup B', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 1, 0, 0, 0, 100),
(3, 'Zone 247 A', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 7, 1, 2, 0, 100),
(4, 'Zone 247 B', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 7, 1, 2, 0, 100),
(5, 'Zone expensive A', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 8, 1, 1000, 0, 100),
(6, 'Zone expensive B', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 8, 1, 1000, 0, 100),
(7, 'Zone refund', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 23, 0, 0, 0, 100),
(8, 'Zone others', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 10, 0, 0, 0, 100),
(9, 'Zone superMan', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 2, 0, 0, 0, 100),
(10, 'Zone teleportation', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 3, 0, 0, 0, 100),
(11, 'Zone pickup C', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 1, 0, 0, 0, 100),
(12, 'Zone entanglement', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 4, 0, 0, 0, 100),
(13, 'Zone quantum break', CONCAT(CURRENT_DATE(), ' ', TIME('23:59')), 5, 0, 0, 0, 100);
INSERT INTO `vn`.`zoneWarehouse` (`id`, `zoneFk`, `warehouseFk`)
VALUES
@ -795,25 +796,25 @@ INSERT INTO `vn`.`itemFamily`(`code`, `description`)
('SER', 'Services'),
('VT', 'Sales');
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `isOnOffer`, `expenceFk`, `isBargain`, `comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`)
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `expenceFk`, `comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`)
VALUES
(1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '1', NULL, 0, 1, 'VT'),
(2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '2', NULL, 0, 2, 'VT'),
(3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, '3', NULL, 0, 5, 'VT'),
(4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 1, 4751000000, 0, NULL, 0, '4', NULL, 0, 3, 'VT'),
(5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, '5', NULL, 0, 3, 'VT'),
(6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '6', NULL, 0, 4, 'VT'),
(7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '7', NULL, 0, 4, 'VT'),
(8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '8', NULL, 0, 5, 'VT'),
(9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 0, 2000000000, 0, NULL, 0, '9', NULL, 0, 4, 'VT'),
(10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 0, 4751000000, 0, NULL, 0, '10', NULL, 0, 4, 'VT'),
(11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 1, 4751000000, 0, NULL, 0, '11', NULL, 0, 4, 'VT'),
(12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 1, 4751000000, 0, NULL, 0, '12', NULL, 0, 3, 'VT'),
(13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '13', NULL, 0, 2, 'VT'),
(14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '', NULL, 0, 4, 'VT'),
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '', NULL, 0, 0, 'EMB'),
(16, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 0, 4751000000, 0, NULL, 0, '', NULL, 0, 0, 'EMB'),
(71, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 1, 4751000000, 0, NULL, 0, '', NULL, 0, 0, 'VT');
(1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'VT'),
(2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '2', NULL, 0, 2, 'VT'),
(3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '3', NULL, 0, 5, 'VT'),
(4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 4751000000, NULL, 0, '4', NULL, 0, 3, 'VT'),
(5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '5', NULL, 0, 3, 'VT'),
(6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '6', NULL, 0, 4, 'VT'),
(7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '7', NULL, 0, 4, 'VT'),
(8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '8', NULL, 0, 5, 'VT'),
(9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '9', NULL, 0, 4, 'VT'),
(10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '10', NULL, 0, 4, 'VT'),
(11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 4751000000, NULL, 0, '11', NULL, 0, 4, 'VT'),
(12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '12', NULL, 0, 3, 'VT'),
(13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '13', NULL, 0, 2, 'VT'),
(14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 4, 'VT'),
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB'),
(16, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB'),
(71, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'VT');
-- Update the taxClass after insert of the items
UPDATE `vn`.`itemTaxCountry` SET `taxClassFk` = 2
@ -1284,11 +1285,11 @@ INSERT INTO `vn`.`supplierAddress`(`id`, `supplierFk`, `nickname`, `street`, `pr
(5, 442, 'GCR building', 'Bristol district', 1, '46000', 'Gotham', '111111111', '222222222'),
(6, 442, 'The Gotham Tonight building', 'Bristol district', 1, '46000', 'Gotham', '111111111', '222222222');
INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`,`isFarmer`,`commission`, `created`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `payDay`, `taxTypeSageFk`, `withholdingSageFk`, `transactionTypeSageFk`, `workerFk`, `supplierActivityFk`)
INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`,`isFarmer`,`commission`, `created`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `payDay`, `taxTypeSageFk`, `withholdingSageFk`, `transactionTypeSageFk`, `workerFk`, `supplierActivityFk`, `isPayMethodChecked`)
VALUES
(1, 'Plants SL', 'Plants nick', 4100000001, 1, '06089160W', 0, 0, CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, 4, 1, 1, 18, 'flowerPlants'),
(2, 'Farmer King', 'The farmer', 4000020002, 1, '87945234L', 1, 0, CURDATE(), 1, 'supplier address 2', 'SILLA', 2, 43022, 1, 2, 10, 93, 2, 8, 18, 'animals'),
(442, 'Verdnatura Levante SL', 'Verdnatura', 5115000442, 1, '06815934E', 0, 0, CURDATE(), 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'flowerPlants');
(1, 'Plants SL', 'Plants nick', 4100000001, 1, '06089160W', 0, 0, CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, 4, 1, 1, 18, 'flowerPlants', 1),
(2, 'Farmer King', 'The farmer', 4000020002, 1, '87945234L', 1, 0, CURDATE(), 1, 'supplier address 2', 'SILLA', 2, 43022, 1, 2, 10, 93, 2, 8, 18, 'animals', 1),
(442, 'Verdnatura Levante SL', 'Verdnatura', 5115000442, 1, '06815934E', 0, 0, CURDATE(), 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'flowerPlants', 1);
INSERT INTO `vn`.`supplierContact`(`id`, `supplierFk`, `phone`, `mobile`, `email`, `observation`, `name`)
VALUES
@ -1769,12 +1770,12 @@ INSERT INTO `vn`.`userConfig` (`userFk`, `warehouseFk`, `companyFk`)
(9, 1, 442),
(18, 3, 567);
INSERT INTO `vn`.`receipt`(`id`, `invoiceFk`, `amountPaid`, `amountUnpaid`, `payed`, `workerFk`, `bankFk`, `clientFk`, `created`, `companyFk`, `isConciliate`)
INSERT INTO `vn`.`receipt`(`id`, `invoiceFk`, `amountPaid`, `payed`, `workerFk`, `bankFk`, `clientFk`, `created`, `companyFk`, `isConciliate`)
VALUES
(1, 'Cobro web', 100.50, 0.00, CURDATE(), 9, 1, 1101, CURDATE(), 442, 1),
(2, 'Cobro web', 200.50, 0.00, DATE_ADD(CURDATE(), INTERVAL -5 DAY), 9, 1, 1101, DATE_ADD(CURDATE(), INTERVAL -5 DAY), 442, 1),
(3, 'Cobro en efectivo', 300.00, 100.00, DATE_ADD(CURDATE(), INTERVAL -10 DAY), 9, 1, 1102, DATE_ADD(CURDATE(), INTERVAL -10 DAY), 442, 0),
(4, 'Cobro en efectivo', 400.00, -50.00, DATE_ADD(CURDATE(), INTERVAL -15 DAY), 9, 1, 1103, DATE_ADD(CURDATE(), INTERVAL -15 DAY), 442, 0);
(1, 'Cobro web', 100.50, CURDATE(), 9, 1, 1101, CURDATE(), 442, 1),
(2, 'Cobro web', 200.50, DATE_ADD(CURDATE(), INTERVAL -5 DAY), 9, 1, 1101, DATE_ADD(CURDATE(), INTERVAL -5 DAY), 442, 1),
(3, 'Cobro en efectivo', 300.00, DATE_ADD(CURDATE(), INTERVAL -10 DAY), 9, 1, 1102, DATE_ADD(CURDATE(), INTERVAL -10 DAY), 442, 0),
(4, 'Cobro en efectivo', 400.00, DATE_ADD(CURDATE(), INTERVAL -15 DAY), 9, 1, 1103, DATE_ADD(CURDATE(), INTERVAL -15 DAY), 442, 0);
INSERT INTO `vn`.`workerTeam`(`id`, `team`, `workerFk`)
VALUES
@ -2232,8 +2233,8 @@ INSERT IGNORE INTO `vn`.`greugeConfig` (`id`, `freightPickUpPrice`) VALUES ('1',
INSERT INTO `vn`.`temperature`(`code`, `name`, `description`)
VALUES
('WARM', 'Warm', 'Warm'),
('COOL', 'Cool', 'Cool');
('warm', 'Warm', 'Warm'),
('cool', 'Cool', 'Cool');
INSERT INTO `vn`.`thermograph`(`id`, `model`)
VALUES

File diff suppressed because it is too large Load Diff

View File

@ -29,9 +29,9 @@ services:
- source: print_local
target: /etc/salix/print.local.json
volumes:
- /mnt/storage/pdfs:/var/lib/salix/pdfs
- /mnt/storage/dms:/var/lib/salix/dms
- /mnt/storage/image:/var/lib/salix/image
- /mnt/appdata/pdfs:/var/lib/salix/pdfs
- /mnt/appdata/dms:/var/lib/salix/dms
- /mnt/appdata/image:/var/lib/salix/image
deploy:
replicas: ${BACK_REPLICAS:?}
placement:

View File

@ -94,6 +94,19 @@ let actions = {
login: async function(userName) {
await this.doLogin(userName);
await this.waitForState('home');
await this.addStyleTag({
content: `
*,
*::after,
*::before {
transition-delay: 0s !important;
transition-duration: 0s !important;
animation-delay: -0.0001s !important;
animation-duration: 0s !important;
animation-play-state: paused !important;
caret-color: transparent !important;
}`
});
},
selectModule: async function(moduleName) {
@ -108,7 +121,7 @@ let actions = {
},
getState: async function() {
return await this.evaluate(() => {
return this.evaluate(() => {
let $state = angular.element(document.body).injector().get('$state');
return $state.current.name;
});
@ -194,7 +207,7 @@ let actions = {
},
getProperty: async function(selector, property) {
return await this.evaluate((selector, property) => {
return this.evaluate((selector, property) => {
return document.querySelector(selector)[property].replace(/\s+/g, ' ').trim();
}, selector, property);
},
@ -202,15 +215,16 @@ let actions = {
getClassName: async function(selector) {
const element = await this.$(selector);
const handle = await element.getProperty('className');
return await handle.jsonValue();
return handle.jsonValue();
},
waitPropertyLength: async function(selector, property, minLength) {
await this.waitForFunction((selector, property, minLength) => {
const element = document.querySelector(selector);
return element && element[property] != null && element[property] !== '' && element[property].length >= minLength;
const isValidElement = element && element[property] != null && element[property] !== '';
return isValidElement && element[property].length >= minLength;
}, {}, selector, property, minLength);
return await this.getProperty(selector, property);
return this.getProperty(selector, property);
},
expectPropertyValue: async function(selector, property, value) {
@ -219,7 +233,7 @@ let actions = {
builtSelector = await this.selectorFormater(selector);
try {
return await this.waitForFunction((selector, property, value) => {
return this.waitForFunction((selector, property, value) => {
const element = document.querySelector(selector);
return element[property] == value;
}, {}, builtSelector, property, value);
@ -239,7 +253,7 @@ let actions = {
return element && element[property] != null && element[property] !== '';
}, {}, builtSelector, property);
return await this.getProperty(builtSelector, property);
return this.getProperty(builtSelector, property);
} catch (error) {
throw new Error(`couldn't get property: ${property} of ${builtSelector}, ${error}`);
}
@ -261,7 +275,7 @@ let actions = {
await this.waitForSelector(selector);
await this.waitForFunction(checkVisibility, {}, selector);
return await this.click(selector);
return this.click(selector);
},
writeOnEditableTD: async function(selector, text) {
@ -274,7 +288,7 @@ let actions = {
focusElement: async function(selector) {
await this.waitForSelector(selector);
return await this.evaluate(selector => {
return this.evaluate(selector => {
let element = document.querySelector(selector);
element.focus();
}, selector);
@ -282,19 +296,19 @@ let actions = {
isVisible: async function(selector) {
await this.waitForSelector(selector);
return await this.evaluate(checkVisibility, selector);
return this.evaluate(checkVisibility, selector);
},
waitImgLoad: async function(selector) {
await this.waitForSelector(selector);
return await this.waitForFunction(selector => {
return this.waitForFunction(selector => {
const imageReady = document.querySelector(selector).complete;
return imageReady;
}, {}, selector);
},
countElement: async function(selector) {
return await this.evaluate(selector => {
return this.evaluate(selector => {
return document.querySelectorAll(selector).length;
}, selector);
},
@ -312,7 +326,7 @@ let actions = {
waitForClassNotPresent: async function(selector, className) {
await this.waitForSelector(selector);
return await this.waitForFunction((selector, className) => {
return this.waitForFunction((selector, className) => {
if (!document.querySelector(selector).classList.contains(className))
return true;
}, {}, selector, className);
@ -320,55 +334,39 @@ let actions = {
waitForClassPresent: async function(selector, className) {
await this.waitForSelector(selector);
return await this.waitForFunction((elementSelector, targetClass) => {
return this.waitForFunction((elementSelector, targetClass) => {
if (document.querySelector(elementSelector).classList.contains(targetClass))
return true;
}, {}, selector, className);
},
waitForTextInElement: async function(selector, text) {
const expectedText = text.toLowerCase();
return new Promise((resolve, reject) => {
let attempts = 0;
const interval = setInterval(async() => {
const currentText = await this.evaluate(selector => {
return document.querySelector(selector).innerText.toLowerCase();
}, selector);
if (currentText === expectedText || attempts === 40) {
clearInterval(interval);
resolve(currentText);
}
attempts += 1;
}, 100);
}).then(result => {
return expect(result).toContain(expectedText);
});
await this.waitForFunction((selector, text) => {
if (document.querySelector(selector)) {
const innerText = document.querySelector(selector).innerText.toLowerCase();
const expectedText = text.toLowerCase();
if (innerText.includes(expectedText))
return innerText;
}
}, {}, selector, text);
},
waitForTextInField: async function(selector, text) {
let builtSelector = await this.selectorFormater(selector);
await this.waitForSelector(builtSelector);
const expectedText = text.toLowerCase();
return new Promise((resolve, reject) => {
let attempts = 0;
const interval = setInterval(async() => {
const currentText = await this.evaluate(selector => {
return document.querySelector(selector).value.toLowerCase();
}, builtSelector);
const builtSelector = await this.selectorFormater(selector);
const expectedValue = text.toLowerCase();
if (currentText === expectedText || attempts === 40) {
clearInterval(interval);
resolve(currentText);
try {
await this.waitForFunction((selector, text) => {
const element = document.querySelector(selector);
if (element) {
const value = element.value.toLowerCase();
if (value.includes(text))
return true;
}
attempts += 1;
}, 100);
}).then(result => {
if (result === '')
return expect(result).toEqual(expectedText);
return expect(result).toContain(expectedText);
});
}, {}, builtSelector, expectedValue);
} catch (error) {
throw new Error(`${text} wasn't the value of ${builtSelector}, ${error}`);
}
},
selectorFormater: function(selector) {
@ -387,13 +385,13 @@ let actions = {
const innerText = document.querySelector(selector).innerText;
return innerText != null && innerText != '';
}, {}, selector);
return await this.evaluate(selector => {
return this.evaluate(selector => {
return document.querySelector(selector).innerText;
}, selector);
},
waitForEmptyInnerText: async function(selector) {
return await this.waitFunction(selector => {
return this.waitFunction(selector => {
return document.querySelector(selector).innerText == '';
}, selector);
},
@ -521,7 +519,7 @@ let actions = {
checkboxState: async function(selector) {
await this.waitForSelector(selector);
return await this.evaluate(selector => {
return this.evaluate(selector => {
let checkbox = document.querySelector(selector);
switch (checkbox.$ctrl.field) {
case null:
@ -536,14 +534,14 @@ let actions = {
isDisabled: async function(selector) {
await this.waitForSelector(selector);
return await this.evaluate(selector => {
return this.evaluate(selector => {
let element = document.querySelector(selector);
return element.$ctrl.disabled;
}, selector);
},
waitForStylePresent: async function(selector, property, value) {
return await this.waitForFunction((selector, property, value) => {
return this.waitForFunction((selector, property, value) => {
const element = document.querySelector(selector);
return element.style[property] == value;
}, {}, selector, property, value);
@ -631,7 +629,7 @@ export function extendPage(page) {
for (let name in actions) {
page[name] = async(...args) => {
try {
return await actions[name].apply(page, args);
return actions[name].apply(page, args);
} catch (err) {
let stringArgs = args
.map(i => typeof i == 'function' ? 'Function' : i)

View File

@ -5,11 +5,17 @@ import {url as defaultURL} from './config';
export async function getBrowser() {
const args = [
`--no-sandbox`,
`--window-size=${ 1920 },${ 1080 }`
`--window-size=${ 1920 },${ 1080 }`,
// '--disable-gpu'
// '--no-sandbox',
// '--single-process',
// '--no-zygote',
// '--disable-dev-shm-usage'
// '--full-memory-crash-report',
// '--unlimited-storage'
];
let env = process.env;
const env = process.env;
if (env.E2E_DEBUG) {
args.push('--auto-open-devtools-for-tabs');
@ -21,7 +27,10 @@ export async function getBrowser() {
args,
defaultViewport: null,
headless: headless,
slowMo: 0, // slow down by ms
slowMo: 5, // slow down by ms
// ignoreDefaultArgs: ['--disable-extensions'],
// executablePath: '/usr/bin/google-chrome-stable',
// executablePath: '/usr/bin/firefox-developer-edition',
});
let page = (await browser.pages())[0];
@ -38,7 +47,8 @@ export async function getBrowser() {
});
});
page = extendPage(page);
page.setDefaultTimeout(4000);
page.setDefaultTimeout(5000);
await page.goto(defaultURL, {waitUntil: 'load'});
return {page, close: browser.close.bind(browser)};
}

View File

@ -1,3 +1,4 @@
/* eslint max-len: ["error", { "ignoreStrings": true }]*/
export default {
globalItems: {
@ -118,6 +119,7 @@ export default {
name: 'vn-client-create vn-textfield[ng-model="$ctrl.client.name"]',
taxNumber: 'vn-client-create vn-textfield[ng-model="$ctrl.client.fi"]',
socialName: 'vn-client-create vn-textfield[ng-model="$ctrl.client.socialName"]',
businessType: 'vn-client-create vn-autocomplete[ng-model="$ctrl.client.businessTypeFk"]',
street: 'vn-client-create vn-textfield[ng-model="$ctrl.client.street"]',
addPostCode: 'vn-client-create vn-datalist[ng-model="$ctrl.client.postcode"] vn-icon-button[icon="add_circle"]',
addProvince: 'vn-autocomplete[ng-model="$ctrl.location.provinceFk"] vn-icon-button[icon="add_circle"]',
@ -273,6 +275,7 @@ export default {
deliveredAmount: '.vn-dialog vn-input-number[ng-model="$ctrl.deliveredAmount"]',
refundAmount: '.vn-dialog vn-input-number[ng-model="$ctrl.amountToReturn"]',
saveButton: '.vn-dialog.shown [response="accept"]',
anyBalanceLine: 'vn-client-balance-index vn-tbody > vn-tr',
firstLineBalance: 'vn-client-balance-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(8)',
firstLineReference: 'vn-client-balance-index vn-tbody > vn-tr:nth-child(1) > vn-td-editable',
firstLineReferenceInput: 'vn-client-balance-index vn-tbody > vn-tr:nth-child(1) > vn-td-editable > div > field > vn-textfield'
@ -310,27 +313,26 @@ export default {
},
itemsIndex: {
createItemButton: `vn-float-button`,
firstSearchResult: 'vn-item-index a:nth-child(1)',
searchResult: 'vn-item-index a.vn-tr',
firstResultPreviewButton: 'vn-item-index vn-tbody > :nth-child(1) .buttons > [icon="preview"]',
firstSearchResult: 'vn-item-index tbody tr:nth-child(1)',
searchResult: 'vn-item-index tbody tr:not(.empty-rows)',
firstResultPreviewButton: 'vn-item-index tbody > :nth-child(1) .buttons > [icon="preview"]',
searchResultCloneButton: 'vn-item-index .buttons > [icon="icon-clone"]',
acceptClonationAlertButton: '.vn-confirm.shown [response="accept"]',
closeItemSummaryPreview: '.vn-popup.shown',
fieldsToShowButton: 'vn-item-index vn-table > div > div > vn-icon-button[icon="more_vert"]',
fieldsToShowForm: '.vn-popover.shown .content',
firstItemImage: 'vn-item-index vn-tbody > a:nth-child(1) > vn-td:nth-child(1) > img',
firstItemImageTd: 'vn-item-index vn-table a:nth-child(1) vn-td:nth-child(1)',
firstItemId: 'vn-item-index vn-tbody > a:nth-child(1) > vn-td:nth-child(2)',
idCheckbox: '.vn-popover.shown vn-horizontal:nth-child(1) > vn-check',
stemsCheckbox: '.vn-popover.shown vn-horizontal:nth-child(2) > vn-check',
sizeCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check',
typeCheckbox: '.vn-popover.shown vn-horizontal:nth-child(5) > vn-check',
categoryCheckbox: '.vn-popover.shown vn-horizontal:nth-child(6) > vn-check',
intrastadCheckbox: '.vn-popover.shown vn-horizontal:nth-child(7) > vn-check',
originCheckbox: '.vn-popover.shown vn-horizontal:nth-child(8) > vn-check',
buyerCheckbox: '.vn-popover.shown vn-horizontal:nth-child(9) > vn-check',
destinyCheckbox: '.vn-popover.shown vn-horizontal:nth-child(10) > vn-check',
taxClassCheckbox: '.vn-popover.shown vn-horizontal:nth-child(11) > vn-check',
shownColumns: 'vn-item-index vn-button[id="shownColumns"]',
shownColumnsList: '.vn-popover.shown .content',
firstItemImage: 'vn-item-index tbody > tr:nth-child(1) > td:nth-child(1) > img',
firstItemImageTd: 'vn-item-index smart-table tr:nth-child(1) td:nth-child(1)',
firstItemId: 'vn-item-index tbody > tr:nth-child(1) > td:nth-child(2)',
idCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Identifier"]',
stemsCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Stems"]',
sizeCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Size"]',
typeCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Type"]',
categoryCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Category"]',
intrastadCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Intrastat"]',
originCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Origin"]',
buyerCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Buyer"]',
densityCheckbox: '.vn-popover.shown vn-horizontal:nth-child(3) > vn-check[label="Density"]',
saveFieldsButton: '.vn-popover.shown vn-button[label="Save"] > button'
},
itemFixedPrice: {
@ -470,6 +472,7 @@ export default {
firstTicketCheckbox: 'vn-ticket-index vn-tbody > a:nth-child(1) > vn-td:nth-child(1) > vn-check',
secondTicketCheckbox: 'vn-ticket-index vn-tbody > a:nth-child(2) > vn-td:nth-child(1) > vn-check',
thirdTicketCheckbox: 'vn-ticket-index vn-tbody > a:nth-child(3) > vn-td:nth-child(1) > vn-check',
fifthTicketCheckbox: 'vn-ticket-index vn-tbody > a:nth-child(5) > vn-td:nth-child(1) > vn-check',
sixthTicketCheckbox: 'vn-ticket-index vn-tbody > a:nth-child(6) > vn-td:nth-child(1) > vn-check',
payoutButton: 'vn-ticket-index vn-button[icon="icon-recovery"]',
payoutCompany: '.vn-dialog vn-autocomplete[ng-model="$ctrl.companyFk"]',
@ -544,7 +547,7 @@ export default {
},
ticketSales: {
setOk: 'vn-ticket-sale vn-tool-bar > vn-button[label="Ok"] > button',
saleLine: 'vn-table div > vn-tbody > vn-tr',
saleLine: 'vn-table div > vn-tbody > vn-tr vn-check',
saleDescriptorPopover: '.vn-popover.shown vn-item-descriptor',
saleDescriptorPopoverSummaryButton: '.vn-popover.shown vn-item-descriptor a[ui-sref="item.card.summary({id: $ctrl.descriptor.id})"]',
newItemButton: 'vn-ticket-sale vn-card vn-icon-button[icon="add_circle"]',
@ -1039,7 +1042,7 @@ export default {
zoneBasicData: {
name: 'vn-zone-basic-data vn-textfield[ng-model="$ctrl.zone.name"]',
agency: 'vn-zone-basic-data vn-autocomplete[ng-model="$ctrl.zone.agencyModeFk"]',
maxVolume: 'vn-zone-basic-data vn-input-number[ng-model="$ctrl.zone.m3Max"]',
maxVolume: 'vn-zone-basic-data vn-input-number[ng-model="$ctrl.zone.itemMaxSize"]',
travelingDays: 'vn-zone-basic-data vn-input-number[ng-model="$ctrl.zone.travelingDays"]',
closing: 'vn-zone-basic-data vn-input-time[ng-model="$ctrl.zone.hour"]',
price: 'vn-zone-basic-data vn-input-number[ng-model="$ctrl.zone.price"]',
@ -1083,7 +1086,7 @@ export default {
allBuyCheckbox: 'vn-entry-buy-index thead vn-check',
firstBuyCheckbox: 'vn-entry-buy-index tbody:nth-child(2) vn-check',
deleteBuysButton: 'vn-entry-buy-index vn-button[icon="delete"]',
addBuyButton: 'vn-entry-buy-index vn-icon[icon="add_circle"]',
addBuyButton: 'vn-entry-buy-index vn-icon[icon="add"]',
secondBuyPackingPrice: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.price3"]',
secondBuyGroupingPrice: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.price2"]',
secondBuyPrice: 'vn-entry-buy-index tbody:nth-child(3) > tr:nth-child(1) vn-input-number[ng-model="buy.buyingValue"]',
@ -1105,9 +1108,9 @@ export default {
importBuysButton: 'vn-entry-buy-import button[type="submit"]'
},
entryLatestBuys: {
firstBuy: 'vn-entry-latest-buys vn-tbody > a:nth-child(1)',
allBuysCheckBox: 'vn-entry-latest-buys vn-thead vn-check',
secondBuyCheckBox: 'vn-entry-latest-buys a:nth-child(2) vn-check[ng-model="buy.checked"]',
firstBuy: 'vn-entry-latest-buys tbody > tr:nth-child(1)',
allBuysCheckBox: 'vn-entry-latest-buys thead vn-check',
secondBuyCheckBox: 'vn-entry-latest-buys tbody tr:nth-child(2) vn-check[ng-model="buy.$checked"]',
editBuysButton: 'vn-entry-latest-buys vn-button[icon="edit"]',
fieldAutocomplete: 'vn-autocomplete[ng-model="$ctrl.editedColumn.field"]',
newValueInput: 'vn-textfield[ng-model="$ctrl.editedColumn.newValue"]',
@ -1144,6 +1147,7 @@ export default {
alias: 'vn-supplier-basic-data vn-textfield[ng-model="$ctrl.supplier.nickname"]',
isSerious: 'vn-supplier-basic-data vn-check[ng-model="$ctrl.supplier.isSerious"]',
isActive: 'vn-supplier-basic-data vn-check[ng-model="$ctrl.supplier.isActive"]',
isPayMethodChecked: 'vn-supplier-basic-data vn-check[ng-model="$ctrl.supplier.isPayMethodChecked"]',
notes: 'vn-supplier-basic-data vn-textarea[ng-model="$ctrl.supplier.note"]',
saveButton: 'vn-supplier-basic-data button[type="submit"]',
},

View File

@ -19,7 +19,9 @@ describe('Login path', async() => {
const message = await page.waitForSnackbar();
const state = await page.getState();
expect(message.text).toContain('Invalid login, remember that distinction is made between uppercase and lowercase');
const errorMessage = 'Invalid login, remember that distinction is made between uppercase and lowercase';
expect(message.text).toContain(errorMessage);
expect(state).toBe('login');
});
@ -28,7 +30,9 @@ describe('Login path', async() => {
const message = await page.waitForSnackbar();
const state = await page.getState();
expect(message.text).toContain('Invalid login, remember that distinction is made between uppercase and lowercase');
const errorMessage = 'Invalid login, remember that distinction is made between uppercase and lowercase';
expect(message.text).toContain(errorMessage);
expect(state).toBe('login');
});

View File

@ -27,16 +27,19 @@ describe('Client create path', () => {
await page.waitForState('client.create');
});
it('should receive an error when clicking the create button having name and Business name fields empty', async() => {
await page.write(selectors.createClientView.taxNumber, '74451390E');
await page.write(selectors.createClientView.userName, 'CaptainMarvel');
await page.write(selectors.createClientView.email, 'CarolDanvers@verdnatura.es');
await page.autocompleteSearch(selectors.createClientView.salesPerson, 'salesPerson');
await page.waitToClick(selectors.createClientView.createButton);
const message = await page.waitForSnackbar();
it('should receive an error when clicking the create button having name and Business name fields empty',
async() => {
await page.write(selectors.createClientView.taxNumber, '74451390E');
await page.write(selectors.createClientView.userName, 'CaptainMarvel');
await page.write(selectors.createClientView.email, 'CarolDanvers@verdnatura.es');
await page.autocompleteSearch(selectors.createClientView.salesPerson, 'salesPerson');
await page.autocompleteSearch(selectors.createClientView.businessType, 'florist');
await page.waitToClick(selectors.createClientView.createButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Some fields are invalid');
});
expect(message.text).toContain('Some fields are invalid');
}
);
it(`should create a new province`, async() => {
await page.waitToClick(selectors.createClientView.addPostCode);
@ -80,9 +83,22 @@ describe('Client create path', () => {
expect(message.text).toContain('Some fields are invalid');
});
/* Tarea #3370
it(`should attempt to create a new user with all it's data but wrong business type`, async() => {
await page.clearInput(selectors.createClientView.email);
await page.write(selectors.createClientView.email, 'CarolDanvers@verdnatura.es');
await page.clearInput(selectors.createClientView.businessType);
await page.waitToClick(selectors.createClientView.createButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Some fields are invalid');
});*/
it(`should attempt to create a new user with all it's data but wrong postal code`, async() => {
await page.clearInput(selectors.createClientView.email);
await page.write(selectors.createClientView.email, 'CarolDanvers@verdnatura.es');
await page.autocompleteSearch(selectors.createClientView.businessType, 'florist');
await page.clearInput(selectors.createClientView.postcode);
await page.write(selectors.createClientView.postcode, '479999');
await page.waitToClick(selectors.createClientView.createButton);

View File

@ -112,7 +112,7 @@ describe('Client Edit fiscalData path', () => {
expect(message.text).toContain('Cannot check Equalization Tax in this NIF/CIF');
});
it('should finally edit the fixcal data correctly as VIES isnt checked and fiscal id is valid for EQtax', async() => {
it('should edit the fiscal data correctly as VIES isnt checked and fiscal id is valid for EQtax', async() => {
await page.clearInput(selectors.clientFiscalData.fiscalId);
await page.write(selectors.clientFiscalData.fiscalId, '94980061C');
await page.waitToClick(selectors.clientFiscalData.saveButton);

View File

@ -38,10 +38,13 @@ describe('Client Edit billing data path', () => {
await page.autocompleteSearch(selectors.clientBillingData.newBankEntityCountry, 'España');
await page.write(selectors.clientBillingData.newBankEntityCode, '9999');
await page.waitToClick(selectors.clientBillingData.acceptBankEntityButton);
const message = await page.waitForSnackbar();
await page.waitForTextInField(selectors.clientBillingData.swiftBic, 'Gotham City Bank');
const newcode = await page.waitToGetProperty(selectors.clientBillingData.swiftBic, 'value');
expect(newcode).toEqual('GTHMCT Gotham City Bank');
expect(message.text).toContain('Data saved!');
});
it(`should confirm the IBAN pay method was sucessfully saved`, async() => {

View File

@ -105,7 +105,23 @@ describe('Client balance path', () => {
expect(result).toContain('-€100.00');
});
it('should create a new payment and check the cash exceeded the maximum', async() => {
const amountPaid = '1001';
await page.closePopup();
await page.waitToClick(selectors.clientBalance.newPaymentButton);
await page.autocompleteSearch(selectors.clientBalance.newPaymentBank, 'Cash');
await page.write(selectors.clientBalance.newPaymentAmount, amountPaid);
await page.clearInput(selectors.clientBalance.newDescription);
await page.write(selectors.clientBalance.newDescription, 'Payment');
await page.waitToClick(selectors.clientBalance.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Amount exceeded');
});
it('should create a new payment that sets the balance back to the original negative value', async() => {
await page.closePopup();
await page.waitToClick(selectors.clientBalance.newPaymentButton);
await page.autocompleteSearch(selectors.clientBalance.newPaymentBank, 'Pay on receipt');
await page.overwrite(selectors.clientBalance.newPaymentAmount, '-150');

View File

@ -16,13 +16,13 @@ describe('Item summary path', () => {
it('should search for an item', async() => {
await page.doSearch('Ranged weapon');
const nResults = await page.countElement(selectors.itemsIndex.searchResult);
const resultsCount = await page.countElement(selectors.itemsIndex.searchResult);
await page.waitForTextInElement(selectors.itemsIndex.searchResult, 'Ranged weapon');
await page.waitToClick(selectors.itemsIndex.firstResultPreviewButton);
const isVisible = await page.isVisible(selectors.itemSummary.basicData);
expect(nResults).toBe(3);
expect(resultsCount).toBe(3);
expect(isVisible).toBeTruthy();
});
@ -61,12 +61,12 @@ describe('Item summary path', () => {
it('should search for other item', async() => {
await page.doSearch('Melee Reinforced');
const nResults = await page.countElement(selectors.itemsIndex.searchResult);
const resultsCount = await page.countElement(selectors.itemsIndex.searchResult);
await page.waitToClick(selectors.itemsIndex.firstResultPreviewButton);
await page.waitForSelector(selectors.itemSummary.basicData, {visible: true});
expect(nResults).toBe(2);
expect(resultsCount).toBe(2);
});
it(`should now check the item summary preview shows fields from basic data`, async() => {

View File

@ -0,0 +1,71 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Item Create', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'item');
});
afterAll(async() => {
await browser.close();
});
it(`should search for the item Infinity Gauntlet to confirm it isn't created yet`, async() => {
await page.doSearch('Infinity Gauntlet');
const resultsCount = await page.countElement(selectors.itemsIndex.searchResult);
expect(resultsCount).toEqual(0);
});
it('should access to the create item view by clicking the create floating button', async() => {
await page.waitToClick(selectors.itemsIndex.createItemButton);
await page.waitForState('item.create');
});
it('should return to the item index by clickig the cancel button', async() => {
await page.waitToClick(selectors.itemCreateView.cancelButton);
await page.waitForState('item.index');
});
it('should now access to the create item view by clicking the create floating button', async() => {
await page.waitToClick(selectors.itemsIndex.createItemButton);
await page.waitForState('item.create');
});
it('should create the Infinity Gauntlet item', async() => {
await page.write(selectors.itemCreateView.temporalName, 'Infinity Gauntlet');
await page.autocompleteSearch(selectors.itemCreateView.type, 'Crisantemo');
await page.autocompleteSearch(selectors.itemCreateView.intrastat, 'Coral y materiales similares');
await page.autocompleteSearch(selectors.itemCreateView.origin, 'Holand');
await page.waitToClick(selectors.itemCreateView.createButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should confirm Infinity Gauntlet item was created', async() => {
let result = await page
.waitToGetProperty(selectors.itemBasicData.name, 'value');
expect(result).toEqual('Infinity Gauntlet');
result = await page
.waitToGetProperty(selectors.itemBasicData.type, 'value');
expect(result).toEqual('Crisantemo');
result = await page
.waitToGetProperty(selectors.itemBasicData.intrastat, 'value');
expect(result).toEqual('5080000 Coral y materiales similares');
result = await page
.waitToGetProperty(selectors.itemBasicData.origin, 'value');
expect(result).toEqual('Holand');
});
});

View File

@ -1,105 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Item Create/Clone path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('buyer', 'item');
});
afterAll(async() => {
await browser.close();
});
describe('create', () => {
it(`should search for the item Infinity Gauntlet to confirm it isn't created yet`, async() => {
await page.doSearch('Infinity Gauntlet');
const nResults = await page.countElement(selectors.itemsIndex.searchResult);
expect(nResults).toEqual(0);
});
it('should access to the create item view by clicking the create floating button', async() => {
await page.waitToClick(selectors.itemsIndex.createItemButton);
await page.waitForState('item.create');
});
it('should return to the item index by clickig the cancel button', async() => {
await page.waitToClick(selectors.itemCreateView.cancelButton);
await page.waitForState('item.index');
});
it('should now access to the create item view by clicking the create floating button', async() => {
await page.waitToClick(selectors.itemsIndex.createItemButton);
await page.waitForState('item.create');
});
it('should create the Infinity Gauntlet item', async() => {
await page.write(selectors.itemCreateView.temporalName, 'Infinity Gauntlet');
await page.autocompleteSearch(selectors.itemCreateView.type, 'Crisantemo');
await page.autocompleteSearch(selectors.itemCreateView.intrastat, 'Coral y materiales similares');
await page.autocompleteSearch(selectors.itemCreateView.origin, 'Holand');
await page.waitToClick(selectors.itemCreateView.createButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should confirm Infinity Gauntlet item was created', async() => {
let result = await page
.waitToGetProperty(selectors.itemBasicData.name, 'value');
expect(result).toEqual('Infinity Gauntlet');
result = await page
.waitToGetProperty(selectors.itemBasicData.type, 'value');
expect(result).toEqual('Crisantemo');
result = await page
.waitToGetProperty(selectors.itemBasicData.intrastat, 'value');
expect(result).toEqual('5080000 Coral y materiales similares');
result = await page
.waitToGetProperty(selectors.itemBasicData.origin, 'value');
expect(result).toEqual('Holand');
});
});
// Issue #2201
// When there is just one result you're redirected automatically to it, so
// it's not possible to use the clone option.
xdescribe('clone', () => {
it('should return to the items index by clicking the return to items button', async() => {
await page.waitToClick(selectors.itemBasicData.goToItemIndexButton);
await page.waitForSelector(selectors.itemsIndex.createItemButton);
await page.waitForState('item.index');
});
it(`should search for the item Infinity Gauntlet`, async() => {
await page.doSearch('Infinity Gauntlet');
const nResults = await page.countElement(selectors.itemsIndex.searchResult);
expect(nResults).toEqual(1);
});
it(`should clone the Infinity Gauntlet`, async() => {
await page.waitForTextInElement(selectors.itemsIndex.searchResult, 'Infinity Gauntlet');
await page.waitToClick(selectors.itemsIndex.searchResultCloneButton);
await page.waitToClick(selectors.itemsIndex.acceptClonationAlertButton);
await page.waitForState('item.tags');
});
it('should search for the item Infinity Gauntlet and find two', async() => {
await page.doSearch('Infinity Gauntlet');
const nResults = await page.countElement(selectors.itemsIndex.searchResult);
expect(nResults).toEqual(2);
});
});
});

View File

@ -16,8 +16,8 @@ describe('Item index path', () => {
});
it('should click on the fields to show button to open the list of columns to show', async() => {
await page.waitToClick(selectors.itemsIndex.fieldsToShowButton);
const visible = await page.isVisible(selectors.itemsIndex.fieldsToShowForm);
await page.waitToClick(selectors.itemsIndex.shownColumns);
const visible = await page.isVisible(selectors.itemsIndex.shownColumnsList);
expect(visible).toBeTruthy();
});
@ -31,7 +31,7 @@ describe('Item index path', () => {
await page.waitToClick(selectors.itemsIndex.intrastadCheckbox);
await page.waitToClick(selectors.itemsIndex.originCheckbox);
await page.waitToClick(selectors.itemsIndex.buyerCheckbox);
await page.waitToClick(selectors.itemsIndex.destinyCheckbox);
await page.waitToClick(selectors.itemsIndex.densityCheckbox);
await page.waitToClick(selectors.itemsIndex.saveFieldsButton);
const message = await page.waitForSnackbar();
@ -39,6 +39,7 @@ describe('Item index path', () => {
});
it('should navigate forth and back to see the images column is still visible', async() => {
await page.closePopup();
await page.waitToClick(selectors.itemsIndex.firstSearchResult);
await page.waitToClick(selectors.itemDescriptor.goBackToModuleIndexButton);
await page.waitToClick(selectors.globalItems.searchButton);
@ -54,7 +55,7 @@ describe('Item index path', () => {
});
it('should mark all unchecked boxes to leave the index as it was', async() => {
await page.waitToClick(selectors.itemsIndex.fieldsToShowButton);
await page.waitToClick(selectors.itemsIndex.shownColumns);
await page.waitToClick(selectors.itemsIndex.idCheckbox);
await page.waitToClick(selectors.itemsIndex.stemsCheckbox);
await page.waitToClick(selectors.itemsIndex.sizeCheckbox);
@ -63,7 +64,7 @@ describe('Item index path', () => {
await page.waitToClick(selectors.itemsIndex.intrastadCheckbox);
await page.waitToClick(selectors.itemsIndex.originCheckbox);
await page.waitToClick(selectors.itemsIndex.buyerCheckbox);
await page.waitToClick(selectors.itemsIndex.destinyCheckbox);
await page.waitToClick(selectors.itemsIndex.densityCheckbox);
await page.waitToClick(selectors.itemsIndex.saveFieldsButton);
const message = await page.waitForSnackbar();
@ -71,6 +72,7 @@ describe('Item index path', () => {
});
it('should now navigate forth and back to see the ids column is now visible', async() => {
await page.closePopup();
await page.waitToClick(selectors.itemsIndex.firstSearchResult);
await page.waitToClick(selectors.itemDescriptor.goBackToModuleIndexButton);
await page.waitToClick(selectors.globalItems.searchButton);

View File

@ -340,9 +340,10 @@ describe('Ticket Edit sale path', () => {
});
it('should confirm the new ticket received the line', async() => {
const expectedLines = 1;
const result = await page.countElement(selectors.ticketSales.saleLine);
expect(result).toEqual(1);
expect(result).toEqual(expectedLines);
});
it('should check the first sale reserved icon isnt visible', async() => {
@ -353,6 +354,7 @@ describe('Ticket Edit sale path', () => {
it('should mark the first sale as reserved', async() => {
await page.waitToClick(selectors.ticketSales.firstSaleCheckbox);
await page.waitToClick(selectors.ticketSales.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuReserve);
await page.closePopup();

View File

@ -62,7 +62,7 @@ describe('Ticket Create packages path', () => {
expect(result).toEqual('7 : Container medical box 1m');
});
it(`should confirm the first quantity is just a number and the string part was ignored by the imput number`, async() => {
it(`should confirm quantity is just a number and the string part was ignored by the imput number`, async() => {
await page.waitForTextInField(selectors.ticketPackages.firstQuantity, '-99');
const result = await page.waitToGetProperty(selectors.ticketPackages.firstQuantity, 'value');

View File

@ -177,7 +177,6 @@ describe('Ticket descriptor path', () => {
describe('SMS', () => {
it('should send the payment SMS using the descriptor menu', async() => {
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitForContentLoaded();
await page.waitToClick(selectors.ticketDescriptor.moreMenuPaymentSMS);
await page.waitForSelector(selectors.ticketDescriptor.SMStext);
await page.waitPropertyLength(selectors.ticketDescriptor.SMStext, 'value', 128);

View File

@ -21,8 +21,8 @@ describe('Ticket index payout path', () => {
it('should check the second ticket from a client and 1 of another', async() => {
await page.waitToClick(selectors.globalItems.searchButton);
await page.waitToClick(selectors.ticketsIndex.secondTicketCheckbox);
await page.waitToClick(selectors.ticketsIndex.thirdTicketCheckbox);
await page.waitToClick(selectors.ticketsIndex.fifthTicketCheckbox);
await page.waitToClick(selectors.ticketsIndex.payoutButton);
const message = await page.waitForSnackbar();
@ -58,9 +58,11 @@ describe('Ticket index payout path', () => {
await page.selectModule('client');
await page.accessToSearchResult('1101');
await page.accessToSection('client.card.balance.index');
await page.waitForSelector('vn-client-balance-index vn-tbody > vn-tr');
let result = await page.countElement('vn-client-balance-index vn-tbody > vn-tr');
await page.waitForSelector(selectors.clientBalance.anyBalanceLine);
const count = await page.countElement(selectors.clientBalance.anyBalanceLine);
const reference = await page.waitToGetProperty(selectors.clientBalance.firstLineReference, 'innerText');
expect(result).toEqual(4);
expect(count).toEqual(4);
expect(reference).toContain('Cash, Albaran: 7, 8Payment');
});
});

View File

@ -25,6 +25,7 @@ describe('Zone basic data path', () => {
await page.clearInput(selectors.zoneBasicData.name);
await page.write(selectors.zoneBasicData.name, 'Brimstone teleportation');
await page.autocompleteSearch(selectors.zoneBasicData.agency, 'Quantum break device');
await page.clearInput(selectors.zoneBasicData.maxVolume);
await page.write(selectors.zoneBasicData.maxVolume, '10');
await page.clearInput(selectors.zoneBasicData.travelingDays);
await page.write(selectors.zoneBasicData.travelingDays, '1');

View File

@ -31,7 +31,7 @@ describe('Entry lastest buys path', () => {
await page.waitForSelector(selectors.entryLatestBuys.fieldAutocomplete, {visible: true});
});
it('should search for the "Description" field and type a new description for the items in each selected buy', async() => {
it('should search for the "Description" and type a new one for the items in each selected buy', async() => {
await page.autocompleteSearch(selectors.entryLatestBuys.fieldAutocomplete, 'Description');
await page.write(selectors.entryLatestBuys.newValueInput, 'Crafted item');
await page.waitToClick(selectors.entryLatestBuys.acceptEditBuysDialog);

View File

@ -28,7 +28,7 @@ describe('Entry import, create and edit buys path', () => {
await page.waitForState('entry.card.buy.import');
});
it('should fill the form, import the designated JSON file and select items for each import and confirm import', async() => {
it('should fill the form, import the a JSON file and select items for each import and confirm import', async() => {
let currentDir = process.cwd();
let filePath = `${currentDir}/e2e/assets/07_import_buys.json`;
@ -42,7 +42,8 @@ describe('Entry import, create and edit buys path', () => {
await page.waitForTextInField(selectors.entryBuys.observation, '729-6340 2846');
await page.autocompleteSearch(selectors.entryBuys.firstImportedItem, 'Ranged Reinforced weapon pistol 9mm');
await page.autocompleteSearch(selectors.entryBuys.secondImportedItem, 'Melee Reinforced weapon heavy shield 1x0.5m');
const itemName = 'Melee Reinforced weapon heavy shield 1x0.5m';
await page.autocompleteSearch(selectors.entryBuys.secondImportedItem, itemName);
await page.autocompleteSearch(selectors.entryBuys.thirdImportedItem, 'Container medical box 1m');
await page.autocompleteSearch(selectors.entryBuys.fourthImportedItem, 'Container ammo box 1m');
@ -88,37 +89,37 @@ describe('Entry import, create and edit buys path', () => {
it('should edit the newest buy', async() => {
await page.clearInput(selectors.entryBuys.secondBuyPackingPrice);
await page.waitForTextInField(selectors.entryBuys.secondBuyPackingPrice, '');
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyPackingPrice, '100');
await page.waitForSnackbar();
await page.clearInput(selectors.entryBuys.secondBuyGroupingPrice);
await page.waitForTextInField(selectors.entryBuys.secondBuyGroupingPrice, '');
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyGroupingPrice, '200');
await page.waitForSnackbar();
await page.clearInput(selectors.entryBuys.secondBuyPrice);
await page.waitForTextInField(selectors.entryBuys.secondBuyPrice, '');
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyPrice, '300');
await page.waitForSnackbar();
await page.clearInput(selectors.entryBuys.secondBuyGrouping);
await page.waitForTextInField(selectors.entryBuys.secondBuyGrouping, '');
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyGrouping, '400');
await page.waitForSnackbar();
await page.clearInput(selectors.entryBuys.secondBuyPacking);
await page.waitForTextInField(selectors.entryBuys.secondBuyPacking, '');
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyPacking, '500');
await page.waitForSnackbar();
await page.clearInput(selectors.entryBuys.secondBuyWeight);
await page.waitForTextInField(selectors.entryBuys.secondBuyWeight, '');
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyWeight, '600');
await page.waitForSnackbar();
await page.clearInput(selectors.entryBuys.secondBuyStickers);
await page.waitForTextInField(selectors.entryBuys.secondBuyStickers, '');
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyStickers, '700');
await page.waitForSnackbar();
@ -126,7 +127,7 @@ describe('Entry import, create and edit buys path', () => {
await page.waitForSnackbar();
await page.clearInput(selectors.entryBuys.secondBuyQuantity);
await page.waitForTextInField(selectors.entryBuys.secondBuyQuantity, '');
await page.waitForTimeout(250);
await page.write(selectors.entryBuys.secondBuyQuantity, '800');
});

View File

@ -22,6 +22,7 @@ describe('Supplier basic data path', () => {
await page.write(selectors.supplierBasicData.alias, 'Plants Nick SL');
await page.waitToClick(selectors.supplierBasicData.isSerious);
await page.waitToClick(selectors.supplierBasicData.isActive);
await page.waitToClick(selectors.supplierBasicData.isPayMethodChecked);
await page.write(selectors.supplierBasicData.notes, 'Some notes');
await page.waitToClick(selectors.supplierBasicData.saveButton);
@ -52,6 +53,12 @@ describe('Supplier basic data path', () => {
expect(result).toBe('unchecked');
});
it('should check the isPayMethodChecked checkbox is now unchecked', async() => {
const result = await page.checkboxState(selectors.supplierBasicData.isPayMethodChecked);
expect(result).toBe('unchecked');
});
it('should check the notes were edited', async() => {
const result = await page.waitToGetProperty(selectors.supplierBasicData.notes, 'value');

View File

@ -49,7 +49,7 @@ export default class Contextmenu {
get rowIndex() {
if (!this.row) return null;
const table = this.row.closest('vn-table, .vn-table');
const table = this.row.closest('table, vn-table, .vn-table');
const rows = table.querySelectorAll('[ng-repeat]');
return Array.from(rows).findIndex(
@ -67,13 +67,13 @@ export default class Contextmenu {
get cell() {
if (!this.target) return null;
return this.target.closest('vn-td, .vn-td, vn-td-editable');
return this.target.closest('td, vn-td, .vn-td, vn-td-editable');
}
get cellIndex() {
if (!this.row) return null;
const cells = this.row.querySelectorAll('vn-td, .vn-td, vn-td-editable');
const cells = this.row.querySelectorAll('td, vn-td, .vn-td, vn-td-editable');
return Array.from(cells).findIndex(
cellItem => cellItem == this.cell
);
@ -82,8 +82,8 @@ export default class Contextmenu {
get rowHeader() {
if (!this.row) return null;
const table = this.row.closest('vn-table, .vn-table');
const headerCells = table && table.querySelectorAll('vn-thead vn-th');
const table = this.row.closest('table, vn-table, .vn-table');
const headerCells = table && table.querySelectorAll('thead th, vn-thead vn-th');
const headerCell = headerCells && headerCells[this.cellIndex];
return headerCell;
@ -147,7 +147,7 @@ export default class Contextmenu {
*/
isActionAllowed() {
if (!this.target) return false;
const isTableCell = this.target.closest('vn-td, .vn-td');
const isTableCell = this.target.closest('td, vn-td, .vn-td');
return isTableCell && this.fieldName;
}
@ -172,9 +172,28 @@ export default class Contextmenu {
excludeSelection() {
let where = {[this.fieldName]: {neq: this.fieldValue}};
if (this.exprBuilder) {
where = buildFilter(where, (param, value) =>
this.exprBuilder({param, value})
);
where = {[this.fieldName]: this.fieldValue};
where = buildFilter(where, (param, value) => {
const expr = this.exprBuilder({param, value});
const props = Object.keys(expr);
let newExpr = {};
for (let prop of props) {
if (expr[prop].like) {
const operator = expr[prop].like;
newExpr[prop] = {nlike: operator};
} else if (expr[prop].between) {
const operator = expr[prop].between;
newExpr = {
or: [
{[prop]: {lt: operator[0]}},
{[prop]: {gt: operator[1]}},
]
};
} else
newExpr[prop] = {neq: this.fieldValue};
}
return newExpr;
});
}
this.model.addFilter({where});
@ -208,15 +227,22 @@ export default class Contextmenu {
if (prop == findProp)
delete instance[prop];
if (prop === 'and') {
for (let [index, param] of instance[prop].entries()) {
if (prop === 'and' || prop === 'or') {
const instanceCopy = instance[prop].slice();
for (let param of instanceCopy) {
const [key] = Object.keys(param);
const index = instance[prop].findIndex(param => {
return Object.keys(param)[0] == key;
});
if (key == findProp)
instance[prop].splice(index, 1);
if (param[key] instanceof Array)
removeProp(param, filterKey, key);
}
if (instance[prop].length == 0)
delete instance[prop];
}
}

View File

@ -52,3 +52,4 @@ import './wday-picker';
import './datalist';
import './contextmenu';
import './rating';
import './smart-table';

View File

@ -3,8 +3,8 @@ import Popover from '../popover';
import './style.scss';
export default class Menu extends Popover {
show(parent) {
super.show(parent);
show(parent, direction) {
super.show(parent, direction);
this.windowEl.addEventListener('click', () => this.hide());
}
}

View File

@ -1,7 +1,18 @@
@import "./effects";
@import "variables";
.vn-menu {
vn-item, .vn-item {
@extend %clickable;
}
vn-item.dropdown:after,
.vn-item.dropdown:after {
font-family: 'Material Icons';
content: 'keyboard_arrow_right';
position: absolute;
color: $color-spacer;
font-size: 1.5em;
right: 0
}
}

View File

@ -145,9 +145,8 @@ export default class MultiCheck extends FormInput {
toggle() {
const data = this.model.data;
if (!data) return;
data.forEach(el => {
for (let el of data)
el[this.checkField] = this.checkAll;
});
}
}
@ -156,8 +155,9 @@ ngModule.vnComponent('vnMultiCheck', {
controller: MultiCheck,
bindings: {
model: '<',
checkField: '<?',
checkField: '@?',
checkAll: '=?',
checked: '=?',
disabled: '<?'
}
});

View File

@ -1,9 +1,10 @@
<div ng-if="$ctrl.model.moreRows">
<vn-button
<div ng-if="$ctrl.model.moreRows" class="vn-py-md">
<div
ng-if="!$ctrl.model.isPaging"
label="Load more results"
ng-click="$ctrl.onLoadClick()">
</vn-button>
<vn-button label="Load more results"></vn-button>
<vn-icon icon="arrow_drop_down"/>
</div>
<vn-spinner
ng-if="$ctrl.model.isPaging"
enable="::true">

View File

@ -1,7 +1,13 @@
@import "variables";
vn-pagination {
display: block;
text-align: center;
color: $color-primary;
vn-button, vn-icon {
display: block
}
& > div > vn-icon-button {
font-size: 2rem;

View File

@ -23,12 +23,15 @@ export default class Popover extends Popup {
* it is shown in a visible relative position to it.
*
* @param {HTMLElement|Event} parent Overrides the parent property
* @param {String} direction - Direction [left]
*/
show(parent) {
show(parent, direction) {
if (parent instanceof Event)
parent = event.target;
if (parent) this.parent = parent;
if (direction) this.direction = direction;
super.show();
this.content = this.popup.querySelector('.content');
this.$timeout(() => this.relocate(), 10);
@ -89,21 +92,40 @@ export default class Popover extends Popup {
let width = clamp(popoverRect.width, parentRect.width, maxWith);
let height = popoverRect.height;
let left = parentRect.left + parentRect.width / 2 - width / 2;
left = clamp(left, margin, maxRight - width);
let left;
if (this.direction == 'left') {
left = parentRect.left + parentRect.width;
left = clamp(left, margin, maxRight - width);
} else {
left = parentRect.left + parentRect.width / 2 - width / 2;
left = clamp(left, margin, maxRight - width);
}
let top = parentRect.top + parentRect.height + arrowOffset;
let top;
if (this.direction == 'left')
top = parentRect.top;
else
top = parentRect.top + parentRect.height + arrowOffset;
let showTop = top + height > maxBottom;
if (showTop) top = parentRect.top - height - arrowOffset;
top = Math.max(top, margin);
if (showTop)
if (this.direction == 'left')
arrowStyle.left = `0`;
else if (showTop)
arrowStyle.bottom = `0`;
else
arrowStyle.top = `0`;
let arrowLeft = (parentRect.left - left) + parentRect.width / 2;
arrowLeft = clamp(arrowLeft, arrowHeight, width - arrowHeight);
let arrowLeft;
if (this.direction == 'left') {
arrowLeft = 0;
let arrowTop = arrowOffset;
arrowStyle.top = `${arrowTop}px`;
} else {
arrowLeft = (parentRect.left - left) + parentRect.width / 2;
arrowLeft = clamp(arrowLeft, arrowHeight, width - arrowHeight);
}
arrowStyle.left = `${arrowLeft}px`;
style.top = `${top}px`;

View File

@ -0,0 +1,102 @@
<div class="vn-pa-md">
<vn-horizontal class="actions">
<div class="actions-left">
<vn-button icon="view_column"
id="shownColumns"
ng-if="$ctrl.options.activeButtons.shownColumns"
ng-click="smartTableColumns.show($event)"
vn-tooltip="Shown columns">
</vn-button>
<div ng-transclude="actions"></div>
</div>
<div class="actions-right">
<div class="totalRows" ng-if="$ctrl.model.data">
{{model.data.length}}
<span translate>results</span>
</div>
<vn-button icon="search"
ng-if="$ctrl.options.activeButtons.search"
ng-click="$ctrl.displaySearch()"
vn-tooltip="Search">
</vn-button>
<div class="button-group"
ng-if="$ctrl.options.activeButtons.crud">
<vn-button icon="add"
ng-click="$ctrl.createRow()"
vn-tooltip="Add new row">
</vn-button>
<vn-button icon="undo"
ng-click="$ctrl.model.undoChanges()"
vn-tooltip="Undo">
</vn-button>
<vn-button icon="delete"
ng-click="deleteConfirmation.show($event)"
ng-show="$ctrl.checkedRows.length > 0"
vn-tooltip="Remove selected rows">
</vn-button>
<vn-button icon="save"
ng-click="$ctrl.saveAll()"
vn-tooltip="Save data">
</vn-button>
</div>
<vn-button icon="refresh"
ng-click="$ctrl.model.refresh()"
vn-tooltip="Refresh">
</vn-button>
</div>
</vn-horizontal>
<div id="table"></div>
<vn-pagination
ng-if="$ctrl.model"
model="$ctrl.model"
class="vn-pt-md">
</vn-pagination>
</div>
<vn-confirm
vn-id="deleteConfirmation"
on-accept="$ctrl.deleteAll()"
question="Are you sure you want to continue?"
message="Remove selected rows">
</vn-confirm>
<vn-crud-model
ng-if="$ctrl.viewConfigId"
vn-id="userViewModel"
url="UserConfigViews"
link="{tableCode: $ctrl.viewConfigId, userFk: $ctrl.currentUserId}"
data="$ctrl.viewConfig"
auto-load="true">
</vn-crud-model>
<vn-popover vn-id="smart-table-columns" message="Fields to show">
<tpl-body>
<div class="smart-table-columns vn-pa-md vn-w-sm">
<vn-horizontal>
<h6 translate style="margin:0">Shown columns</h6>
<vn-icon
vn-none
icon="info"
color-marginal
vn-tooltip="Check the columns you want to see"/>
</vn-horizontal>
<div class="vn-mb-md">
<vn-check label="Tick all"
ng-model="$ctrl.checkAll">
</vn-check>
</div>
<vn-horizontal class="vn-mb-md">
<vn-check ng-repeat="column in $ctrl.columns"
label="{{column.caption}}"
ng-model="$ctrl.viewConfig[0].configuration[column.field]">
</vn-check>
</vn-horizontal>
<vn-horizontal>
<vn-button
label="Save"
ng-click="$ctrl.saveViewConfig()">
</vn-button>
</vn-horizontal>
</div>
</tpl-body>
</vn-popover>

View File

@ -0,0 +1,454 @@
import ngModule from '../../module';
import Component from '../../lib/component';
import {buildFilter} from 'vn-loopback/util/filter';
import angular from 'angular';
import {camelToKebab} from '../../lib/string';
import './style.scss';
import './table.scss';
export default class SmartTable extends Component {
constructor($element, $, $transclude) {
super($element, $);
this.currentUserId = window.localStorage.currentUserWorkerId;
this.$transclude = $transclude;
this.sortCriteria = [];
this.$inputsScope;
this.columns = [];
this.autoSave = false;
this.transclude();
}
$onDestroy() {
const styleElement = document.querySelector('style[id="smart-table"]');
if (this.$.css && styleElement)
styleElement.parentNode.removeChild(styleElement);
}
get options() {
return this._options;
}
set options(options) {
this._options = options;
if (!options) return;
const activeButtons = options.activeButtons;
const missingId = activeButtons && activeButtons.shownColumns && !this.viewConfigId;
if (missingId)
throw new Error('vnSmartTable: View identifier not defined');
}
get model() {
return this._model;
}
set model(value) {
this._model = value;
if (value)
this.$.model = value;
}
get viewConfigId() {
return this._viewConfigId;
}
set viewConfigId(value) {
this._viewConfigId = value;
/* if (value) {
this.defaultViewConfig = {};
const url = 'DefaultViewConfigs';
const filter = {where: {tableCode: value}};
this.$http.get(url, {filter})
.then(res => {
if (res && res.data.length) {
const columns = res.data[0].columns;
this.defaultViewConfig = columns;
}
});
} */
}
getDefaultViewConfig() {
const url = 'DefaultViewConfigs';
const filter = {where: {tableCode: this.viewConfigId}};
return this.$http.get(url, {filter})
.then(res => {
if (res && res.data.length)
return res.data[0].columns;
});
}
get viewConfig() {
return this._viewConfig;
}
set viewConfig(value) {
this._viewConfig = value;
if (!value) return;
if (!value.length) {
this.getDefaultViewConfig().then(columns => {
const defaultViewConfig = columns ? columns : {};
const userViewModel = this.$.userViewModel;
for (const column of this.columns) {
if (defaultViewConfig[column.field] == undefined)
defaultViewConfig[column.field] = true;
}
userViewModel.insert({
userFk: this.currentUserId,
tableConfig: this.viewConfigId,
configuration: defaultViewConfig
});
}).finally(() => this.applyViewConfig());
} else
this.applyViewConfig();
}
get checkedRows() {
const model = this.model;
if (model && model.data)
return model.data.filter(row => row.$checked);
return null;
}
get checkAll() {
return this._checkAll;
}
set checkAll(value) {
this._checkAll = value;
if (value !== undefined) {
const shownColumns = this.viewConfig[0].configuration;
for (let param in shownColumns)
shownColumns[param] = value;
}
}
transclude() {
const slotTable = this.element.querySelector('#table');
this.$transclude($clone => {
const table = $clone[0];
slotTable.appendChild(table);
this.registerColumns();
this.emptyDataRows();
}, null, 'table');
}
saveViewConfig() {
const userViewModel = this.$.userViewModel;
const [viewConfig] = userViewModel.data;
viewConfig.configuration = Object.assign({}, viewConfig.configuration);
userViewModel.save()
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')))
.then(() => this.applyViewConfig())
.then(() => this.$.smartTableColumns.hide());
}
applyViewConfig() {
const userViewModel = this.$.userViewModel;
const [viewConfig] = userViewModel.data;
const selectors = [];
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} tbody > tr > td:nth-child(${column.index + 1})`);
}
}
let styleElement = document.querySelector('style[id="smart-table"]');
if (styleElement)
styleElement.parentNode.removeChild(styleElement);
if (selectors.length) {
const rule = selectors.join(', ') + '{display: none}';
this.$.css = document.createElement('style');
this.$.css.setAttribute('id', 'smart-table');
document.head.appendChild(this.$.css);
this.$.css.appendChild(document.createTextNode(rule));
}
}
registerColumns() {
const header = this.element.querySelector('thead > tr');
if (!header) return;
const columns = header.querySelectorAll('th');
// Click handler
for (const [index, column] of columns.entries()) {
const field = column.getAttribute('field');
if (field) {
const columnElement = angular.element(column);
const caption = columnElement.text().trim();
this.columns.push({field, caption, index});
column.addEventListener('click', () => this.orderHandler(column));
}
}
}
emptyDataRows() {
const header = this.element.querySelector('thead > tr');
const columns = header.querySelectorAll('th');
const tbody = this.element.querySelector('tbody');
if (tbody) {
const noSearch = this.$compile(`
<tr class="empty-rows" ng-if="!model.data">
<td colspan="${columns.length}" translate>Enter a new search</td>
</tr>
`)(this.$);
tbody.appendChild(noSearch[0]);
const noRows = this.$compile(`
<tr class="empty-rows" ng-if="model.data.length == 0">
<td colspan="${columns.length}" translate>No data</td>
</tr>
`)(this.$);
tbody.appendChild(noRows[0]);
}
}
orderHandler(element) {
const field = element.getAttribute('field');
const existingCriteria = this.sortCriteria.find(criteria => {
return criteria.field == field;
});
const isASC = existingCriteria && existingCriteria.sortType == 'ASC';
const isDESC = existingCriteria && existingCriteria.sortType == 'DESC';
if (!existingCriteria) {
this.sortCriteria.push({field: field, sortType: 'ASC'});
element.classList.remove('desc');
element.classList.add('asc');
}
if (isDESC) {
this.sortCriteria.splice(this.sortCriteria.findIndex(criteria => {
return criteria.field == field;
}), 1);
element.classList.remove('desc');
element.classList.remove('asc');
}
if (isASC) {
existingCriteria.sortType = 'DESC';
element.classList.remove('asc');
element.classList.add('desc');
}
this.applySort();
}
displaySearch() {
const header = this.element.querySelector('thead > tr');
if (!header) return;
const tbody = this.element.querySelector('tbody');
const columns = header.querySelectorAll('th');
const hasSearchRow = tbody.querySelector('tr#searchRow');
if (hasSearchRow) {
if (this.$inputsScope)
this.$inputsScope.$destroy();
return hasSearchRow.remove();
}
const searchRow = document.createElement('tr');
searchRow.setAttribute('id', 'searchRow');
this.$inputsScope = this.$.$new();
for (let column of columns) {
const field = column.getAttribute('field');
const cell = document.createElement('td');
if (field) {
let input;
let options;
const columnOptions = this.options && this.options.columns;
if (columnOptions)
options = columnOptions.find(column => column.field == field);
if (options && options.searchable == false) {
searchRow.appendChild(cell);
continue;
}
if (options && options.autocomplete) {
let props = ``;
const autocomplete = options.autocomplete;
for (const prop in autocomplete)
props += `${camelToKebab(prop)}="${autocomplete[prop]}"\n`;
input = this.$compile(`
<vn-autocomplete
class="dense"
name="${field}"
ng-model="searchProps['${field}']"
${props}
on-change="$ctrl.searchByColumn('${field}')"
clear-disabled="true"
/>`)(this.$inputsScope);
} else {
input = this.$compile(`
<vn-textfield
class="dense"
name="${field}"
ng-model="searchProps['${field}']"
ng-keydown="$ctrl.searchWithEvent($event, '${field}')"
clear-disabled="true"
/>`)(this.$inputsScope);
}
cell.appendChild(input[0]);
}
searchRow.appendChild(cell);
}
tbody.prepend(searchRow);
}
searchWithEvent($event, field) {
if ($event.key != 'Enter') return;
this.searchByColumn(field);
}
searchByColumn(field) {
const searchCriteria = this.$inputsScope.searchProps[field];
const emptySearch = searchCriteria == '' || null;
const filters = this.filterSanitizer(field);
if (filters && filters.userFilter)
this.model.userFilter = filters.userFilter;
if (!emptySearch)
this.addFilter(field, this.$inputsScope.searchProps[field]);
else this.model.refresh();
}
addFilter(field, value) {
let where = {[field]: value};
if (this.exprBuilder) {
where = buildFilter(where, (param, value) =>
this.exprBuilder({param, value})
);
}
this.model.addFilter({where});
}
applySort() {
let order = this.sortCriteria.map(criteria => `${criteria.field} ${criteria.sortType}`);
order = order.join(', ');
if (order)
this.model.order = order;
this.model.refresh();
}
filterSanitizer(field) {
const userFilter = this.model.userFilter;
const userParams = this.model.userParams;
const where = userFilter && userFilter.where;
if (this.exprBuilder) {
const param = this.exprBuilder({
param: field,
value: null
});
if (param) [field] = Object.keys(param);
}
if (!where) return;
const whereKeys = Object.keys(where);
for (let key of whereKeys) {
removeProp(where, field, key);
if (!Object.keys(where))
delete userFilter.where;
}
function removeProp(instance, findProp, prop) {
if (prop == findProp)
delete instance[prop];
if (prop === 'and') {
for (let [index, param] of instance[prop].entries()) {
const [key] = Object.keys(param);
if (key == findProp)
instance[prop].splice(index, 1);
if (param[key] instanceof Array)
removeProp(param, field, key);
}
}
}
return {userFilter, userParams};
}
removeFilter() {
this.model.applyFilter(userFilter, userParams);
}
createRow() {
let data = {};
if (this.defaultNewData)
data = this.defaultNewData();
this.model.insert(data);
}
deleteAll() {
for (let row of this.checkedRows)
this.model.removeRow(row);
if (this.autoSave)
this.saveAll();
}
saveAll() {
const model = this.model;
if (!model.isChanged)
return this.vnApp.showError(this.$t('No changes to save'));
this.model.save()
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')));
}
}
SmartTable.$inject = ['$element', '$scope', '$transclude'];
ngModule.vnComponent('smartTable', {
template: require('./index.html'),
controller: SmartTable,
transclude: {
table: '?slotTable',
actions: '?slotActions'
},
bindings: {
model: '<?',
viewConfigId: '@?',
autoSave: '<?',
exprBuilder: '&?',
defaultNewData: '&?',
options: '<?'
}
});

View File

@ -0,0 +1,9 @@
Remove selected rows: Eliminar líneas seleccionadas
Add new row: Añadir nueva fila
Undo: Deshacer
Save data: Guardar datos
Shown columns: Columnas visibles
Check the columns you want to see: Marca las columnas que quieres ver
Showing: Mostrando
results: resultados
Tick all: Marcar todas

View File

@ -0,0 +1,146 @@
@import "effects";
@import "variables";
smart-table {
th[field] {
overflow: visible;
cursor: pointer;
align-items: center;
}
th[field][number] {
& > :before {
vertical-align: middle;
font-family: 'Material Icons';
content: 'arrow_downward';
color: $color-spacer;
margin-right: 2px;
opacity: 0
}
&.asc > :before, &.desc > :before {
color: $color-font;
opacity: 1;
}
&.asc > :before {
content: 'arrow_upward';
}
&.desc > :before {
content: 'arrow_downward';
}
&:hover > :before {
opacity: 1;
}
}
th[field]:not([number]) {
& > :after {
vertical-align: middle;
font-family: 'Material Icons';
content: 'arrow_downward';
color: $color-spacer;
margin-left: 2px;
opacity: 0
}
&.asc > :after, &.desc > :after {
color: $color-font;
opacity: 1;
}
&.asc > :after {
content: 'arrow_upward';
}
&.desc > :after {
content: 'arrow_downward';
}
&:hover > :after {
opacity: 1;
}
}
tr[vn-anchor] {
@extend %clickable;
}
.totalRows {
color: $color-font-secondary;
}
.actions-left,
.actions-right {
display: flex;
align-items: center;
.button-group {
display: flex;
box-shadow: 0 2px 2px 0 rgba(0, 0, 0, .3);
& > vn-button {
box-shadow: 0 0 0 0
}
}
}
.actions-left {
justify-content: flex-start;
slot-actions > vn-button,
& > vn-button,
.button-group {
margin-right: 10px
}
slot-actions {
display: flex
}
}
.actions-right {
justify-content: flex-end;
& > vn-button,
.button-group {
margin-left: 10px
}
}
#table {
overflow-x: auto;
margin-top: 15px
}
vn-tbody a[ng-repeat].vn-tr:focus {
background-color: $color-primary-light
}
.new-row {
background-color: $color-success-light
}
.changed-row {
background-color: $color-primary-light
}
}
.smart-table-columns {
h6 {
color: $color-font-secondary
}
& > vn-horizontal {
align-items: flex-start;
flex-wrap: wrap;
}
vn-check {
flex: initial;
width: 33%
}
}

View File

@ -0,0 +1,108 @@
@import "effects";
@import "variables";
smart-table table {
width: 100%;
border-collapse: collapse;
& > thead {
border-bottom: 2px solid $color-spacer;
& > * > th {
font-weight: normal;
}
}
& > tfoot {
border-top: 2px solid $color-spacer;
}
thead, tbody, tfoot {
& > * {
& > th {
color: $color-font-light;
}
& > th,
& > td {
overflow: hidden;
}
& > th,
& > td {
text-align: left;
padding: 9px 5px;
white-space: nowrap;
text-overflow: ellipsis;
&[number] {
text-align: right;
}
&[centered] {
text-align: center;
}
&[shrink] {
width: 1px;
text-align: center;
}
&[shrink-date] {
width: 100px;
max-width: 100px;
}
&[shrink-datetime] {
width: 150px;
max-width: 150px;
}
&[expand] {
max-width: 400px;
min-width: 0;
}
&[actions] {
width: 1px;
& > * {
vertical-align: middle;
}
}
vn-icon.bright, i.bright {
color: #f7931e;
}
}
}
}
tbody > * {
border-bottom: 1px solid $color-spacer-light;
&:last-child {
border-bottom: none;
}
& > td {
.chip {
padding: 4px;
border-radius: 4px;
color: $color-font-bg;
&.notice {
background-color: $color-notice-medium
}
&.success {
background-color: $color-success-medium;
}
&.warning {
background-color: $color-main-medium;
}
&.alert {
background-color: $color-alert-medium;
}
&.message {
color: $color-font-dark;
background-color: $color-bg-dark
}
}
}
}
.vn-check {
margin: 0;
}
.empty-rows > td {
color: $color-font-secondary;
font-size: 1.375rem;
text-align: center;
}
}

View File

@ -96,24 +96,6 @@ vn-table {
max-width: 400px;
min-width: 0;
}
&[vn-fetched-tags] {
min-width: 155px;
& > vn-one {
overflow: hidden;
text-overflow: ellipsis;
font-size: 1rem;
}
& > vn-one:nth-child(2) h3 {
color: $color-font-secondary;
text-transform: uppercase;
line-height: initial;
font-size: 0.75rem
}
}
&[vn-fetched-tags][wide] {
width: 430px;
}
vn-icon.bright, i.bright {
color: #f7931e;
}

View File

@ -16,3 +16,4 @@ import './droppable';
import './http-click';
import './http-submit';
import './anchor';

View File

@ -18,6 +18,18 @@ class Email {
return this.$http.get(`email/${template}`, {params})
.then(() => this.vnApp.showMessage(this.$t('Notification sent!')));
}
/**
* Sends an email displaying a notification when it's sent.
*
* @param {String} template The email report name
* @param {Object} params The email parameters
* @return {Promise} Promise resolved when it's sent
*/
sendCsv(template, params) {
return this.$http.get(`csv/${template}/send`, {params})
.then(() => this.vnApp.showMessage(this.$t('Notification sent!')));
}
}
Email.$inject = ['$http', '$translate', 'vnApp'];

View File

@ -20,6 +20,21 @@ class Report {
const serializedParams = this.$httpParamSerializer(params);
window.open(`api/report/${report}?${serializedParams}`);
}
/**
* Shows a report in another window, automatically adds the authorization
* token to params.
*
* @param {String} report The report name
* @param {Object} params The report parameters
*/
showCsv(report, params) {
params = Object.assign({
authorization: this.vnToken.token
}, params);
const serializedParams = this.$httpParamSerializer(params);
window.open(`api/csv/${report}/download?${serializedParams}`);
}
}
Report.$inject = ['$httpParamSerializer', 'vnToken'];

133
front/package-lock.json generated
View File

@ -1,133 +0,0 @@
{
"name": "salix-front",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@uirouter/angularjs": {
"version": "1.0.29",
"resolved": "https://registry.npmjs.org/@uirouter/angularjs/-/angularjs-1.0.29.tgz",
"integrity": "sha512-RImWnBarNixkMto0o8stEaGwZmvhv5cnuOLXyMU2pY8MP2rgEF74ZNJTLeJCW14LR7XDUxVH8Mk8bPI6lxedmQ==",
"requires": {
"@uirouter/core": "6.0.7"
}
},
"@uirouter/core": {
"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=="
},
"angular-animate": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.8.2.tgz",
"integrity": "sha512-Jbr9+grNMs9Kj57xuBU3Ju3NOPAjS1+g2UAwwDv7su1lt0/PLDy+9zEwDiu8C8xJceoTbmBNKiWGPJGBdCQLlA=="
},
"angular-moment": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/angular-moment/-/angular-moment-1.3.0.tgz",
"integrity": "sha512-KG8rvO9MoaBLwtGnxTeUveSyNtrL+RNgGl1zqWN36+HDCCVGk2DGWOzqKWB6o+eTTbO3Opn4hupWKIElc8XETA==",
"requires": {
"moment": ">=2.8.0 <3.0.0"
}
},
"angular-translate": {
"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==",
"requires": {
"angular": "^1.8.0"
}
},
"angular-translate-loader-partial": {
"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==",
"requires": {
"angular-translate": "~2.18.4"
}
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "~1.0.2"
}
},
"croppie": {
"version": "2.6.5",
"resolved": "https://registry.npmjs.org/croppie/-/croppie-2.6.5.tgz",
"integrity": "sha512-IlChnVUGG5T3w2gRZIaQgBtlvyuYnlUWs2YZIXXR3H9KrlO1PtBT3j+ykxvy9eZIWhk+V5SpBmhCQz5UXKrEKQ=="
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"mg-crud": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/mg-crud/-/mg-crud-1.1.2.tgz",
"integrity": "sha1-p6AWGzWSPK7/8ZpIBpS2V1vDggw=",
"requires": {
"angular": "^1.6.1"
}
},
"moment": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
},
"oclazyload": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/oclazyload/-/oclazyload-0.6.3.tgz",
"integrity": "sha1-Kjirv/QJDAihEBZxkZRbWfLoJ5w="
},
"require-yaml": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/require-yaml/-/require-yaml-0.0.1.tgz",
"integrity": "sha1-LhsY2RPDuqcqWk03O28Tjd0sMr0=",
"requires": {
"js-yaml": "^4.0.0"
},
"dependencies": {
"argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"js-yaml": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz",
"integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==",
"requires": {
"argparse": "^2.0.1"
}
}
}
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"validator": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-6.3.0.tgz",
"integrity": "sha1-R84j7Y1Ord+p1LjvAHG2zxB418g="
}
}
}

View File

@ -34,8 +34,10 @@ export default class Controller extends Component {
for (let starredModule of res.data) {
const module = this.modules.find(mod => mod.name === starredModule.moduleFk);
module.starred = true;
module.position = starredModule.position;
if (module) {
module.starred = true;
module.position = starredModule.position;
}
}
this.countModules();
});

View File

@ -3,7 +3,7 @@
icon="menu"
class="show-menu"
ng-if="$ctrl.leftMenu"
ng-click="$ctrl.leftMenu.show()">
ng-click="$ctrl.leftMenu.toggle()">
</vn-icon-button>
<div class="side start">
<a class="logo" ui-sref="home" title="{{'Home' | translate}}">

View File

@ -48,6 +48,10 @@ vn-layout {
.show-menu {
display: none;
}
& > .show-menu {
margin-right: 5px;
display: block
}
.vn-button {
color: inherit;
font-size: 1.05rem;
@ -71,6 +75,10 @@ vn-layout {
& > .main-view {
padding-left: $menu-width;
}
&.shown > .main-view {
padding-left: 0;
}
}
&.right-menu {
& > vn-topbar > .end {
@ -85,6 +93,8 @@ vn-layout {
}
& > .main-view {
padding-top: $topbar-height;
transition: padding-left 200ms ease-out;
}
ui-view {
& > * {
@ -134,7 +144,8 @@ vn-layout {
& > vn-topbar {
left: 0;
}
& > .main-view {
& > .main-view,
&.shown > .main-view {
padding-left: 0;
}
}

View File

@ -1,5 +1,11 @@
<vn-crud-model vn-id="model" url="{{$ctrl.url}}" filter="$ctrl.filter" link="{originFk: $ctrl.originId}"
data="$ctrl.logs" limit="20" auto-load="true">
<vn-crud-model
vn-id="model"
url="{{$ctrl.url}}"
filter="$ctrl.filter"
link="{originFk: $ctrl.originId}"
data="$ctrl.logs"
limit="20"
auto-load="true">
</vn-crud-model>
<vn-data-viewer model="model" class="vn-w-xl">
<vn-card>

View File

@ -52,12 +52,18 @@ export default class SideMenu extends Component {
this.hide();
}
toggle() {
if (this.shown) this.hide();
else this.show();
}
show() {
if (this.shown) return;
this.shown = true;
this.handler = e => this.onEscape(e);
this.$window.addEventListener('keydown', this.handler);
this.stateHandler = this.$transitions.onStart({}, t => this.onTransition(t));
this.layout.element.classList.add('shown');
}
hide() {
@ -65,6 +71,7 @@ export default class SideMenu extends Component {
this.$window.removeEventListener('keydown', this.handler);
this.stateHandler();
this.shown = false;
this.layout.element.classList.remove('shown');
}
}

View File

@ -14,16 +14,20 @@ vn-side-menu > .menu {
box-shadow: 0 1px 3px $color-shadow;
overflow: auto;
top: $topbar-height;
transition: transform 200ms ease-out;
&.left {
left: 0;
left: 0
}
&.right {
right: 0;
}
&.shown {
transform: translateZ(0) translateX(-$menu-width);
}
@media screen and (max-width: $mobile-width) {
transition: transform 200ms ease-out;
z-index: 15;
top: 0;

View File

@ -61,7 +61,7 @@
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
label="Type"
label="Orientation"
ng-model="$ctrl.viewportType"
data="$ctrl.viewportTypes"
selection="$ctrl.viewportSelection"

View File

@ -36,6 +36,18 @@ export default class UploadPhoto extends Component {
width: 1350,
height: 900
}
},
{
code: 'vertical',
description: this.$t('Vertical'),
viewport: {
width: 306.66,
height: 533.33
},
output: {
width: 460,
height: 800
}
}
];
this.viewportType = 'normal';
@ -103,8 +115,17 @@ export default class UploadPhoto extends Component {
const reader = new FileReader();
reader.onload = e => this.editor.bind({url: e.target.result});
reader.readAsDataURL(value);
} else if (this.uploadMethod == 'URL')
this.editor.bind({url: value});
} else if (this.uploadMethod == 'URL') {
const img = new Image();
img.crossOrigin = 'Anonymous';
img.src = value;
img.onload = () => this.editor.bind({url: value});
img.onerror = () => {
this.vnApp.showError(
this.$t(`This photo provider doesn't allow remote downloads`)
);
};
}
}
}

View File

@ -3,6 +3,8 @@ Select an image: Selecciona una imagen
File name: Nombre del fichero
Rotate left: Girar a la izquierda
Rotate right: Girar a la derecha
Panoramic: Panorámico
Panoramic: Panorámica
Orientation: Orientación
Select from computer: Seleccionar desde ordenador
Import from external URL: Importar desde URL externa
This photo provider doesn't allow remote downloads: Este proveedor de fotos no permite descargas remotas

View File

@ -94,8 +94,11 @@ async function launchBackTest(done) {
await new Promise((resolve, reject) => {
const jasmine = require('gulp-jasmine');
let options = {
const options = {
verbose: false,
includeStackTrace: false,
errorOnFail: false,
timeout: 5000,
config: {}
};
@ -125,7 +128,8 @@ async function launchBackTest(done) {
if (err)
throw err;
}
launchBackTest.description = `Runs the backend tests once using a random container, can receive --ci arg to save reports on a xml file`;
launchBackTest.description = `
Runs the backend tests once using a random container, can receive --ci arg to save reports on a xml file`;
// Backend tests

View File

@ -8,11 +8,12 @@ describe('Model getEnumValues()', () => {
});
it('should return an array of enum values from a given column', async() => {
let result = await app.models.TravelThermograph.getSetValues('temperature');
let result = await app.models.TpvTransaction.getSetValues('status');
expect(result.length).toEqual(3);
expect(result.length).toEqual(4);
expect(result[0].value).toEqual('enum');
expect(result[1].value).toEqual('COOL');
expect(result[2].value).toEqual('WARM');
expect(result[1].value).toEqual('started');
expect(result[2].value).toEqual('ok');
expect(result[3].value).toEqual('ko');
});
});

View File

@ -115,5 +115,8 @@
"A ticket with a negative base can't be invoiced": "A ticket with a negative base can't be invoiced",
"This client is not invoiceable": "This client is not invoiceable",
"INACTIVE_PROVIDER": "Inactive provider",
"reference duplicated": "reference duplicated"
"reference duplicated": "reference duplicated",
"The PDF document does not exists": "The PDF document does not exists. Try regenerating it from 'Regenerate invoice PDF' option",
"This item is not available": "This item is not available",
"Deny buy request": "Purchase request for ticket id [{{ticketId}}]({{{url}}}) has been rejected. Reason: {{observation}}"
}

View File

@ -133,6 +133,7 @@
"reserved": "reservado",
"Changed sale reserved state": "He cambiado el estado reservado de las siguientes lineas al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Bought units from buy request": "Se ha comprado {{quantity}} unidades de [{{itemId}} {{concept}}]({{{urlItem}}}) para el ticket id [{{ticketId}}]({{{url}}})",
"Deny buy request":"Se ha rechazado la petición de compra para el ticket id [{{ticketId}}]({{{url}}}). Motivo: {{observation}}",
"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}}})",
@ -210,5 +211,6 @@
"Can't verify data unless the client has a business type": "No se puede verificar datos de un cliente que no tiene tipo de negocio",
"You don't have enough privileges to set this credit amount": "No tienes suficientes privilegios para establecer esta cantidad de crédito",
"You can't change the credit set to zero from a manager": "No puedes cambiar el cŕedito establecido a cero por un gerente",
"Amounts do not match": "Amounts do not match"
"Amounts do not match": "Amounts do not match",
"The PDF document does not exists": "El documento PDF no existe. Prueba a regenerarlo desde la opción 'Regenerar PDF factura'"
}

View File

@ -9,19 +9,19 @@
"properties": {
"id": {
"id": true,
"type": "Number"
"type": "number"
},
"sender": {
"type": "String"
"receiver": {
"type": "string"
},
"replyTo": {
"type": "String"
"type": "string"
},
"subject": {
"type": "String"
"type": "string"
},
"body": {
"type": "String"
"type": "string"
}
},
"acls": [

View File

@ -28,7 +28,7 @@
url="Workers/activeWithRole"
search-function="{firstName: $search}"
value-field="id"
where="{role: 'salesPerson'}"
where="{role: {inq: ['salesPerson', 'officeBoss']}}"
label="Salesperson">
<tpl-item>{{firstName}} {{name}}</tpl-item>
</vn-autocomplete>

View File

@ -50,7 +50,8 @@ module.exports = function(Self) {
city: data.city,
provinceFk: data.provinceFk,
countryFk: data.countryFk,
isEqualizated: data.isEqualizated
isEqualizated: data.isEqualizated,
businessTypeFk: data.businessTypeFk
}, myOptions);
const address = await models.Address.create({

View File

@ -19,7 +19,7 @@ module.exports = Self => {
}
});
Self.hasCustomerRole = (id, options) => {
Self.hasCustomerRole = async(id, options) => {
const myOptions = {};
if (typeof options == 'object')
@ -31,7 +31,9 @@ module.exports = Self => {
JOIN salix.Role r ON r.id = A.roleFK
WHERE r.name = 'customer'
AND A.id IN (?)`;
const [result] = await Self.rawSql(query, [id], myOptions);
const {isCustomer} = result;
return Self.rawSql(query, [id], myOptions);
return isCustomer;
};
};

View File

@ -8,12 +8,10 @@ describe('client canBeInvoiced()', () => {
accessToken: {userId: userId}
};
beforeAll(async done => {
beforeAll(async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
done();
});
it('should return falsy for a client without the data checked', async() => {

View File

@ -8,7 +8,8 @@ describe('Client Create', () => {
name: 'Wade',
socialName: 'Deadpool Marvel',
street: 'Wall Street',
city: 'New York'
city: 'New York',
businessTypeFk: 'florist'
};
it(`should not find Deadpool as he's not created yet`, async() => {
@ -45,6 +46,7 @@ describe('Client Create', () => {
expect(client.email).toEqual(newAccount.email);
expect(client.fi).toEqual(newAccount.fi);
expect(client.socialName).toEqual(newAccount.socialName);
expect(client.businessTypeFk).toEqual(newAccount.businessTypeFk);
await tx.rollback();
} catch (e) {

View File

@ -9,9 +9,9 @@ describe('Client hasCustomerRole', () => {
const id = 1101;
const [result] = await models.Client.hasCustomerRole(id, options);
const result = await models.Client.hasCustomerRole(id, options);
expect(result).toEqual(jasmine.objectContaining({isCustomer: 1}));
expect(result).toBeTruthy();
await tx.rollback();
} catch (e) {
@ -27,9 +27,9 @@ describe('Client hasCustomerRole', () => {
const options = {transaction: tx};
const id = 8;
const [result] = await models.Client.hasCustomerRole(id, options);
const result = await models.Client.hasCustomerRole(id, options);
expect(result).toEqual(jasmine.objectContaining({isCustomer: 0}));
expect(result).toBeFalsy();
await tx.rollback();
} catch (e) {
@ -46,9 +46,9 @@ describe('Client hasCustomerRole', () => {
const id = 999;
const [result] = await models.Client.hasCustomerRole(id, options);
const result = await models.Client.hasCustomerRole(id, options);
expect(result).toEqual(jasmine.objectContaining({isCustomer: 0}));
expect(result).toBeFalsy();
await tx.rollback();
} catch (e) {
@ -65,9 +65,9 @@ describe('Client hasCustomerRole', () => {
const id = 'WRONG!';
const [result] = await models.Client.hasCustomerRole(id, options);
const result = await models.Client.hasCustomerRole(id, options);
expect(result).toEqual(jasmine.objectContaining({isCustomer: 0}));
expect(result).toBeFalsy();
await tx.rollback();
} catch (e) {

View File

@ -129,7 +129,7 @@ module.exports = Self => {
function hasIban(err, done) {
Self.app.models.PayMethod.findById(this.payMethodFk, (_, instance) => {
if (instance && instance.ibanRequired && !this.iban)
if (instance && instance.ibanRequiredForClients && !this.iban)
err();
done();
});

View File

@ -130,6 +130,12 @@
"mysql": {
"columnName": "transactionTypeSageFk"
}
},
"businessTypeFk": {
"type": "string",
"mysql": {
"columnName": "businessTypeFk"
}
}
},
"relations": {

View File

@ -25,7 +25,10 @@
"outstandingDebt": {
"type": "Number"
},
"ibanRequired": {
"ibanRequiredForClients": {
"type": "boolean"
},
"ibanRequiredForSuppliers": {
"type": "boolean"
}
}

View File

@ -4,13 +4,11 @@ describe('loopback model address', () => {
let createdAddressId;
const clientId = 1101;
afterAll(async done => {
afterAll(async() => {
let client = await app.models.Client.findById(clientId);
await app.models.Address.destroyById(createdAddressId);
await client.updateAttribute('isEqualizated', false);
done();
});
describe('observe()', () => {

View File

@ -44,7 +44,8 @@
label="Amount"
ng-model="$ctrl.amountPaid"
step="0.01"
required="true">
required="true"
max="$ctrl.maxAmount">
</vn-input-number>
</vn-horizontal>
<vn-horizontal>

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