Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 5192-db_export
gitea/salix/pipeline/head There was a failure building this commit
Details
gitea/salix/pipeline/head There was a failure building this commit
Details
This commit is contained in:
commit
251c7b54ba
15
CHANGELOG.md
15
CHANGELOG.md
|
@ -5,17 +5,30 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [2306.01] - 2023-02-23
|
## [2308.01] - 2023-03-09
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
-
|
-
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
-
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
-
|
||||||
|
|
||||||
|
## [2306.01] - 2023-02-23
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- (Tickets -> Datos Básicos) Mensaje de confirmación al intentar generar tickets con negativos
|
||||||
|
- (Artículos) El visible y disponible se calcula a partir de un almacén diferente dependiendo de la sección en la que te encuentres. Se ha añadido un icono que informa sobre a partir de que almacén se esta calculando.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- (General -> Inicio) Ahora permite recuperar la contraseña tanto con el correo de recuperación como el usuario
|
- (General -> Inicio) Ahora permite recuperar la contraseña tanto con el correo de recuperación como el usuario
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- (Monitor de tickets) Cuando ordenas por columna, ya no se queda deshabilitado el botón de 'Actualizar'
|
- (Monitor de tickets) Cuando ordenas por columna, ya no se queda deshabilitado el botón de 'Actualizar'
|
||||||
- (Zone -> Días de entrega) Al hacer click en un día, muestra correctamente las zonas
|
- (Zone -> Días de entrega) Al hacer click en un día, muestra correctamente las zonas
|
||||||
|
- (Artículos) El disponible en la vista previa se muestra correctamente
|
||||||
|
|
||||||
## [2304.01] - 2023-02-09
|
## [2304.01] - 2023-02-09
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
"type": "date"
|
"type": "date"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"scope": {
|
"scope": {
|
||||||
"where" :{
|
"where" :{
|
||||||
"expired": null
|
"expired": null
|
||||||
|
|
|
@ -1,2 +1,173 @@
|
||||||
DELETE FROM `salix`.`ACL` WHERE model="SaleChecked";
|
DELETE FROM `salix`.`ACL` WHERE model="SaleChecked";
|
||||||
DROP TABLE IF EXISTS `vn`.`saleChecked`;
|
DROP TABLE IF EXISTS `vn`.`saleChecked`;
|
||||||
|
DROP PROCEDURE IF EXISTS `vn`.`clean`;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
$$
|
||||||
|
CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`clean`()
|
||||||
|
BEGIN
|
||||||
|
DECLARE vDateShort DATETIME;
|
||||||
|
DECLARE vOneYearAgo DATE;
|
||||||
|
DECLARE vFourYearsAgo DATE;
|
||||||
|
DECLARE v18Month DATE;
|
||||||
|
DECLARE v26Month DATE;
|
||||||
|
DECLARE v3Month DATE;
|
||||||
|
DECLARE vTrashId VARCHAR(15);
|
||||||
|
|
||||||
|
SET vDateShort = util.VN_CURDATE() - INTERVAL 2 MONTH;
|
||||||
|
SET vOneYearAgo = util.VN_CURDATE() - INTERVAL 1 YEAR;
|
||||||
|
SET vFourYearsAgo = util.VN_CURDATE() - INTERVAL 4 YEAR;
|
||||||
|
SET v18Month = util.VN_CURDATE() - INTERVAL 18 MONTH;
|
||||||
|
SET v26Month = util.VN_CURDATE() - INTERVAL 26 MONTH;
|
||||||
|
SET v3Month = util.VN_CURDATE() - INTERVAL 3 MONTH;
|
||||||
|
|
||||||
|
DELETE FROM ticketParking WHERE created < vDateShort;
|
||||||
|
DELETE FROM routesMonitor WHERE dated < vDateShort;
|
||||||
|
DELETE FROM workerTimeControlLog WHERE created < vDateShort;
|
||||||
|
DELETE FROM `message` WHERE sendDate < vDateShort;
|
||||||
|
DELETE FROM messageInbox WHERE sendDate < vDateShort;
|
||||||
|
DELETE FROM messageInbox WHERE sendDate < vDateShort;
|
||||||
|
DELETE FROM workerTimeControl WHERE timed < vFourYearsAgo;
|
||||||
|
DELETE FROM itemShelving WHERE created < util.VN_CURDATE() AND visible = 0;
|
||||||
|
DELETE FROM ticketDown WHERE created < TIMESTAMPADD(DAY,-1,util.VN_CURDATE());
|
||||||
|
DELETE FROM entryLog WHERE creationDate < vDateShort;
|
||||||
|
DELETE IGNORE FROM expedition WHERE created < v26Month;
|
||||||
|
DELETE FROM sms WHERE created < v18Month;
|
||||||
|
DELETE FROM saleTracking WHERE created < vOneYearAgo;
|
||||||
|
DELETE FROM ticketTracking WHERE created < v18Month;
|
||||||
|
DELETE tobs FROM ticketObservation tobs
|
||||||
|
JOIN ticket t ON tobs.ticketFk = t.id WHERE t.shipped < TIMESTAMPADD(YEAR,-2,util.VN_CURDATE());
|
||||||
|
DELETE sc.* FROM saleCloned sc JOIN sale s ON s.id = sc.saleClonedFk JOIN ticket t ON t.id = s.ticketFk WHERE t.shipped < vOneYearAgo;
|
||||||
|
DELETE FROM sharingCart where ended < vDateShort;
|
||||||
|
DELETE FROM sharingClient where ended < vDateShort;
|
||||||
|
DELETE tw.* FROM ticketWeekly tw
|
||||||
|
LEFT JOIN sale s ON s.ticketFk = tw.ticketFk WHERE s.itemFk IS NULL;
|
||||||
|
DELETE FROM claim WHERE ticketCreated < vFourYearsAgo;
|
||||||
|
DELETE FROM message WHERE sendDate < vDateShort;
|
||||||
|
-- Robert ubicacion anterior de trevelLog comentario para debug
|
||||||
|
DELETE FROM zoneEvent WHERE `type` = 'day' AND dated < v3Month;
|
||||||
|
DELETE bm
|
||||||
|
FROM buyMark bm
|
||||||
|
JOIN buy b ON b.id = bm.id
|
||||||
|
JOIN entry e ON e.id = b.entryFk
|
||||||
|
JOIN travel t ON t.id = e.travelFk
|
||||||
|
WHERE t.landed <= vDateShort;
|
||||||
|
DELETE FROM vn.buy WHERE created < vDateShort AND entryFk = 9200;
|
||||||
|
DELETE FROM vn.itemShelvingLog WHERE created < vDateShort;
|
||||||
|
DELETE FROM vn.stockBuyed WHERE creationDate < vDateShort;
|
||||||
|
DELETE FROM vn.itemCleanLog WHERE created < util.VN_NOW() - INTERVAL 1 YEAR;
|
||||||
|
DELETE FROM printQueue WHERE statusCode = 'printed' AND created < vDateShort;
|
||||||
|
|
||||||
|
-- Equipos duplicados
|
||||||
|
DELETE w.*
|
||||||
|
FROM workerTeam w
|
||||||
|
JOIN (SELECT id, team, workerFk, COUNT(*) - 1 as duplicated
|
||||||
|
FROM workerTeam
|
||||||
|
GROUP BY team,workerFk
|
||||||
|
HAVING duplicated
|
||||||
|
) d ON d.team = w.team AND d.workerFk = w.workerFk AND d.id != w.id;
|
||||||
|
|
||||||
|
DELETE sc
|
||||||
|
FROM saleComponent sc
|
||||||
|
JOIN sale s ON s.id= sc.saleFk
|
||||||
|
JOIN ticket t ON t.id= s.ticketFk
|
||||||
|
WHERE t.shipped < v18Month;
|
||||||
|
|
||||||
|
DELETE c
|
||||||
|
FROM vn.claim c
|
||||||
|
JOIN vn.claimState cs ON cs.id = c.claimStateFk
|
||||||
|
WHERE cs.description = "Anulado" AND
|
||||||
|
c.created < vDateShort;
|
||||||
|
DELETE
|
||||||
|
FROM vn.expeditionTruck
|
||||||
|
WHERE ETD < v3Month;
|
||||||
|
|
||||||
|
-- borrar travels sin entradas
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tmp.thermographToDelete;
|
||||||
|
CREATE TEMPORARY TABLE tmp.thermographToDelete
|
||||||
|
SELECT th.id,th.dmsFk
|
||||||
|
FROM vn.travel t
|
||||||
|
LEFT JOIN vn.entry e ON e.travelFk = t.id
|
||||||
|
JOIN vn.travelThermograph th ON th.travelFk = t.id
|
||||||
|
WHERE t.shipped < TIMESTAMPADD(MONTH, -3, util.VN_CURDATE()) AND e.travelFk IS NULL;
|
||||||
|
|
||||||
|
SELECT dt.id INTO vTrashId
|
||||||
|
FROM vn.dmsType dt
|
||||||
|
WHERE dt.code = 'trash';
|
||||||
|
|
||||||
|
UPDATE tmp.thermographToDelete th
|
||||||
|
JOIN vn.dms d ON d.id = th.dmsFk
|
||||||
|
SET d.dmsTypeFk = vTrashId;
|
||||||
|
|
||||||
|
DELETE th
|
||||||
|
FROM tmp.thermographToDelete tmp
|
||||||
|
JOIN vn.travelThermograph th ON th.id = tmp.id;
|
||||||
|
|
||||||
|
DELETE t
|
||||||
|
FROM vn.travel t
|
||||||
|
LEFT JOIN vn.entry e ON e.travelFk = t.id
|
||||||
|
WHERE t.shipped < TIMESTAMPADD(MONTH, -3, util.VN_CURDATE()) AND e.travelFk IS NULL;
|
||||||
|
|
||||||
|
UPDATE dms d
|
||||||
|
JOIN dmsType dt ON dt.id = d.dmsTypeFk
|
||||||
|
SET d.dmsTypeFk = vTrashId
|
||||||
|
WHERE created < TIMESTAMPADD(MONTH, -dt.monthToDelete, util.VN_CURDATE());
|
||||||
|
|
||||||
|
-- borrar entradas sin compras
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tmp.entryToDelete;
|
||||||
|
CREATE TEMPORARY TABLE tmp.entryToDelete
|
||||||
|
SELECT e.*
|
||||||
|
FROM vn.entry e
|
||||||
|
LEFT JOIN vn.buy b ON b.entryFk = e.id
|
||||||
|
JOIN vn.entryConfig ec ON e.id != ec.defaultEntry
|
||||||
|
WHERE e.dated < TIMESTAMPADD(MONTH, -3, util.VN_CURDATE()) AND b.entryFK IS NULL;
|
||||||
|
|
||||||
|
DELETE e
|
||||||
|
FROM vn.entry e
|
||||||
|
JOIN tmp.entryToDelete tmp ON tmp.id = e.id;
|
||||||
|
|
||||||
|
-- borrar de route registros menores a 4 años
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tmp.routeToDelete;
|
||||||
|
CREATE TEMPORARY TABLE tmp.routeToDelete
|
||||||
|
SELECT *
|
||||||
|
FROM vn.route r
|
||||||
|
WHERE created < TIMESTAMPADD(YEAR,-4,util.VN_CURDATE());
|
||||||
|
|
||||||
|
UPDATE tmp.routeToDelete tmp
|
||||||
|
JOIN vn.dms d ON d.id = tmp.gestdocFk
|
||||||
|
SET d.dmsTypeFk = vTrashId;
|
||||||
|
|
||||||
|
DELETE r
|
||||||
|
FROM tmp.routeToDelete tmp
|
||||||
|
JOIN vn.route r ON r.id = tmp.id;
|
||||||
|
|
||||||
|
-- borrar registros de dua y awb menores a 2 años
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tmp.duaToDelete;
|
||||||
|
CREATE TEMPORARY TABLE tmp.duaToDelete
|
||||||
|
SELECT *
|
||||||
|
FROM vn.dua
|
||||||
|
WHERE operated < TIMESTAMPADD(YEAR,-2,util.VN_CURDATE());
|
||||||
|
|
||||||
|
UPDATE tmp.duaToDelete tm
|
||||||
|
JOIN vn.dms d ON d.id = tm.gestdocFk
|
||||||
|
SET d.dmsTypeFk = vTrashId;
|
||||||
|
|
||||||
|
DELETE d
|
||||||
|
FROM tmp.duaToDelete tmp
|
||||||
|
JOIN vn.dua d ON d.id = tmp.id;
|
||||||
|
|
||||||
|
DELETE FROM vn.awb WHERE created < TIMESTAMPADD(YEAR,-2,util.VN_CURDATE());
|
||||||
|
|
||||||
|
-- Borra los registros de collection y ticketcollection
|
||||||
|
DELETE FROM vn.collection WHERE created < vDateShort;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tmp.thermographToDelete;
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tmp.entryToDelete;
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tmp.duaToDelete;
|
||||||
|
|
||||||
|
DELETE FROM travelLog WHERE creationDate < v3Month;
|
||||||
|
|
||||||
|
CALL shelving_clean;
|
||||||
|
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
|
@ -3,7 +3,3 @@ ALTER TABLE `vn`.`itemConfig` ADD CONSTRAINT itemConfig_FK FOREIGN KEY (defaultT
|
||||||
ALTER TABLE `vn`.`itemConfig` ADD validPriorities varchar(50) DEFAULT '[1,2,3]' NOT NULL;
|
ALTER TABLE `vn`.`itemConfig` ADD validPriorities varchar(50) DEFAULT '[1,2,3]' NOT NULL;
|
||||||
ALTER TABLE `vn`.`itemConfig` ADD defaultPriority INT DEFAULT 2 NOT NULL;
|
ALTER TABLE `vn`.`itemConfig` ADD defaultPriority INT DEFAULT 2 NOT NULL;
|
||||||
ALTER TABLE `vn`.`item` MODIFY COLUMN relevancy tinyint(1) DEFAULT 0 NOT NULL COMMENT 'La web ordena de forma descendiente por este campo para mostrar los artículos';
|
ALTER TABLE `vn`.`item` MODIFY COLUMN relevancy tinyint(1) DEFAULT 0 NOT NULL COMMENT 'La web ordena de forma descendiente por este campo para mostrar los artículos';
|
||||||
|
|
||||||
INSERT INTO `salix`.`ACL`
|
|
||||||
(model, property, accessType, permission, principalType, principalId)
|
|
||||||
VALUES('ItemConfig', '*', 'READ', 'ALLOW', 'ROLE', 'buyer');
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
ALTER TABLE `vn`.`itemConfig` ADD defaultTag INT DEFAULT 56 NOT NULL;
|
||||||
|
ALTER TABLE `vn`.`itemConfig` ADD CONSTRAINT itemConfig_FK FOREIGN KEY (defaultTag) REFERENCES vn.tag(id);
|
||||||
|
ALTER TABLE `vn`.`itemConfig` ADD validPriorities varchar(50) DEFAULT '[1,2,3]' NOT NULL;
|
||||||
|
ALTER TABLE `vn`.`itemConfig` ADD defaultPriority INT DEFAULT 2 NOT NULL;
|
||||||
|
ALTER TABLE `vn`.`item` MODIFY COLUMN relevancy tinyint(1) DEFAULT 0 NOT NULL COMMENT 'La web ordena de forma descendiente por este campo para mostrar los artículos';
|
|
@ -0,0 +1,30 @@
|
||||||
|
DROP FUNCTION IF EXISTS `vn`.`invoiceOut_getWeight`;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
$$
|
||||||
|
CREATE DEFINER=`root`@`localhost` FUNCTION `vn`.`invoiceOut_getWeight`(vInvoice VARCHAR(15)) RETURNS decimal(10,2)
|
||||||
|
READS SQL DATA
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Calcula el peso de una factura emitida
|
||||||
|
*
|
||||||
|
* @param vInvoice Id de la factura
|
||||||
|
* @return vTotalWeight peso de la factura
|
||||||
|
*/
|
||||||
|
DECLARE vTotalWeight DECIMAL(10,2);
|
||||||
|
|
||||||
|
SELECT SUM(CAST(IFNULL(i.stems, 1)
|
||||||
|
* s.quantity
|
||||||
|
* IF(ic.grams, ic.grams, IFNULL(i.weightByPiece, 0)) / 1000 AS DECIMAL(10,2)))
|
||||||
|
INTO vTotalWeight
|
||||||
|
FROM ticket t
|
||||||
|
JOIN sale s ON s.ticketFk = t.id
|
||||||
|
JOIN item i ON i.id = s.itemFk
|
||||||
|
JOIN itemCost ic ON ic.itemFk = i.id
|
||||||
|
AND ic.warehouseFk = t.warehouseFk
|
||||||
|
WHERE t.refFk = vInvoice
|
||||||
|
AND i.intrastatFk;
|
||||||
|
|
||||||
|
RETURN vTotalWeight;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,3 @@
|
||||||
|
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
|
||||||
|
VALUES
|
||||||
|
('Client', 'getClientOrSupplierReference', 'READ', 'ALLOW', 'ROLE', 'employee');
|
|
@ -0,0 +1,127 @@
|
||||||
|
DROP PROCEDURE IF EXISTS `vn`.`ticket_canAdvance`;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
$$
|
||||||
|
CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_canAdvance`(vDateFuture DATE, vDateToAdvance DATE, vWarehouseFk INT)
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Devuelve los tickets y la cantidad de lineas de venta que se pueden adelantar.
|
||||||
|
*
|
||||||
|
* @param vDateFuture Fecha de los tickets que se quieren adelantar.
|
||||||
|
* @param vDateToAdvance Fecha a cuando se quiere adelantar.
|
||||||
|
* @param vWarehouseFk Almacén
|
||||||
|
*/
|
||||||
|
|
||||||
|
DECLARE vDateInventory DATE;
|
||||||
|
|
||||||
|
SELECT inventoried INTO vDateInventory FROM config;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tmp.stock;
|
||||||
|
CREATE TEMPORARY TABLE tmp.stock
|
||||||
|
(itemFk INT PRIMARY KEY,
|
||||||
|
amount INT)
|
||||||
|
ENGINE = MEMORY;
|
||||||
|
|
||||||
|
INSERT INTO tmp.stock(itemFk, amount)
|
||||||
|
SELECT itemFk, SUM(quantity) amount FROM
|
||||||
|
(
|
||||||
|
SELECT itemFk, quantity
|
||||||
|
FROM itemTicketOut
|
||||||
|
WHERE shipped >= vDateInventory
|
||||||
|
AND shipped < vDateFuture
|
||||||
|
AND warehouseFk = vWarehouseFk
|
||||||
|
UNION ALL
|
||||||
|
SELECT itemFk, quantity
|
||||||
|
FROM itemEntryIn
|
||||||
|
WHERE landed >= vDateInventory
|
||||||
|
AND landed < vDateFuture
|
||||||
|
AND isVirtualStock = FALSE
|
||||||
|
AND warehouseInFk = vWarehouseFk
|
||||||
|
UNION ALL
|
||||||
|
SELECT itemFk, quantity
|
||||||
|
FROM itemEntryOut
|
||||||
|
WHERE shipped >= vDateInventory
|
||||||
|
AND shipped < vDateFuture
|
||||||
|
AND warehouseOutFk = vWarehouseFk
|
||||||
|
) t
|
||||||
|
GROUP BY itemFk HAVING amount != 0;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tmp.filter;
|
||||||
|
CREATE TEMPORARY TABLE tmp.filter
|
||||||
|
(INDEX (id))
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
origin.ticketFk futureId,
|
||||||
|
dest.ticketFk id,
|
||||||
|
dest.state,
|
||||||
|
origin.futureState,
|
||||||
|
origin.futureIpt,
|
||||||
|
dest.ipt,
|
||||||
|
origin.workerFk,
|
||||||
|
origin.futureLiters,
|
||||||
|
origin.futureLines,
|
||||||
|
dest.shipped,
|
||||||
|
origin.shipped futureShipped,
|
||||||
|
dest.totalWithVat,
|
||||||
|
origin.totalWithVat futureTotalWithVat,
|
||||||
|
dest.agency,
|
||||||
|
origin.futureAgency,
|
||||||
|
dest.lines,
|
||||||
|
dest.liters,
|
||||||
|
origin.futureLines - origin.hasStock AS notMovableLines,
|
||||||
|
(origin.futureLines = origin.hasStock) AS isFullMovable
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
s.ticketFk,
|
||||||
|
t.workerFk,
|
||||||
|
t.shipped,
|
||||||
|
t.totalWithVat,
|
||||||
|
st.name futureState,
|
||||||
|
t.addressFk,
|
||||||
|
am.name futureAgency,
|
||||||
|
count(s.id) futureLines,
|
||||||
|
GROUP_CONCAT(DISTINCT ipt.code ORDER BY ipt.code) futureIpt,
|
||||||
|
CAST(SUM(litros) AS DECIMAL(10,0)) futureLiters,
|
||||||
|
SUM((s.quantity <= IFNULL(st.amount,0))) hasStock
|
||||||
|
FROM ticket t
|
||||||
|
JOIN sale s ON s.ticketFk = t.id
|
||||||
|
JOIN saleVolume sv ON sv.saleFk = s.id
|
||||||
|
JOIN item i ON i.id = s.itemFk
|
||||||
|
JOIN ticketState ts ON ts.ticketFk = t.id
|
||||||
|
JOIN state st ON st.id = ts.stateFk
|
||||||
|
JOIN agencyMode am ON t.agencyModeFk = am.id
|
||||||
|
LEFT JOIN itemPackingType ipt ON ipt.code = i.itemPackingTypeFk
|
||||||
|
LEFT JOIN tmp.stock st ON st.itemFk = i.id
|
||||||
|
WHERE t.shipped BETWEEN vDateFuture AND util.dayend(vDateFuture)
|
||||||
|
AND t.warehouseFk = vWarehouseFk
|
||||||
|
GROUP BY t.id
|
||||||
|
) origin
|
||||||
|
JOIN (
|
||||||
|
SELECT
|
||||||
|
t.id ticketFk,
|
||||||
|
t.addressFk,
|
||||||
|
st.name state,
|
||||||
|
GROUP_CONCAT(DISTINCT ipt.code ORDER BY ipt.code) ipt,
|
||||||
|
t.shipped,
|
||||||
|
t.totalWithVat,
|
||||||
|
am.name agency,
|
||||||
|
CAST(SUM(litros) AS DECIMAL(10,0)) liters,
|
||||||
|
CAST(COUNT(*) AS DECIMAL(10,0)) `lines`
|
||||||
|
FROM ticket t
|
||||||
|
JOIN sale s ON s.ticketFk = t.id
|
||||||
|
JOIN saleVolume sv ON sv.saleFk = s.id
|
||||||
|
JOIN item i ON i.id = s.itemFk
|
||||||
|
JOIN ticketState ts ON ts.ticketFk = t.id
|
||||||
|
JOIN state st ON st.id = ts.stateFk
|
||||||
|
JOIN agencyMode am ON t.agencyModeFk = am.id
|
||||||
|
LEFT JOIN itemPackingType ipt ON ipt.code = i.itemPackingTypeFk
|
||||||
|
WHERE t.shipped BETWEEN vDateToAdvance AND util.dayend(vDateToAdvance)
|
||||||
|
AND t.warehouseFk = vWarehouseFk
|
||||||
|
AND st.order <= 5
|
||||||
|
GROUP BY t.id
|
||||||
|
) dest ON dest.addressFk = origin.addressFk
|
||||||
|
WHERE origin.hasStock != 0;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE tmp.stock;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,4 @@
|
||||||
|
ALTER TABLE `vn`.`itemConfig` ADD warehouseFk smallint(6) unsigned NULL;
|
||||||
|
UPDATE `vn`.`itemConfig`
|
||||||
|
SET warehouseFk=60
|
||||||
|
WHERE id=0;
|
|
@ -0,0 +1,3 @@
|
||||||
|
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
|
||||||
|
VALUES
|
||||||
|
('ItemConfig', '*', '*', 'ALLOW', 'ROLE', 'employee');
|
|
@ -717,7 +717,8 @@ INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeF
|
||||||
(28, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
|
(28, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
|
||||||
(29, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
|
(29, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
|
||||||
(30, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
|
(30, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
|
||||||
(31, 1, 8, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL + 2 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE());
|
(31, 1, 8, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL + 2 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE()),
|
||||||
|
(32, 1, 8, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL + 2 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE());
|
||||||
|
|
||||||
INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `description`)
|
INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `description`)
|
||||||
VALUES
|
VALUES
|
||||||
|
@ -1019,7 +1020,9 @@ INSERT INTO `vn`.`sale`(`id`, `itemFk`, `ticketFk`, `concept`, `quantity`, `pric
|
||||||
(34, 4, 28, 'Melee weapon heavy shield 1x0.5m', 20, 1.72, 0, 0, 0, util.VN_CURDATE()),
|
(34, 4, 28, 'Melee weapon heavy shield 1x0.5m', 20, 1.72, 0, 0, 0, util.VN_CURDATE()),
|
||||||
(35, 4, 29, 'Melee weapon heavy shield 1x0.5m', 20, 1.72, 0, 0, 0, util.VN_CURDATE()),
|
(35, 4, 29, 'Melee weapon heavy shield 1x0.5m', 20, 1.72, 0, 0, 0, util.VN_CURDATE()),
|
||||||
(36, 4, 30, 'Melee weapon heavy shield 1x0.5m', 20, 1.72, 0, 0, 0, util.VN_CURDATE()),
|
(36, 4, 30, 'Melee weapon heavy shield 1x0.5m', 20, 1.72, 0, 0, 0, util.VN_CURDATE()),
|
||||||
(37, 4, 31, 'Melee weapon heavy shield 1x0.5m', 20, 1.72, 0, 0, 0, util.VN_CURDATE());
|
(37, 4, 31, 'Melee weapon heavy shield 1x0.5m', 20, 1.72, 0, 0, 0, util.VN_CURDATE()),
|
||||||
|
(38, 2, 32, 'Melee weapon combat fist 15cm', 30, 7.07, 0, 0, 0, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH)),
|
||||||
|
(39, 1, 32, 'Ranged weapon longbow 2m', 2, 103.49, 0, 0, 0, util.VN_CURDATE());
|
||||||
|
|
||||||
INSERT INTO `vn`.`saleChecked`(`saleFk`, `isChecked`)
|
INSERT INTO `vn`.`saleChecked`(`saleFk`, `isChecked`)
|
||||||
VALUES
|
VALUES
|
||||||
|
@ -2744,9 +2747,9 @@ INSERT INTO `vn`.`collection` (`id`, `created`, `workerFk`, `stateFk`, `itemPack
|
||||||
VALUES
|
VALUES
|
||||||
(3, util.VN_NOW(), 1107, 5, NULL, 0, 0, 1, NULL, NULL);
|
(3, util.VN_NOW(), 1107, 5, NULL, 0, 0, 1, NULL, NULL);
|
||||||
|
|
||||||
INSERT INTO `vn`.`itemConfig` (`id`, `isItemTagTriggerDisabled`, `monthToDeactivate`, `wasteRecipients`, `validPriorities`, `defaultPriority`, `defaultTag`)
|
INSERT INTO `vn`.`itemConfig` (`id`, `isItemTagTriggerDisabled`, `monthToDeactivate`, `wasteRecipients`, `validPriorities`, `defaultPriority`, `defaultTag`, `warehouseFk`)
|
||||||
VALUES
|
VALUES
|
||||||
(0, 0, 24, '', '[1,2,3]', 2, 56);
|
(0, 0, 24, '', '[1,2,3]', 2, 56, 60);
|
||||||
|
|
||||||
INSERT INTO `vn`.`ticketCollection` (`ticketFk`, `collectionFk`, `created`, `level`, `wagon`, `smartTagFk`, `usedShelves`, `itemCount`, `liters`)
|
INSERT INTO `vn`.`ticketCollection` (`ticketFk`, `collectionFk`, `created`, `level`, `wagon`, `smartTagFk`, `usedShelves`, `itemCount`, `liters`)
|
||||||
VALUES
|
VALUES
|
||||||
|
|
|
@ -26347,6 +26347,7 @@ CREATE TABLE `entry` (
|
||||||
`buyerFk` int(10) unsigned DEFAULT NULL,
|
`buyerFk` int(10) unsigned DEFAULT NULL,
|
||||||
`typeFk` varchar(100) COLLATE utf8mb3_unicode_ci DEFAULT NULL COMMENT 'Tipo de entrada',
|
`typeFk` varchar(100) COLLATE utf8mb3_unicode_ci DEFAULT NULL COMMENT 'Tipo de entrada',
|
||||||
`reference` varchar(50) COLLATE utf8mb3_unicode_ci DEFAULT NULL COMMENT 'Referencia para eti',
|
`reference` varchar(50) COLLATE utf8mb3_unicode_ci DEFAULT NULL COMMENT 'Referencia para eti',
|
||||||
|
`ref` varchar(50) GENERATED ALWAYS AS (`invoiceNumber`) VIRTUAL COMMENT 'Columna virtual provisional para Salix',
|
||||||
PRIMARY KEY (`id`),
|
PRIMARY KEY (`id`),
|
||||||
KEY `Id_Proveedor` (`supplierFk`),
|
KEY `Id_Proveedor` (`supplierFk`),
|
||||||
KEY `Fecha` (`dated`),
|
KEY `Fecha` (`dated`),
|
||||||
|
@ -26361,7 +26362,8 @@ CREATE TABLE `entry` (
|
||||||
CONSTRAINT `entry_FK_1` FOREIGN KEY (`typeFk`) REFERENCES `entryType` (`code`) ON UPDATE CASCADE,
|
CONSTRAINT `entry_FK_1` FOREIGN KEY (`typeFk`) REFERENCES `entryType` (`code`) ON UPDATE CASCADE,
|
||||||
CONSTRAINT `entry_ibfk_1` FOREIGN KEY (`supplierFk`) REFERENCES `supplier` (`id`) ON UPDATE CASCADE,
|
CONSTRAINT `entry_ibfk_1` FOREIGN KEY (`supplierFk`) REFERENCES `supplier` (`id`) ON UPDATE CASCADE,
|
||||||
CONSTRAINT `entry_ibfk_6` FOREIGN KEY (`travelFk`) REFERENCES `travel` (`id`) ON UPDATE CASCADE,
|
CONSTRAINT `entry_ibfk_6` FOREIGN KEY (`travelFk`) REFERENCES `travel` (`id`) ON UPDATE CASCADE,
|
||||||
CONSTRAINT `entry_ibfk_7` FOREIGN KEY (`companyFk`) REFERENCES `company` (`id`) ON UPDATE CASCADE
|
CONSTRAINT `entry_ibfk_7` FOREIGN KEY (`companyFk`) REFERENCES `company` (`id`) ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT `entry_observationEditorFk` FOREIGN KEY (`observationEditorFk`) REFERENCES `account`.`user`(`id`) ON UPDATE CASCADE
|
||||||
) ENGINE=InnoDBDEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci COMMENT='InnoDB free: 88064 kB; (`Id_Proveedor`) REFER `vn2008/Provee';
|
) ENGINE=InnoDBDEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci COMMENT='InnoDB free: 88064 kB; (`Id_Proveedor`) REFER `vn2008/Provee';
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
|
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
|
||||||
|
|
|
@ -778,18 +778,16 @@ export default {
|
||||||
ipt: 'vn-autocomplete[label="Destination IPT"]',
|
ipt: 'vn-autocomplete[label="Destination IPT"]',
|
||||||
tableIpt: 'vn-autocomplete[name="ipt"]',
|
tableIpt: 'vn-autocomplete[name="ipt"]',
|
||||||
tableFutureIpt: 'vn-autocomplete[name="futureIpt"]',
|
tableFutureIpt: 'vn-autocomplete[name="futureIpt"]',
|
||||||
futureState: 'vn-check[label="Pending Origin"]',
|
isFullMovable: 'vn-check[ng-model="filter.isFullMovable"]',
|
||||||
state: 'vn-check[label="Pending Destination"]',
|
|
||||||
warehouseFk: 'vn-autocomplete[label="Warehouse"]',
|
warehouseFk: 'vn-autocomplete[label="Warehouse"]',
|
||||||
tableButtonSearch: 'vn-button[vn-tooltip="Search"]',
|
tableButtonSearch: 'vn-button[vn-tooltip="Search"]',
|
||||||
moveButton: 'vn-button[vn-tooltip="Advance tickets"]',
|
moveButton: 'vn-button[vn-tooltip="Advance tickets"]',
|
||||||
acceptButton: '.vn-confirm.shown button[response="accept"]',
|
acceptButton: '.vn-confirm.shown button[response="accept"]',
|
||||||
multiCheck: 'vn-multi-check',
|
firstCheck: 'tbody > tr:nth-child(1) > td > vn-check',
|
||||||
tableId: 'vn-textfield[name="id"]',
|
tableId: 'vn-textfield[name="id"]',
|
||||||
tableFutureId: 'vn-textfield[name="futureId"]',
|
tableFutureId: 'vn-textfield[name="futureId"]',
|
||||||
tableLiters: 'vn-textfield[name="liters"]',
|
tableLiters: 'vn-textfield[name="liters"]',
|
||||||
tableLines: 'vn-textfield[name="lines"]',
|
tableLines: 'vn-textfield[name="lines"]',
|
||||||
tableStock: 'vn-textfield[name="hasStock"]',
|
|
||||||
submit: 'vn-submit[label="Search"]',
|
submit: 'vn-submit[label="Search"]',
|
||||||
table: 'tbody > tr:not(.empty-rows)'
|
table: 'tbody > tr:not(.empty-rows)'
|
||||||
},
|
},
|
||||||
|
@ -1260,6 +1258,21 @@ export default {
|
||||||
importBuysButton: 'vn-entry-buy-import button[type="submit"]'
|
importBuysButton: 'vn-entry-buy-import button[type="submit"]'
|
||||||
},
|
},
|
||||||
entryLatestBuys: {
|
entryLatestBuys: {
|
||||||
|
table: 'tbody > tr:not(.empty-rows)',
|
||||||
|
chip: 'vn-chip > vn-icon',
|
||||||
|
generalSearchInput: 'vn-textfield[ng-model="$ctrl.filter.search"]',
|
||||||
|
firstReignIcon: 'vn-horizontal.item-category vn-one',
|
||||||
|
typeInput: 'vn-autocomplete[ng-model="$ctrl.filter.typeFk"]',
|
||||||
|
salesPersonInput: 'vn-autocomplete[ng-model="$ctrl.filter.salesPersonFk"]',
|
||||||
|
supplierInput: 'vn-autocomplete[ng-model="$ctrl.filter.supplierFk"]',
|
||||||
|
fromInput: 'vn-date-picker[ng-model="$ctrl.filter.from"]',
|
||||||
|
toInput: 'vn-date-picker[ng-model="$ctrl.filter.to"]',
|
||||||
|
activeCheck: 'vn-check[ng-model="$ctrl.filter.active"]',
|
||||||
|
floramondoCheck: 'vn-check[ng-model="$ctrl.filter.floramondo"]',
|
||||||
|
visibleCheck: 'vn-check[ng-model="$ctrl.filter.visible"]',
|
||||||
|
addTagButton: 'vn-icon-button[vn-tooltip="Add tag"]',
|
||||||
|
itemTagInput: 'vn-autocomplete[ng-model="itemTag.tagFk"]',
|
||||||
|
itemTagValueInput: 'vn-autocomplete[ng-model="itemTag.value"]',
|
||||||
firstBuy: 'vn-entry-latest-buys tbody > tr:nth-child(1)',
|
firstBuy: 'vn-entry-latest-buys tbody > tr:nth-child(1)',
|
||||||
allBuysCheckBox: 'vn-entry-latest-buys thead vn-check',
|
allBuysCheckBox: 'vn-entry-latest-buys thead vn-check',
|
||||||
secondBuyCheckBox: 'vn-entry-latest-buys tbody tr:nth-child(2) vn-check[ng-model="buy.checked"]',
|
secondBuyCheckBox: 'vn-entry-latest-buys tbody tr:nth-child(2) vn-check[ng-model="buy.checked"]',
|
||||||
|
|
|
@ -55,7 +55,7 @@ describe('Ticket Summary path', () => {
|
||||||
let result = await page
|
let result = await page
|
||||||
.waitToGetProperty(selectors.ticketSummary.firstSaleItemId, 'innerText');
|
.waitToGetProperty(selectors.ticketSummary.firstSaleItemId, 'innerText');
|
||||||
|
|
||||||
expect(result).toContain('000002');
|
expect(result).toContain('2');
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should click on the first sale ID to make the item descriptor visible`, async() => {
|
it(`should click on the first sale ID to make the item descriptor visible`, async() => {
|
||||||
|
|
|
@ -4,12 +4,17 @@ import getBrowser from '../../helpers/puppeteer';
|
||||||
describe('Ticket Advance path', () => {
|
describe('Ticket Advance path', () => {
|
||||||
let browser;
|
let browser;
|
||||||
let page;
|
let page;
|
||||||
|
const httpRequests = [];
|
||||||
|
|
||||||
beforeAll(async() => {
|
beforeAll(async() => {
|
||||||
browser = await getBrowser();
|
browser = await getBrowser();
|
||||||
page = browser.page;
|
page = browser.page;
|
||||||
await page.loginAndModule('employee', 'ticket');
|
await page.loginAndModule('employee', 'ticket');
|
||||||
await page.accessToSection('ticket.advance');
|
await page.accessToSection('ticket.advance');
|
||||||
|
page.on('request', req => {
|
||||||
|
if (req.url().includes(`Tickets/getTicketsAdvance`))
|
||||||
|
httpRequests.push(req.url());
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async() => {
|
afterAll(async() => {
|
||||||
|
@ -43,91 +48,74 @@ describe('Ticket Advance path', () => {
|
||||||
it('should search with the required data', async() => {
|
it('should search with the required data', async() => {
|
||||||
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
||||||
await page.waitToClick(selectors.ticketAdvance.submit);
|
await page.waitToClick(selectors.ticketAdvance.submit);
|
||||||
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
|
|
||||||
|
expect(httpRequests.length).toBeGreaterThan(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search with the origin IPT', async() => {
|
it('should search with the origin IPT', async() => {
|
||||||
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
|
||||||
await page.autocompleteSearch(selectors.ticketAdvance.ipt, 'Horizontal');
|
|
||||||
await page.waitToClick(selectors.ticketAdvance.submit);
|
|
||||||
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
|
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
|
||||||
await page.clearInput(selectors.ticketAdvance.ipt);
|
|
||||||
await page.waitToClick(selectors.ticketAdvance.submit);
|
|
||||||
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should search with the destination IPT', async() => {
|
|
||||||
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
||||||
await page.autocompleteSearch(selectors.ticketAdvance.futureIpt, 'Horizontal');
|
await page.autocompleteSearch(selectors.ticketAdvance.futureIpt, 'Horizontal');
|
||||||
await page.waitToClick(selectors.ticketAdvance.submit);
|
await page.waitToClick(selectors.ticketAdvance.submit);
|
||||||
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
|
|
||||||
|
const request = httpRequests.find(req => req.includes(('futureIpt=H')));
|
||||||
|
|
||||||
|
expect(request).toBeDefined();
|
||||||
|
|
||||||
|
httpRequests.splice(httpRequests.indexOf(request), 1);
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
||||||
await page.clearInput(selectors.ticketAdvance.futureIpt);
|
await page.clearInput(selectors.ticketAdvance.futureIpt);
|
||||||
await page.waitToClick(selectors.ticketAdvance.submit);
|
await page.waitToClick(selectors.ticketAdvance.submit);
|
||||||
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search with the origin pending state', async() => {
|
it('should search with the destination IPT', async() => {
|
||||||
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
||||||
await page.waitToClick(selectors.ticketAdvance.futureState);
|
await page.autocompleteSearch(selectors.ticketAdvance.ipt, 'Horizontal');
|
||||||
await page.waitToClick(selectors.ticketAdvance.submit);
|
await page.waitToClick(selectors.ticketAdvance.submit);
|
||||||
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
|
|
||||||
|
const request = httpRequests.find(req => req.includes(('ipt=H')));
|
||||||
|
|
||||||
|
expect(request).toBeDefined();
|
||||||
|
|
||||||
|
httpRequests.splice(httpRequests.indexOf(request), 1);
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
||||||
await page.waitToClick(selectors.ticketAdvance.futureState);
|
await page.clearInput(selectors.ticketAdvance.ipt);
|
||||||
await page.waitToClick(selectors.ticketAdvance.submit);
|
await page.waitToClick(selectors.ticketAdvance.submit);
|
||||||
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 0);
|
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
|
||||||
await page.waitToClick(selectors.ticketAdvance.futureState);
|
|
||||||
await page.waitToClick(selectors.ticketAdvance.submit);
|
|
||||||
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should search with the destination grouped state', async() => {
|
|
||||||
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
|
||||||
await page.waitToClick(selectors.ticketAdvance.state);
|
|
||||||
await page.waitToClick(selectors.ticketAdvance.submit);
|
|
||||||
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 0);
|
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
|
||||||
await page.waitToClick(selectors.ticketAdvance.state);
|
|
||||||
await page.waitToClick(selectors.ticketAdvance.submit);
|
|
||||||
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
|
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
|
||||||
await page.waitToClick(selectors.ticketAdvance.state);
|
|
||||||
await page.waitToClick(selectors.ticketAdvance.submit);
|
|
||||||
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search in smart-table with an IPT Origin', async() => {
|
it('should search in smart-table with an IPT Origin', async() => {
|
||||||
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
|
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
|
||||||
await page.autocompleteSearch(selectors.ticketAdvance.tableFutureIpt, 'Vertical');
|
await page.autocompleteSearch(selectors.ticketAdvance.tableFutureIpt, 'Vertical');
|
||||||
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
|
|
||||||
|
const request = httpRequests.find(req => req.includes(('futureIpt')));
|
||||||
|
|
||||||
|
expect(request).toBeDefined();
|
||||||
|
|
||||||
|
httpRequests.splice(httpRequests.indexOf(request), 1);
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
|
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
|
||||||
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
||||||
await page.waitToClick(selectors.ticketAdvance.submit);
|
await page.waitToClick(selectors.ticketAdvance.submit);
|
||||||
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should search in smart-table with an IPT Destination', async() => {
|
it('should search in smart-table with an IPT Destination', async() => {
|
||||||
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
|
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
|
||||||
await page.autocompleteSearch(selectors.ticketAdvance.tableIpt, 'Vertical');
|
await page.autocompleteSearch(selectors.ticketAdvance.tableIpt, 'Vertical');
|
||||||
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
|
|
||||||
|
const request = httpRequests.find(req => req.includes(('ipt')));
|
||||||
|
|
||||||
|
expect(request).toBeDefined();
|
||||||
|
|
||||||
|
httpRequests.splice(httpRequests.indexOf(request), 1);
|
||||||
|
|
||||||
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
|
await page.waitToClick(selectors.ticketAdvance.tableButtonSearch);
|
||||||
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
await page.waitToClick(selectors.ticketAdvance.openAdvancedSearchButton);
|
||||||
await page.waitToClick(selectors.ticketAdvance.submit);
|
await page.waitToClick(selectors.ticketAdvance.submit);
|
||||||
await page.waitForNumberOfElements(selectors.ticketAdvance.table, 1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should check the one ticket and move to the present', async() => {
|
it('should check the first ticket and move to the present', async() => {
|
||||||
await page.waitToClick(selectors.ticketAdvance.multiCheck);
|
await page.waitToClick(selectors.ticketAdvance.firstCheck);
|
||||||
await page.waitToClick(selectors.ticketAdvance.moveButton);
|
await page.waitToClick(selectors.ticketAdvance.moveButton);
|
||||||
await page.waitToClick(selectors.ticketAdvance.acceptButton);
|
await page.waitToClick(selectors.ticketAdvance.acceptButton);
|
||||||
const message = await page.waitForSnackbar();
|
const message = await page.waitForSnackbar();
|
||||||
|
|
|
@ -45,7 +45,7 @@ describe('Claim summary path', () => {
|
||||||
it('should display the claimed line(s)', async() => {
|
it('should display the claimed line(s)', async() => {
|
||||||
const result = await page.waitToGetProperty(selectors.claimSummary.firstSaleItemId, 'innerText');
|
const result = await page.waitToGetProperty(selectors.claimSummary.firstSaleItemId, 'innerText');
|
||||||
|
|
||||||
expect(result).toContain('000002');
|
expect(result).toContain('2');
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should click on the first sale ID making the item descriptor visible`, async() => {
|
it(`should click on the first sale ID making the item descriptor visible`, async() => {
|
||||||
|
|
|
@ -4,10 +4,15 @@ import getBrowser from '../../helpers/puppeteer';
|
||||||
describe('Entry lastest buys path', () => {
|
describe('Entry lastest buys path', () => {
|
||||||
let browser;
|
let browser;
|
||||||
let page;
|
let page;
|
||||||
|
const httpRequests = [];
|
||||||
|
|
||||||
beforeAll(async() => {
|
beforeAll(async() => {
|
||||||
browser = await getBrowser();
|
browser = await getBrowser();
|
||||||
page = browser.page;
|
page = browser.page;
|
||||||
|
page.on('request', req => {
|
||||||
|
if (req.url().includes(`Buys/latestBuysFilter`))
|
||||||
|
httpRequests.push(req.url());
|
||||||
|
});
|
||||||
await page.loginAndModule('buyer', 'entry');
|
await page.loginAndModule('buyer', 'entry');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -20,6 +25,87 @@ describe('Entry lastest buys path', () => {
|
||||||
await page.waitForSelector(selectors.entryLatestBuys.editBuysButton, {visible: false});
|
await page.waitForSelector(selectors.entryLatestBuys.editBuysButton, {visible: false});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should filter by name', async() => {
|
||||||
|
await page.write(selectors.entryLatestBuys.generalSearchInput, 'Melee');
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
await page.waitToClick(selectors.entryLatestBuys.chip);
|
||||||
|
|
||||||
|
expect(httpRequests.find(req => req.includes(('search=Melee')))).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter by reign and type', async() => {
|
||||||
|
await page.click(selectors.entryLatestBuys.firstReignIcon);
|
||||||
|
await page.autocompleteSearch(selectors.entryLatestBuys.typeInput, 'Alstroemeria');
|
||||||
|
await page.click(selectors.entryLatestBuys.chip);
|
||||||
|
|
||||||
|
expect(httpRequests.find(req => req.includes(('categoryFk')))).toBeDefined();
|
||||||
|
expect(httpRequests.find(req => req.includes(('typeFk')))).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter by from date', async() => {
|
||||||
|
await page.pickDate(selectors.entryLatestBuys.fromInput, new Date());
|
||||||
|
await page.waitToClick(selectors.entryLatestBuys.chip);
|
||||||
|
|
||||||
|
expect(httpRequests.find(req => req.includes(('from')))).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter by to date', async() => {
|
||||||
|
await page.pickDate(selectors.entryLatestBuys.toInput, new Date());
|
||||||
|
await page.waitToClick(selectors.entryLatestBuys.chip);
|
||||||
|
|
||||||
|
expect(httpRequests.find(req => req.includes(('to')))).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter by sales person', async() => {
|
||||||
|
await page.autocompleteSearch(selectors.entryLatestBuys.salesPersonInput, 'buyerNick');
|
||||||
|
await page.waitToClick(selectors.entryLatestBuys.chip);
|
||||||
|
|
||||||
|
expect(httpRequests.find(req => req.includes(('salesPersonFk')))).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter by supplier', async() => {
|
||||||
|
await page.autocompleteSearch(selectors.entryLatestBuys.supplierInput, 'Farmer King');
|
||||||
|
await page.waitToClick(selectors.entryLatestBuys.chip);
|
||||||
|
|
||||||
|
expect(httpRequests.find(req => req.includes(('supplierFk')))).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter by active', async() => {
|
||||||
|
await page.waitToClick(selectors.entryLatestBuys.activeCheck);
|
||||||
|
await page.waitToClick(selectors.entryLatestBuys.activeCheck);
|
||||||
|
await page.waitToClick(selectors.entryLatestBuys.chip);
|
||||||
|
|
||||||
|
expect(httpRequests.find(req => req.includes(('active=true')))).toBeDefined();
|
||||||
|
expect(httpRequests.find(req => req.includes(('active=false')))).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter by visible', async() => {
|
||||||
|
await page.waitToClick(selectors.entryLatestBuys.visibleCheck);
|
||||||
|
await page.waitToClick(selectors.entryLatestBuys.visibleCheck);
|
||||||
|
await page.waitToClick(selectors.entryLatestBuys.chip);
|
||||||
|
|
||||||
|
expect(httpRequests.find(req => req.includes(('visible=true')))).toBeDefined();
|
||||||
|
expect(httpRequests.find(req => req.includes(('visible=false')))).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter by floramondo', async() => {
|
||||||
|
await page.waitToClick(selectors.entryLatestBuys.floramondoCheck);
|
||||||
|
await page.waitToClick(selectors.entryLatestBuys.floramondoCheck);
|
||||||
|
await page.waitToClick(selectors.entryLatestBuys.chip);
|
||||||
|
|
||||||
|
expect(httpRequests.find(req => req.includes(('floramondo=true')))).toBeDefined();
|
||||||
|
expect(httpRequests.find(req => req.includes(('floramondo=false')))).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter by tag Color', async() => {
|
||||||
|
await page.waitToClick(selectors.entryLatestBuys.addTagButton);
|
||||||
|
await page.autocompleteSearch(selectors.entryLatestBuys.itemTagInput, 'Color');
|
||||||
|
await page.autocompleteSearch(selectors.entryLatestBuys.itemTagValueInput, 'Brown');
|
||||||
|
await page.waitToClick(selectors.entryLatestBuys.chip);
|
||||||
|
|
||||||
|
expect(httpRequests.find(req => req.includes(('tags')))).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
it('should select all lines but one and then check the edit buys button appears', async() => {
|
it('should select all lines but one and then check the edit buys button appears', async() => {
|
||||||
await page.waitToClick(selectors.entryLatestBuys.allBuysCheckBox);
|
await page.waitToClick(selectors.entryLatestBuys.allBuysCheckBox);
|
||||||
await page.waitToClick(selectors.entryLatestBuys.secondBuyCheckBox);
|
await page.waitToClick(selectors.entryLatestBuys.secondBuyCheckBox);
|
||||||
|
|
|
@ -82,8 +82,6 @@
|
||||||
}
|
}
|
||||||
&[type=time],
|
&[type=time],
|
||||||
&[type=date] {
|
&[type=date] {
|
||||||
clip-path: inset(0 20px 0 0);
|
|
||||||
|
|
||||||
&::-webkit-inner-spin-button,
|
&::-webkit-inner-spin-button,
|
||||||
&::-webkit-clear-button {
|
&::-webkit-clear-button {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
@ -3,5 +3,4 @@ import './ucwords';
|
||||||
import './dash-if-empty';
|
import './dash-if-empty';
|
||||||
import './percentage';
|
import './percentage';
|
||||||
import './currency';
|
import './currency';
|
||||||
import './zero-fill';
|
|
||||||
import './id';
|
import './id';
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
describe('ZeroFill filter', () => {
|
|
||||||
let zeroFillFilter;
|
|
||||||
|
|
||||||
beforeEach(ngModule('vnCore'));
|
|
||||||
|
|
||||||
beforeEach(inject(_zeroFillFilter_ => {
|
|
||||||
zeroFillFilter = _zeroFillFilter_;
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should return null for a input null', () => {
|
|
||||||
expect(zeroFillFilter(null, null)).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return a positive number pads a number with five zeros', () => {
|
|
||||||
expect(zeroFillFilter(1, 5)).toBe('00001');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return negative number pads a number with five zeros', () => {
|
|
||||||
expect(zeroFillFilter(-1, 5)).toBe('-00001');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return zero number with zero zeros', () => {
|
|
||||||
expect(zeroFillFilter(0, 1)).toBe('0');
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,17 +0,0 @@
|
||||||
import ngModule from '../module';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pads a number with zeros.
|
|
||||||
*
|
|
||||||
* @param {Number} input The number to pad
|
|
||||||
* @param {Number} padLength The resulting number of digits
|
|
||||||
* @return {String} The zero-filled number
|
|
||||||
*/
|
|
||||||
export default function zeroFill() {
|
|
||||||
return function(input, padLength) {
|
|
||||||
if (input == null) return input;
|
|
||||||
let sign = Math.sign(input) === -1 ? '-' : '';
|
|
||||||
return sign + new Array(padLength).concat([Math.abs(input)]).join('0').slice(-padLength);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
ngModule.filter('zeroFill', zeroFill);
|
|
|
@ -1,8 +1,8 @@
|
||||||
|
|
||||||
<vn-dialog
|
<vn-dialog
|
||||||
vn-id="instanceLog">
|
vn-id="instanceLog">
|
||||||
<tpl-body>
|
<tpl-body>
|
||||||
<vn-log
|
<vn-log
|
||||||
|
class="vn-instance-log"
|
||||||
url="{{$ctrl.url}}"
|
url="{{$ctrl.url}}"
|
||||||
origin-id="$ctrl.originId"
|
origin-id="$ctrl.originId"
|
||||||
changed-model="$ctrl.changedModel"
|
changed-model="$ctrl.changedModel"
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
.vn-dialog {
|
vn-log.vn-instance-log {
|
||||||
& > .window:not(:has(.empty-rows)) {
|
|
||||||
width:60%;
|
|
||||||
vn-log {
|
|
||||||
vn-card {
|
vn-card {
|
||||||
|
width: 900px;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
& > * {
|
& > * {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethodCtx('post', {
|
||||||
|
description: 'Returns the sent parameters',
|
||||||
|
returns: {
|
||||||
|
type: 'object',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/post`,
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.post = async ctx => {
|
||||||
|
return ctx.req.body;
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,4 +1,5 @@
|
||||||
|
|
||||||
module.exports = function(Self) {
|
module.exports = function(Self) {
|
||||||
require('../methods/application/status')(Self);
|
require('../methods/application/status')(Self);
|
||||||
|
require('../methods/application/post')(Self);
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,12 @@
|
||||||
"principalType": "ROLE",
|
"principalType": "ROLE",
|
||||||
"principalId": "$everyone",
|
"principalId": "$everyone",
|
||||||
"permission": "ALLOW"
|
"permission": "ALLOW"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"property": "post",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone",
|
||||||
|
"permission": "ALLOW"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ module.exports = () => {
|
||||||
if (!env || env === 'development')
|
if (!env || env === 'development')
|
||||||
return new Date(Date.UTC(2001, 0, 1, 11));
|
return new Date(Date.UTC(2001, 0, 1, 11));
|
||||||
|
|
||||||
return new Date(Date.UTC());
|
return new Date();
|
||||||
};
|
};
|
||||||
|
|
||||||
Date.vnNew = () => {
|
Date.vnNew = () => {
|
||||||
|
|
|
@ -101,7 +101,7 @@
|
||||||
<vn-span
|
<vn-span
|
||||||
ng-click="itemDescriptor.show($event, saleClaimed.itemFk)"
|
ng-click="itemDescriptor.show($event, saleClaimed.itemFk)"
|
||||||
class="link">
|
class="link">
|
||||||
{{::saleClaimed.itemFk | zeroFill:6}}
|
{{::saleClaimed.itemFk}}
|
||||||
</vn-span>
|
</vn-span>
|
||||||
</td>
|
</td>
|
||||||
<td number>
|
<td number>
|
||||||
|
|
|
@ -115,7 +115,7 @@
|
||||||
<span
|
<span
|
||||||
ng-click="itemDescriptor.show($event, saleClaimed.sale.itemFk, saleClaimed.sale.id)"
|
ng-click="itemDescriptor.show($event, saleClaimed.sale.itemFk, saleClaimed.sale.id)"
|
||||||
class="link">
|
class="link">
|
||||||
{{::saleClaimed.sale.itemFk | zeroFill:6}}
|
{{::saleClaimed.sale.itemFk}}
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td expand>{{::saleClaimed.sale.ticket.landed | date: 'dd/MM/yyyy'}}</vn-td>
|
<vn-td expand>{{::saleClaimed.sale.ticket.landed | date: 'dd/MM/yyyy'}}</vn-td>
|
||||||
|
@ -241,7 +241,7 @@
|
||||||
<span
|
<span
|
||||||
ng-click="itemDescriptor.show($event, action.sale.itemFk, action.sale.id)"
|
ng-click="itemDescriptor.show($event, action.sale.itemFk, action.sale.id)"
|
||||||
class="link">
|
class="link">
|
||||||
{{::action.sale.itemFk | zeroFill:6}}
|
{{::action.sale.itemFk}}
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td number>
|
<vn-td number>
|
||||||
|
|
|
@ -67,7 +67,7 @@ module.exports = function(Self) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
delete args.ctx; // Remove unwanted properties
|
delete args.ctx; // Remove unwanted properties
|
||||||
const newReceipt = await models.Receipt.create(args, myOptions);
|
|
||||||
const originalClient = await models.Client.findById(args.clientFk, null, myOptions);
|
const originalClient = await models.Client.findById(args.clientFk, null, myOptions);
|
||||||
const bank = await models.Bank.findById(args.bankFk, null, myOptions);
|
const bank = await models.Bank.findById(args.bankFk, null, myOptions);
|
||||||
const accountingType = await models.AccountingType.findById(bank.accountingTypeFk, null, myOptions);
|
const accountingType = await models.AccountingType.findById(bank.accountingTypeFk, null, myOptions);
|
||||||
|
@ -76,23 +76,8 @@ module.exports = function(Self) {
|
||||||
if (!args.compensationAccount)
|
if (!args.compensationAccount)
|
||||||
throw new UserError('Compensation account is empty');
|
throw new UserError('Compensation account is empty');
|
||||||
|
|
||||||
const supplierCompensation = await models.Supplier.findOne({
|
// Check compensation account exists
|
||||||
where: {
|
await models.Client.getClientOrSupplierReference(args.compensationAccount, myOptions);
|
||||||
account: args.compensationAccount
|
|
||||||
}
|
|
||||||
}, myOptions);
|
|
||||||
|
|
||||||
let clientCompensation = {};
|
|
||||||
|
|
||||||
if (!supplierCompensation) {
|
|
||||||
clientCompensation = await models.Client.findOne({
|
|
||||||
where: {
|
|
||||||
accountingAccount: args.compensationAccount
|
|
||||||
}
|
|
||||||
}, myOptions);
|
|
||||||
}
|
|
||||||
if (!supplierCompensation && !clientCompensation)
|
|
||||||
throw new UserError('Invalid account');
|
|
||||||
|
|
||||||
await Self.rawSql(
|
await Self.rawSql(
|
||||||
`CALL vn.ledger_doCompensation(?, ?, ?, ?, ?, ?, ?)`,
|
`CALL vn.ledger_doCompensation(?, ?, ?, ?, ?, ?, ?)`,
|
||||||
|
@ -151,7 +136,7 @@ module.exports = function(Self) {
|
||||||
myOptions
|
myOptions
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const newReceipt = await models.Receipt.create(args, myOptions);
|
||||||
if (tx) await tx.commit();
|
if (tx) await tx.commit();
|
||||||
|
|
||||||
return newReceipt;
|
return newReceipt;
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('getClientOrSupplierReference', {
|
||||||
|
description: 'Returns the reference of a compensation providing a bank account',
|
||||||
|
accessType: 'READ',
|
||||||
|
accepts: {
|
||||||
|
arg: 'bankAccount',
|
||||||
|
type: 'number',
|
||||||
|
required: true,
|
||||||
|
description: 'The bank account of a client or a supplier'
|
||||||
|
},
|
||||||
|
returns: {
|
||||||
|
type: 'string',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/getClientOrSupplierReference`,
|
||||||
|
verb: 'GET'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.getClientOrSupplierReference = async(bankAccount, options) => {
|
||||||
|
const models = Self.app.models;
|
||||||
|
const myOptions = {};
|
||||||
|
let reference = {};
|
||||||
|
|
||||||
|
if (typeof options == 'object')
|
||||||
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
const supplierCompensation = await models.Supplier.findOne({
|
||||||
|
where: {
|
||||||
|
account: bankAccount
|
||||||
|
}
|
||||||
|
}, myOptions);
|
||||||
|
|
||||||
|
reference.supplierId = supplierCompensation?.id;
|
||||||
|
reference.supplierName = supplierCompensation?.name;
|
||||||
|
|
||||||
|
let clientCompensation = {};
|
||||||
|
|
||||||
|
if (!supplierCompensation) {
|
||||||
|
clientCompensation = await models.Client.findOne({
|
||||||
|
where: {
|
||||||
|
accountingAccount: bankAccount
|
||||||
|
}
|
||||||
|
}, myOptions);
|
||||||
|
reference.clientId = clientCompensation?.id;
|
||||||
|
reference.clientName = clientCompensation?.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!supplierCompensation && !clientCompensation)
|
||||||
|
throw new UserError('Invalid account');
|
||||||
|
|
||||||
|
return reference;
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,76 @@
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
const base64url = require('base64url');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('confirm', {
|
||||||
|
description: 'Confirms electronic payment transaction',
|
||||||
|
accessType: 'WRITE',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'Ds_SignatureVersion',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
}, {
|
||||||
|
arg: 'Ds_MerchantParameters',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
}, {
|
||||||
|
arg: 'Ds_Signature',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: 'Boolean',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/confirm`,
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.confirm = async(signatureVersion, merchantParameters, signature) => {
|
||||||
|
const $ = Self.app.models;
|
||||||
|
|
||||||
|
const decodedParams = JSON.parse(
|
||||||
|
base64url.decode(merchantParameters, 'utf8'));
|
||||||
|
const params = {};
|
||||||
|
|
||||||
|
for (const param in decodedParams)
|
||||||
|
params[param] = decodeURIComponent(decodedParams[param]);
|
||||||
|
|
||||||
|
const orderId = params['Ds_Order'];
|
||||||
|
const merchantId = parseInt(params['Ds_MerchantCode']);
|
||||||
|
|
||||||
|
if (!orderId)
|
||||||
|
throw new UserError('Order id not found');
|
||||||
|
if (!merchantId)
|
||||||
|
throw new UserError('Mechant id not found');
|
||||||
|
|
||||||
|
const merchant = await $.TpvMerchant.findById(merchantId, {
|
||||||
|
fields: ['id', 'secretKey']
|
||||||
|
});
|
||||||
|
|
||||||
|
const base64hmac = Self.createSignature(
|
||||||
|
orderId,
|
||||||
|
merchant.secretKey,
|
||||||
|
merchantParameters
|
||||||
|
);
|
||||||
|
|
||||||
|
if (base64hmac !== base64url.toBase64(signature))
|
||||||
|
throw new UserError('Invalid signature');
|
||||||
|
|
||||||
|
await Self.rawSql(
|
||||||
|
'CALL hedera.tpvTransaction_confirm(?, ?, ?, ?, ?, ?)', [
|
||||||
|
params['Ds_Amount'],
|
||||||
|
orderId,
|
||||||
|
merchantId,
|
||||||
|
params['Ds_Currency'],
|
||||||
|
params['Ds_Response'],
|
||||||
|
params['Ds_ErrorCode']
|
||||||
|
]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,39 @@
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethodCtx('end', {
|
||||||
|
description: 'Ends electronic payment transaction',
|
||||||
|
accessType: 'WRITE',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'orderId',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
}, {
|
||||||
|
arg: 'status',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
http: {
|
||||||
|
path: `/end`,
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.end = async(ctx, orderId, status) => {
|
||||||
|
const userId = ctx.req.accessToken.userId;
|
||||||
|
const transaction = await Self.findById(orderId, {
|
||||||
|
fields: ['id', 'clientFk']
|
||||||
|
});
|
||||||
|
|
||||||
|
if (transaction?.clientFk != userId)
|
||||||
|
throw new UserError('Transaction not owned by user');
|
||||||
|
|
||||||
|
await Self.rawSql(
|
||||||
|
'CALL hedera.tpvTransaction_end(?, ?)', [
|
||||||
|
orderId,
|
||||||
|
status
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,85 @@
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethodCtx('start', {
|
||||||
|
description: 'Starts electronic payment transaction',
|
||||||
|
accessType: 'WRITE',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'amount',
|
||||||
|
type: 'Number',
|
||||||
|
required: true,
|
||||||
|
}, {
|
||||||
|
arg: 'companyId',
|
||||||
|
type: 'Number',
|
||||||
|
required: false,
|
||||||
|
}, {
|
||||||
|
arg: 'urlOk',
|
||||||
|
type: 'String',
|
||||||
|
required: false,
|
||||||
|
}, {
|
||||||
|
arg: 'urlKo',
|
||||||
|
type: 'String',
|
||||||
|
required: false,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: 'Object',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/start`,
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.start = async(ctx, amount, companyId, urlOk, urlKo) => {
|
||||||
|
const userId = ctx.req.accessToken.userId;
|
||||||
|
const [[row]] = await Self.rawSql(
|
||||||
|
'CALL hedera.tpvTransaction_start(?, ?, ?)', [
|
||||||
|
amount,
|
||||||
|
companyId,
|
||||||
|
userId
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!row)
|
||||||
|
throw new UserError('Transaction error');
|
||||||
|
|
||||||
|
const orderId = row.transactionId.padStart(12, '0');
|
||||||
|
const merchantUrl = row.merchantUrl ? row.merchantUrl : '';
|
||||||
|
urlOk = urlOk ? urlOk.replace('_transactionId_', orderId) : '';
|
||||||
|
urlKo = urlKo ? urlKo.replace('_transactionId_', orderId) : '';
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
'Ds_Merchant_Amount': amount,
|
||||||
|
'Ds_Merchant_Order': orderId,
|
||||||
|
'Ds_Merchant_MerchantCode': row.merchant,
|
||||||
|
'Ds_Merchant_Currency': row.currency,
|
||||||
|
'Ds_Merchant_TransactionType': row.transactionType,
|
||||||
|
'Ds_Merchant_Terminal': row.terminal,
|
||||||
|
'Ds_Merchant_MerchantURL': merchantUrl,
|
||||||
|
'Ds_Merchant_UrlOK': urlOk,
|
||||||
|
'Ds_Merchant_UrlKO': urlKo
|
||||||
|
};
|
||||||
|
for (const param in params)
|
||||||
|
params[param] = encodeURIComponent(params[param]);
|
||||||
|
|
||||||
|
const json = JSON.stringify(params);
|
||||||
|
const merchantParameters = Buffer.from(json).toString('base64');
|
||||||
|
|
||||||
|
const signature = Self.createSignature(
|
||||||
|
orderId,
|
||||||
|
row.secretKey,
|
||||||
|
merchantParameters
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: row.url,
|
||||||
|
postValues: {
|
||||||
|
'Ds_SignatureVersion': 'HMAC_SHA256_V1',
|
||||||
|
'Ds_MerchantParameters': merchantParameters,
|
||||||
|
'Ds_Signature': signature
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
|
@ -47,4 +47,5 @@ module.exports = Self => {
|
||||||
require('../methods/client/incotermsAuthorizationEmail')(Self);
|
require('../methods/client/incotermsAuthorizationEmail')(Self);
|
||||||
require('../methods/client/consumptionSendQueued')(Self);
|
require('../methods/client/consumptionSendQueued')(Self);
|
||||||
require('../methods/client/filter')(Self);
|
require('../methods/client/filter')(Self);
|
||||||
|
require('../methods/client/getClientOrSupplierReference')(Self);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
const crypto = require('crypto');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
require('../methods/tpv-transaction/confirm')(Self);
|
||||||
|
require('../methods/tpv-transaction/start')(Self);
|
||||||
|
require('../methods/tpv-transaction/end')(Self);
|
||||||
|
|
||||||
|
Self.createSignature = function(orderId, secretKey, merchantParameters) {
|
||||||
|
secretKey = Buffer.from(secretKey, 'base64');
|
||||||
|
const iv = Buffer.alloc(8, 0);
|
||||||
|
|
||||||
|
const cipher = crypto.createCipheriv('des-ede3-cbc', secretKey, iv);
|
||||||
|
cipher.setAutoPadding(false);
|
||||||
|
const orderKey = Buffer.concat([
|
||||||
|
cipher.update(zeroPad(orderId, 8)),
|
||||||
|
cipher.final()
|
||||||
|
]);
|
||||||
|
|
||||||
|
return crypto.createHmac('sha256', orderKey)
|
||||||
|
.update(merchantParameters)
|
||||||
|
.digest('base64');
|
||||||
|
};
|
||||||
|
|
||||||
|
function zeroPad(buf, blocksize) {
|
||||||
|
const buffer = typeof buf === 'string' ? Buffer.from(buf, 'utf8') : buf;
|
||||||
|
const pad = Buffer.alloc((blocksize - (buffer.length % blocksize)) % blocksize, 0);
|
||||||
|
return Buffer.concat([buffer, pad]);
|
||||||
|
}
|
||||||
|
};
|
|
@ -48,6 +48,14 @@
|
||||||
max="$ctrl.maxAmount">
|
max="$ctrl.maxAmount">
|
||||||
</vn-input-number>
|
</vn-input-number>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
|
<vn-vertical ng-show="$ctrl.bankSelection.accountingType.code == 'compensation'">
|
||||||
|
<h6 translate>Compensation</h6>
|
||||||
|
<vn-textfield
|
||||||
|
ng-model="$ctrl.receipt.compensationAccount"
|
||||||
|
label="Compensation Account"
|
||||||
|
on-change="$ctrl.accountShortToStandard(value)">
|
||||||
|
</vn-textfield>
|
||||||
|
</vn-vertical>
|
||||||
<vn-horizontal>
|
<vn-horizontal>
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
label="Reference"
|
label="Reference"
|
||||||
|
@ -71,14 +79,6 @@
|
||||||
</vn-input-number>
|
</vn-input-number>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
</vn-vertical>
|
</vn-vertical>
|
||||||
<vn-vertical ng-show="$ctrl.bankSelection.accountingType.code == 'compensation'">
|
|
||||||
<h6 translate>Compensation</h6>
|
|
||||||
<vn-textfield
|
|
||||||
ng-model="$ctrl.receipt.compensationAccount"
|
|
||||||
label="Compensation Account"
|
|
||||||
on-change="$ctrl.accountShortToStandard(value)">
|
|
||||||
</vn-textfield>
|
|
||||||
</vn-vertical>
|
|
||||||
<vn-horizontal ng-show="$ctrl.bankSelection.accountingType.code == 'cash'">
|
<vn-horizontal ng-show="$ctrl.bankSelection.accountingType.code == 'cash'">
|
||||||
<vn-check
|
<vn-check
|
||||||
label="View receipt"
|
label="View receipt"
|
||||||
|
|
|
@ -4,7 +4,6 @@ import Dialog from 'core/components/dialog';
|
||||||
class Controller extends Dialog {
|
class Controller extends Dialog {
|
||||||
constructor($element, $, $transclude, vnReport) {
|
constructor($element, $, $transclude, vnReport) {
|
||||||
super($element, $, $transclude);
|
super($element, $, $transclude);
|
||||||
this.viewReceipt = true;
|
|
||||||
this.vnReport = vnReport;
|
this.vnReport = vnReport;
|
||||||
this.receipt = {};
|
this.receipt = {};
|
||||||
}
|
}
|
||||||
|
@ -59,11 +58,18 @@ class Controller extends Dialog {
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
const accountingType = value.accountingType;
|
const accountingType = value.accountingType;
|
||||||
if (accountingType.receiptDescription != null) {
|
|
||||||
this.receipt.description = accountingType.receiptDescription;
|
this.receipt.description = [];
|
||||||
if (this.originalDescription) this.receipt.description += `, ${this.originalDescription}`;
|
this.viewReceipt = accountingType.code == 'cash';
|
||||||
} else if (this.originalDescription)
|
if (accountingType.code == 'compensation')
|
||||||
this.receipt.description = this.originalDescription;
|
this.receipt.description = '';
|
||||||
|
else {
|
||||||
|
if (accountingType.receiptDescription != null && accountingType.receiptDescription != '')
|
||||||
|
this.receipt.description.push(accountingType.receiptDescription);
|
||||||
|
if (this.originalDescription)
|
||||||
|
this.receipt.description.push(this.originalDescription);
|
||||||
|
this.receipt.description.join(', ');
|
||||||
|
}
|
||||||
this.maxAmount = accountingType && accountingType.maxAmount;
|
this.maxAmount = accountingType && accountingType.maxAmount;
|
||||||
|
|
||||||
this.receipt.payed = Date.vnNew();
|
this.receipt.payed = Date.vnNew();
|
||||||
|
@ -109,7 +115,25 @@ class Controller extends Dialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
accountShortToStandard(value) {
|
accountShortToStandard(value) {
|
||||||
|
if (value) {
|
||||||
this.receipt.compensationAccount = value.replace('.', '0'.repeat(11 - value.length));
|
this.receipt.compensationAccount = value.replace('.', '0'.repeat(11 - value.length));
|
||||||
|
const params = {bankAccount: this.receipt.compensationAccount};
|
||||||
|
this.$http.get(`Clients/getClientOrSupplierReference`, {params})
|
||||||
|
.then(res => {
|
||||||
|
if (res.data.clientId) {
|
||||||
|
this.receipt.description = this.$t('Client Compensation Reference', {
|
||||||
|
clientId: res.data.clientId,
|
||||||
|
clientName: res.data.clientName
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.receipt.description = this.$t('Supplier Compensation Reference', {
|
||||||
|
supplierId: res.data.supplierId,
|
||||||
|
supplierName: res.data.supplierName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else
|
||||||
|
this.receipt.description = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
getAmountPaid() {
|
getAmountPaid() {
|
||||||
|
|
|
@ -38,7 +38,7 @@ describe('Client', () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(controller.receipt.description).toEqual('Cash, Albaran: 1, 2');
|
expect(controller.receipt.description.join(',')).toEqual('Cash,Albaran: 1, 2');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -75,7 +75,6 @@ describe('Client', () => {
|
||||||
jest.spyOn(controller.vnReport, 'show');
|
jest.spyOn(controller.vnReport, 'show');
|
||||||
|
|
||||||
controller.$params = {id: 1101};
|
controller.$params = {id: 1101};
|
||||||
controller.viewReceipt = false;
|
|
||||||
|
|
||||||
$httpBackend.expect('POST', `Clients/1101/createReceipt`).respond({id: 1});
|
$httpBackend.expect('POST', `Clients/1101/createReceipt`).respond({id: 1});
|
||||||
controller.responseHandler('accept');
|
controller.responseHandler('accept');
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
View receipt: Ver recibo
|
View receipt: Ver recibo
|
||||||
Amount exceeded: Según ley contra el fraude no se puede recibir cobros por importe igual o superior a {{maxAmount}}
|
Amount exceeded: Según ley contra el fraude no se puede recibir cobros por importe igual o superior a {{maxAmount}}
|
||||||
|
Client Compensation Reference: "({{clientId}}) Ntro Cliente: {{clientName}}"
|
||||||
|
Supplier Compensation Reference: "({{supplierId}}) Ntro Proveedor: {{supplierName}}"
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
const LoopBackContext = require('loopback-context');
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
require('../methods/entry/filter')(Self);
|
require('../methods/entry/filter')(Self);
|
||||||
require('../methods/entry/getEntry')(Self);
|
require('../methods/entry/getEntry')(Self);
|
||||||
|
@ -7,4 +8,41 @@ module.exports = Self => {
|
||||||
require('../methods/entry/importBuysPreview')(Self);
|
require('../methods/entry/importBuysPreview')(Self);
|
||||||
require('../methods/entry/lastItemBuys')(Self);
|
require('../methods/entry/lastItemBuys')(Self);
|
||||||
require('../methods/entry/entryOrderPdf')(Self);
|
require('../methods/entry/entryOrderPdf')(Self);
|
||||||
|
|
||||||
|
Self.observe('before save', async function(ctx, options) {
|
||||||
|
if (ctx.isNewInstance) return;
|
||||||
|
|
||||||
|
const changes = ctx.data || ctx.instance;
|
||||||
|
const orgData = ctx.currentInstance;
|
||||||
|
|
||||||
|
const observation = changes.observation || orgData.observation;
|
||||||
|
const hasChanges = orgData && changes;
|
||||||
|
const observationChanged = hasChanges
|
||||||
|
&& orgData.observation != observation;
|
||||||
|
|
||||||
|
if (observationChanged) {
|
||||||
|
let tx;
|
||||||
|
const myOptions = {};
|
||||||
|
|
||||||
|
if (typeof options == 'object')
|
||||||
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
if (!myOptions.transaction) {
|
||||||
|
tx = await Self.beginTransaction({});
|
||||||
|
myOptions.transaction = tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const loopbackContext = LoopBackContext.getCurrentContext();
|
||||||
|
const userId = loopbackContext.active.accessToken.userId;
|
||||||
|
const id = changes.id || orgData.id;
|
||||||
|
const entry = await Self.app.models.Entry.findById(id, null, myOptions);
|
||||||
|
await entry.updateAttribute('observationEditorFk', userId, myOptions);
|
||||||
|
if (tx) await tx.commit();
|
||||||
|
} catch (e) {
|
||||||
|
if (tx) await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -77,6 +77,9 @@
|
||||||
"companyFk": {
|
"companyFk": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"required": true
|
"required": true
|
||||||
|
},
|
||||||
|
"observationEditorFk": {
|
||||||
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"relations": {
|
"relations": {
|
||||||
|
@ -99,6 +102,11 @@
|
||||||
"type": "belongsTo",
|
"type": "belongsTo",
|
||||||
"model": "Currency",
|
"model": "Currency",
|
||||||
"foreignKey": "currencyFk"
|
"foreignKey": "currencyFk"
|
||||||
|
},
|
||||||
|
"observationEditor": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "Account",
|
||||||
|
"foreignKey": "observationEditorFk"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@
|
||||||
ng-if="buy.id"
|
ng-if="buy.id"
|
||||||
ng-click="itemDescriptor.show($event, buy.item.id)"
|
ng-click="itemDescriptor.show($event, buy.item.id)"
|
||||||
class="link">
|
class="link">
|
||||||
{{::buy.item.id | zeroFill:6}}
|
{{::buy.item.id}}
|
||||||
</span>
|
</span>
|
||||||
<vn-autocomplete ng-if="!buy.id" class="dense"
|
<vn-autocomplete ng-if="!buy.id" class="dense"
|
||||||
vn-focus
|
vn-focus
|
||||||
|
|
|
@ -1,121 +1,137 @@
|
||||||
<mg-ajax path="Tags" options="mgIndex as tags"></mg-ajax>
|
<vn-crud-model url="Tags" fields="['id','name', 'isFree']" data="$ctrl.tags" auto-load="true">
|
||||||
<div class="search-panel">
|
<vn-crud-model url="ItemCategories" data="$ctrl.categories" auto-load="true"></vn-crud-model>
|
||||||
<form ng-submit="$ctrl.onSearch()">
|
<vn-side-menu side="right">
|
||||||
<vn-horizontal>
|
<vn-horizontal class="input">
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
label="General search"
|
label="General search"
|
||||||
ng-model="filter.search"
|
ng-model="$ctrl.filter.search"
|
||||||
info="Search items by id, name or barcode"
|
info="Search items by id, name or barcode"
|
||||||
vn-focus>
|
vn-focus
|
||||||
|
ng-keydown="$ctrl.onKeyPress($event)">
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
<vn-horizontal>
|
<vn-horizontal class="item-category">
|
||||||
<vn-autocomplete
|
<vn-autocomplete
|
||||||
vn-focus
|
vn-id="category"
|
||||||
url="ItemCategories"
|
data="$ctrl.categories"
|
||||||
label="Category"
|
ng-model="$ctrl.filter.categoryFk"
|
||||||
show-field="name"
|
show-field="name"
|
||||||
value-field="id"
|
value-field="id"
|
||||||
ng-model="filter.categoryFk">
|
label="Category">
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
|
<vn-one ng-repeat="category in $ctrl.categories">
|
||||||
|
<vn-icon
|
||||||
|
ng-class="{'active': $ctrl.filter.categoryFk == category.id}"
|
||||||
|
icon="{{::category.icon}}"
|
||||||
|
vn-tooltip="{{::category.name}}"
|
||||||
|
ng-click="$ctrl.changeCategory(category.id)">
|
||||||
|
</vn-icon>
|
||||||
|
</vn-one>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-vertical class="input">
|
||||||
<vn-autocomplete
|
<vn-autocomplete
|
||||||
|
vn-id="type"
|
||||||
|
disabled="!$ctrl.filter.categoryFk"
|
||||||
url="ItemTypes"
|
url="ItemTypes"
|
||||||
label="Type"
|
label="Type"
|
||||||
where="{categoryFk: filter.categoryFk}"
|
where="{categoryFk: $ctrl.filter.categoryFk}"
|
||||||
show-field="name"
|
show-field="name"
|
||||||
value-field="id"
|
value-field="id"
|
||||||
ng-model="filter.typeFk"
|
ng-model="$ctrl.filter.typeFk"
|
||||||
fields="['categoryFk']"
|
fields="['categoryFk']"
|
||||||
include="'category'">
|
include="'category'"
|
||||||
|
on-change="$ctrl.addFilters()">
|
||||||
<tpl-item>
|
<tpl-item>
|
||||||
<div>{{name}}</div>
|
<div>{{name}}</div>
|
||||||
<div class="text-caption text-secondary">
|
<div class="text-caption text-secondary">
|
||||||
{{category.name}}
|
{{category.name}}
|
||||||
</div>
|
</div> </tpl-item
|
||||||
</tpl-item>>
|
>>
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
</vn-horizontal>
|
</vn-vertical>
|
||||||
<vn-horizontal>
|
<vn-horizontal class="input horizontal">
|
||||||
<vn-autocomplete
|
<vn-autocomplete
|
||||||
|
vn-id="salesPerson"
|
||||||
disabled="false"
|
disabled="false"
|
||||||
ng-model="filter.salesPersonFk"
|
ng-model="$ctrl.filter.salesPersonFk"
|
||||||
url="Workers/activeWithRole"
|
url="Workers/activeWithRole"
|
||||||
show-field="nickname"
|
show-field="nickname"
|
||||||
search-function="{firstName: $search}"
|
search-function="{firstName: $search}"
|
||||||
value-field="id"
|
value-field="id"
|
||||||
where="{role: {inq: ['logistic', 'buyer']}}"
|
where="{role: {inq: ['logistic', 'buyer']}}"
|
||||||
label="Buyer">
|
label="Buyer"
|
||||||
|
on-change="$ctrl.addFilters()">
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
<vn-autocomplete
|
<vn-autocomplete
|
||||||
vn-one
|
vn-id="supplier"
|
||||||
label="Supplier"
|
label="Supplier"
|
||||||
ng-model="filter.supplierFk"
|
ng-model="$ctrl.filter.supplierFk"
|
||||||
url="Suppliers"
|
url="Suppliers"
|
||||||
fields="['name','nickname']"
|
fields="['name','nickname']"
|
||||||
search-function="{or: [{nickname: {like: '%'+ $search +'%'}}, {name: {like: '%'+ $search +'%'}}]}"
|
search-function="{or: [{nickname: {like: '%'+ $search +'%'}}, {name: {like: '%'+ $search +'%'}}]}"
|
||||||
show-field="name"
|
show-field="name"
|
||||||
value-field="id">
|
value-field="id"
|
||||||
|
on-change="$ctrl.addFilters()">
|
||||||
<tpl-item>{{name}}: {{nickname}}</tpl-item>
|
<tpl-item>{{name}}: {{nickname}}</tpl-item>
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
<vn-horizontal>
|
<vn-vertical class="input">
|
||||||
<vn-date-picker
|
<vn-date-picker
|
||||||
vn-one
|
|
||||||
label="From"
|
label="From"
|
||||||
ng-model="filter.from">
|
ng-model="$ctrl.filter.from"
|
||||||
|
on-change="$ctrl.addFilters()">
|
||||||
</vn-date-picker>
|
</vn-date-picker>
|
||||||
<vn-date-picker
|
<vn-date-picker
|
||||||
vn-one
|
|
||||||
label="To"
|
label="To"
|
||||||
ng-model="filter.to">
|
ng-model="$ctrl.filter.to"
|
||||||
|
on-change="$ctrl.addFilters()">
|
||||||
</vn-date-picker>
|
</vn-date-picker>
|
||||||
</vn-horizontal>
|
</vn-vertical>
|
||||||
<vn-horizontal>
|
<vn-horizontal class="checks">
|
||||||
<vn-check
|
<vn-check
|
||||||
label="Is active"
|
label="Is active"
|
||||||
ng-model="filter.active"
|
ng-model="$ctrl.filter.active"
|
||||||
triple-state="true">
|
triple-state="true"
|
||||||
|
ng-click="$ctrl.addFilters()">
|
||||||
</vn-check>
|
</vn-check>
|
||||||
<vn-check
|
<vn-check
|
||||||
label="Is visible"
|
label="Is visible"
|
||||||
ng-model="filter.visible"
|
ng-model="$ctrl.filter.visible"
|
||||||
triple-state="true">
|
triple-state="true"
|
||||||
|
ng-click="$ctrl.addFilters()">
|
||||||
</vn-check>
|
</vn-check>
|
||||||
<vn-check
|
<vn-check
|
||||||
label="Is floramondo"
|
label="Is floramondo"
|
||||||
ng-model="filter.floramondo"
|
ng-model="$ctrl.filter.floramondo"
|
||||||
triple-state="true">
|
triple-state="true"
|
||||||
|
ng-click="$ctrl.addFilters()">
|
||||||
</vn-check>
|
</vn-check>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
<vn-horizontal class="vn-pt-sm">
|
<vn-horizontal class="tags">
|
||||||
<vn-one class="text-subtitle1" translate>
|
<vn-one class="text-subtitle1" translate> Tags </vn-one>
|
||||||
Tags
|
|
||||||
</vn-one>
|
|
||||||
<vn-icon-button
|
<vn-icon-button
|
||||||
vn-none
|
vn-none
|
||||||
vn-bind="+"
|
|
||||||
vn-tooltip="Add tag"
|
vn-tooltip="Add tag"
|
||||||
icon="add_circle"
|
icon="add_circle"
|
||||||
ng-click="filter.tags.push({})">
|
ng-click="$ctrl.filter.tags.push({})">
|
||||||
</vn-icon-button>
|
</vn-icon-button>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
<vn-horizontal ng-repeat="itemTag in filter.tags">
|
<vn-horizontal class="tags horizontal" ng-repeat="itemTag in $ctrl.filter.tags">
|
||||||
<vn-autocomplete
|
<vn-autocomplete
|
||||||
vn-id="tag"
|
vn-id="tag"
|
||||||
|
data="$ctrl.tags"
|
||||||
ng-model="itemTag.tagFk"
|
ng-model="itemTag.tagFk"
|
||||||
data="tags.model"
|
|
||||||
show-field="name"
|
show-field="name"
|
||||||
label="Tag"
|
label="Tag"
|
||||||
on-change="itemTag.value = null">
|
on-change="itemTag.value = null">
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
ng-show="tag.selection.isFree || tag.selection.isFree == undefined"
|
ng-show="tag.selection.isFree || tag.selection.isFree == undefined"
|
||||||
vn-id="text"
|
|
||||||
label="Value"
|
label="Value"
|
||||||
ng-model="itemTag.value">
|
ng-model="itemTag.value"
|
||||||
|
ng-keydown="$ctrl.onKeyPress($event)">
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
<vn-autocomplete
|
<vn-autocomplete
|
||||||
vn-one
|
|
||||||
ng-show="tag.selection.isFree === false"
|
ng-show="tag.selection.isFree === false"
|
||||||
url="{{'Tags/' + itemTag.tagFk + '/filterValue'}}"
|
url="{{'Tags/' + itemTag.tagFk + '/filterValue'}}"
|
||||||
search-function="{value: $search}"
|
search-function="{value: $search}"
|
||||||
|
@ -123,76 +139,105 @@
|
||||||
ng-model="itemTag.value"
|
ng-model="itemTag.value"
|
||||||
show-field="value"
|
show-field="value"
|
||||||
value-field="value"
|
value-field="value"
|
||||||
rule>
|
on-change="$ctrl.addFilters()">
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
<vn-icon-button
|
<vn-icon-button
|
||||||
vn-none
|
vn-none
|
||||||
vn-tooltip="Remove tag"
|
vn-tooltip="Remove tag"
|
||||||
icon="delete"
|
icon="delete"
|
||||||
ng-click="filter.tags.splice($index, 1)"
|
ng-click="$ctrl.removeTag(itemTag)">
|
||||||
tabindex="-1">
|
|
||||||
</vn-icon-button>
|
</vn-icon-button>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
<vn-horizontal class="vn-pt-sm">
|
<div class="chips">
|
||||||
<vn-one class="text-subtitle1" translate>
|
<vn-chip
|
||||||
More fields
|
ng-if="$ctrl.filter.search"
|
||||||
</vn-one>
|
removable="true"
|
||||||
<vn-icon-button
|
vn-tooltip="Item id/name"
|
||||||
vn-none
|
on-remove="$ctrl.removeItemFilter('search')"
|
||||||
vn-bind="+"
|
class="colored">
|
||||||
vn-tooltip="Add field"
|
<span>Id/Name: {{$ctrl.filter.search}}</span>
|
||||||
icon="add_circle"
|
</vn-chip>
|
||||||
ng-click="$ctrl.fieldFilters.push({})">
|
<vn-chip
|
||||||
</vn-icon-button>
|
ng-if="category.selection"
|
||||||
</vn-horizontal>
|
removable="true"
|
||||||
<vn-horizontal ng-repeat="fieldFilter in $ctrl.fieldFilters">
|
vn-tooltip="Category"
|
||||||
<vn-autocomplete
|
on-remove="$ctrl.removeItemFilter('categoryFk')"
|
||||||
label="Field"
|
class="colored">
|
||||||
ng-model="fieldFilter.name"
|
<span>{{category.selection.name}}</span>
|
||||||
data="$ctrl.moreFields"
|
</vn-chip>
|
||||||
value-field="name"
|
<vn-chip
|
||||||
show-field="label"
|
ng-if="type.selection"
|
||||||
show-filter="false"
|
removable="true"
|
||||||
translate-fields="['label']"
|
vn-tooltip="Type"
|
||||||
selection="info"
|
on-remove="$ctrl.removeItemFilter('typeFk')"
|
||||||
on-change="fieldFilter.value = null">
|
class="colored">
|
||||||
</vn-autocomplete>
|
<span>{{type.selection.name}}</span>
|
||||||
<vn-one ng-switch="info.type">
|
</vn-chip>
|
||||||
<div ng-switch-when="Number">
|
<vn-chip
|
||||||
<vn-input-number
|
ng-if="salesPerson.selection"
|
||||||
label="Value"
|
removable="true"
|
||||||
ng-model="fieldFilter.value">
|
vn-tooltip="Sales person"
|
||||||
</vn-input-number>
|
on-remove="$ctrl.removeItemFilter('salesPersonFk')"
|
||||||
</div>
|
class="colored">
|
||||||
<div ng-switch-when="Boolean">
|
<span>Sales person: {{salesPerson.selection.nickname}}</span>
|
||||||
<vn-check
|
</vn-chip>
|
||||||
label="Value"
|
<vn-chip
|
||||||
ng-model="fieldFilter.value">
|
ng-if="supplier.selection"
|
||||||
</vn-check>
|
removable="true"
|
||||||
</div>
|
vn-tooltip="Supplier"
|
||||||
<div ng-switch-when="Date">
|
on-remove="$ctrl.removeItemFilter('supplierFk')"
|
||||||
<vn-date-picker
|
class="colored">
|
||||||
label="Value"
|
<span>Supplier: {{supplier.selection.name}}</span>
|
||||||
ng-model="fieldFilter.value">
|
</vn-chip>
|
||||||
</vn-date-picker>
|
<vn-chip
|
||||||
</div>
|
ng-if="$ctrl.filter.from"
|
||||||
<div ng-switch-default>
|
removable="true"
|
||||||
<vn-textfield
|
vn-tooltip="From date"
|
||||||
label="Value"
|
on-remove="$ctrl.removeItemFilter('from')"
|
||||||
ng-model="fieldFilter.value">
|
class="colored">
|
||||||
</vn-textfield>
|
<span>From: {{$ctrl.filter.from | date:'dd/MM/yyyy'}}</span>
|
||||||
</div>
|
</vn-chip>
|
||||||
</vn-one>
|
<vn-chip
|
||||||
<vn-icon-button
|
ng-if="$ctrl.filter.to"
|
||||||
vn-none
|
removable="true"
|
||||||
vn-tooltip="Remove field"
|
vn-tooltip="To date"
|
||||||
icon="delete"
|
on-remove="$ctrl.removeItemFilter('to')"
|
||||||
ng-click="$ctrl.removeField($index, fieldFilter.name)"
|
class="colored">
|
||||||
tabindex="-1">
|
<span>To: {{$ctrl.filter.to | date:'dd/MM/yyyy'}}</span>
|
||||||
</vn-icon-button>
|
</vn-chip>
|
||||||
</vn-horizontal>
|
<vn-chip
|
||||||
<vn-horizontal class="vn-mt-lg">
|
ng-if="$ctrl.filter.active != null"
|
||||||
<vn-submit label="Search"></vn-submit>
|
removable="true"
|
||||||
</vn-horizontal>
|
vn-tooltip="Active"
|
||||||
</form>
|
on-remove="$ctrl.removeItemFilter('active')"
|
||||||
|
class="colored">
|
||||||
|
<span>Active: {{$ctrl.filter.active ? '✓' : '✗'}}</span>
|
||||||
|
</vn-chip>
|
||||||
|
<vn-chip
|
||||||
|
ng-if="$ctrl.filter.floramondo != null"
|
||||||
|
removable="true"
|
||||||
|
vn-tooltip="Floramondo"
|
||||||
|
on-remove="$ctrl.removeItemFilter('floramondo')"
|
||||||
|
class="colored">
|
||||||
|
<span>Floramondo: {{$ctrl.filter.floramondo ? '✓' : '✗'}}</span>
|
||||||
|
</vn-chip>
|
||||||
|
<vn-chip
|
||||||
|
ng-if="$ctrl.filter.visible != null"
|
||||||
|
removable="true"
|
||||||
|
vn-tooltip="Visible"
|
||||||
|
on-remove="$ctrl.removeItemFilter('visible')"
|
||||||
|
class="colored">
|
||||||
|
<span>Visible: {{$ctrl.filter.visible ? '✓' : '✗'}}</span>
|
||||||
|
</vn-chip>
|
||||||
|
<vn-chip
|
||||||
|
ng-repeat="chipTag in $ctrl.filter.tags"
|
||||||
|
removable="true"
|
||||||
|
vn-tooltip="Tag"
|
||||||
|
on-remove="$ctrl.removeTag(chipTag)"
|
||||||
|
class="colored"
|
||||||
|
ng-if="chipTag.value">
|
||||||
|
<span>{{$ctrl.showTagInfo(chipTag)}}</span>
|
||||||
|
</vn-chip>
|
||||||
|
</vn-chip>
|
||||||
</div>
|
</div>
|
||||||
|
</vn-side-menu>
|
||||||
|
|
|
@ -1,67 +1,61 @@
|
||||||
import ngModule from '../module';
|
import ngModule from '../module';
|
||||||
import SearchPanel from 'core/components/searchbar/search-panel';
|
import SearchPanel from 'core/components/searchbar/search-panel';
|
||||||
|
import './style.scss';
|
||||||
|
|
||||||
class Controller extends SearchPanel {
|
class Controller extends SearchPanel {
|
||||||
constructor($element, $) {
|
constructor($element, $) {
|
||||||
super($element, $);
|
super($element, $);
|
||||||
let model = 'Item';
|
}
|
||||||
let moreFields = ['description', 'name'];
|
|
||||||
|
|
||||||
let properties;
|
$onInit() {
|
||||||
let validations = window.validations;
|
this.filter = {
|
||||||
|
isActive: true,
|
||||||
|
tags: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (validations && validations[model])
|
changeCategory(id) {
|
||||||
properties = validations[model].properties;
|
if (this.filter.categoryFk != id) {
|
||||||
else
|
this.filter.categoryFk = id;
|
||||||
properties = {};
|
this.addFilters();
|
||||||
|
|
||||||
this.moreFields = [];
|
|
||||||
for (let field of moreFields) {
|
|
||||||
let prop = properties[field];
|
|
||||||
this.moreFields.push({
|
|
||||||
name: field,
|
|
||||||
label: prop ? prop.description : field,
|
|
||||||
type: prop ? prop.type : null
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get filter() {
|
removeItemFilter(param) {
|
||||||
let filter = this.$.filter;
|
this.filter[param] = null;
|
||||||
|
if (param == 'categoryFk') this.filter['typeFk'] = null;
|
||||||
for (let fieldFilter of this.fieldFilters)
|
this.addFilters();
|
||||||
filter[fieldFilter.name] = fieldFilter.value;
|
|
||||||
|
|
||||||
return filter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set filter(value) {
|
removeTag(tag) {
|
||||||
if (!value)
|
const index = this.filter.tags.indexOf(tag);
|
||||||
value = {};
|
if (index > -1) this.filter.tags.splice(index, 1);
|
||||||
if (!value.tags)
|
this.addFilters();
|
||||||
value.tags = [{}];
|
|
||||||
|
|
||||||
this.fieldFilters = [];
|
|
||||||
for (let field of this.moreFields) {
|
|
||||||
if (value[field.name] != undefined) {
|
|
||||||
this.fieldFilters.push({
|
|
||||||
name: field.name,
|
|
||||||
value: value[field.name],
|
|
||||||
info: field
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$.filter = value;
|
onKeyPress($event) {
|
||||||
|
if ($event.key === 'Enter')
|
||||||
|
this.addFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
removeField(index, field) {
|
addFilters() {
|
||||||
this.fieldFilters.splice(index, 1);
|
for (let i = 0; i < this.filter.tags.length; i++) {
|
||||||
delete this.$.filter[field];
|
if (!this.filter.tags[i].value)
|
||||||
|
this.filter.tags.splice(i, 1);
|
||||||
|
}
|
||||||
|
return this.model.addFilter({}, this.filter);
|
||||||
|
}
|
||||||
|
|
||||||
|
showTagInfo(itemTag) {
|
||||||
|
if (!itemTag.tagFk) return itemTag.value;
|
||||||
|
return `${this.tags.find(tag => tag.id == itemTag.tagFk).name}: ${itemTag.value}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngModule.component('vnLatestBuysSearchPanel', {
|
ngModule.component('vnLatestBuysSearchPanel', {
|
||||||
template: require('./index.html'),
|
template: require('./index.html'),
|
||||||
controller: Controller
|
controller: Controller,
|
||||||
|
bindings: {
|
||||||
|
model: '<'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,50 +10,46 @@ describe('Entry', () => {
|
||||||
beforeEach(angular.mock.inject($componentController => {
|
beforeEach(angular.mock.inject($componentController => {
|
||||||
$element = angular.element(`<vn-latest-buys-search-panel></vn-latest-buys-search-panel>`);
|
$element = angular.element(`<vn-latest-buys-search-panel></vn-latest-buys-search-panel>`);
|
||||||
controller = $componentController('vnLatestBuysSearchPanel', {$element});
|
controller = $componentController('vnLatestBuysSearchPanel', {$element});
|
||||||
|
controller.model = {addFilter: () => {}};
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('filter() setter', () => {
|
describe('removeItemFilter()', () => {
|
||||||
it(`should set the tags property to the scope filter with an empty array`, () => {
|
it(`should remove param from filter`, () => {
|
||||||
const expectedFilter = {
|
controller.filter = {tags: [], categoryFk: 1, typeFk: 1};
|
||||||
tags: [{}]
|
const expectFilter = {tags: [], categoryFk: null, typeFk: null};
|
||||||
};
|
|
||||||
controller.filter = null;
|
|
||||||
|
|
||||||
expect(controller.filter).toEqual(expectedFilter);
|
controller.removeItemFilter('categoryFk');
|
||||||
});
|
|
||||||
|
|
||||||
it(`should set the tags property to the scope filter with an array of tags`, () => {
|
expect(controller.filter).toEqual(expectFilter);
|
||||||
const expectedFilter = {
|
|
||||||
description: 'My item',
|
|
||||||
tags: [{}]
|
|
||||||
};
|
|
||||||
const expectedFieldFilter = [{
|
|
||||||
info: {
|
|
||||||
label: 'description',
|
|
||||||
name: 'description',
|
|
||||||
type: null
|
|
||||||
},
|
|
||||||
name: 'description',
|
|
||||||
value: 'My item'
|
|
||||||
}];
|
|
||||||
controller.filter = {
|
|
||||||
description: 'My item'
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(controller.filter).toEqual(expectedFilter);
|
|
||||||
expect(controller.fieldFilters).toEqual(expectedFieldFilter);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('removeField()', () => {
|
describe('removeTag()', () => {
|
||||||
it(`should remove the description property from the fieldFilters and from the scope filter`, () => {
|
it(`should remove tag from filter`, () => {
|
||||||
const expectedFilter = {tags: [{}]};
|
const tag = {tagFk: 1, value: 'Value'};
|
||||||
controller.filter = {description: 'My item'};
|
controller.filter = {tags: [tag]};
|
||||||
|
const expectFilter = {tags: []};
|
||||||
|
|
||||||
controller.removeField(0, 'description');
|
controller.removeTag(tag);
|
||||||
|
|
||||||
expect(controller.filter).toEqual(expectedFilter);
|
expect(controller.filter).toEqual(expectFilter);
|
||||||
expect(controller.fieldFilters).toEqual([]);
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('showTagInfo()', () => {
|
||||||
|
it(`should show tag value`, () => {
|
||||||
|
const tag = {value: 'Value'};
|
||||||
|
const result = controller.showTagInfo(tag);
|
||||||
|
|
||||||
|
expect(result).toEqual('Value');
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should show tag name and value`, () => {
|
||||||
|
const tag = {tagFk: 1, value: 'Value'};
|
||||||
|
controller.tags = [{id: 1, name: 'tagName'}];
|
||||||
|
const result = controller.showTagInfo(tag);
|
||||||
|
|
||||||
|
expect(result).toEqual('tagName: Value');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
@import "variables";
|
||||||
|
|
||||||
|
vn-latest-buys-search-panel vn-side-menu div {
|
||||||
|
& > .input {
|
||||||
|
padding-left: $spacing-md;
|
||||||
|
padding-right: $spacing-md;
|
||||||
|
border-color: $color-spacer;
|
||||||
|
border-bottom: $border-thin;
|
||||||
|
}
|
||||||
|
& > .horizontal {
|
||||||
|
grid-auto-flow: column;
|
||||||
|
grid-column-gap: $spacing-sm;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
& > .checks {
|
||||||
|
padding: $spacing-md;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
border-color: $color-spacer;
|
||||||
|
border-bottom: $border-thin;
|
||||||
|
}
|
||||||
|
& > .tags {
|
||||||
|
padding: $spacing-md;
|
||||||
|
padding-bottom: 0%;
|
||||||
|
padding-top: 0%;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
& > .chips {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: $spacing-md;
|
||||||
|
overflow: hidden;
|
||||||
|
max-width: 100%;
|
||||||
|
border-color: $color-spacer;
|
||||||
|
border-top: $border-thin;
|
||||||
|
}
|
||||||
|
& > .item-category {
|
||||||
|
padding: $spacing-sm;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
vn-autocomplete[vn-id="category"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > vn-one {
|
||||||
|
padding: $spacing-sm;
|
||||||
|
min-width: 33.33%;
|
||||||
|
text-align: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
|
||||||
|
& > vn-icon {
|
||||||
|
padding: $spacing-sm;
|
||||||
|
background-color: $color-font-secondary;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: $color-main;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
& > i:before {
|
||||||
|
font-size: 2.6rem;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,16 +8,10 @@
|
||||||
auto-load="true">
|
auto-load="true">
|
||||||
</vn-crud-model>
|
</vn-crud-model>
|
||||||
<vn-portal slot="topbar">
|
<vn-portal slot="topbar">
|
||||||
<vn-searchbar
|
|
||||||
vn-focus
|
|
||||||
panel="vn-latest-buys-search-panel"
|
|
||||||
placeholder="Search by item id or name"
|
|
||||||
info="You can search by item id or name"
|
|
||||||
suggested-filter="{isActive: true}"
|
|
||||||
model="model"
|
|
||||||
auto-state="false">
|
|
||||||
</vn-searchbar>
|
|
||||||
</vn-portal>
|
</vn-portal>
|
||||||
|
<vn-latest-buys-search-panel
|
||||||
|
model="model">
|
||||||
|
</vn-latest-buys-search-panel>
|
||||||
<vn-card>
|
<vn-card>
|
||||||
<smart-table
|
<smart-table
|
||||||
model="model"
|
model="model"
|
||||||
|
@ -261,7 +255,7 @@
|
||||||
</tpl-body>
|
</tpl-body>
|
||||||
<tpl-buttons>
|
<tpl-buttons>
|
||||||
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
|
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
|
||||||
<button response="accept" translate>Create</button>
|
<button response="accept" translate>Save</button>
|
||||||
</tpl-buttons>
|
</tpl-buttons>
|
||||||
</vn-dialog>
|
</vn-dialog>
|
||||||
<vn-item-descriptor-popover
|
<vn-item-descriptor-popover
|
||||||
|
|
|
@ -148,7 +148,7 @@
|
||||||
<span
|
<span
|
||||||
ng-click="itemDescriptor.show($event, line.item.id)"
|
ng-click="itemDescriptor.show($event, line.item.id)"
|
||||||
class="link">
|
class="link">
|
||||||
{{::line.item.id | zeroFill:6}}
|
{{::line.item.id}}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td number shrink>
|
<td number shrink>
|
||||||
|
|
|
@ -94,11 +94,6 @@
|
||||||
"model": "Supplier",
|
"model": "Supplier",
|
||||||
"foreignKey": "supplierFk"
|
"foreignKey": "supplierFk"
|
||||||
},
|
},
|
||||||
"supplierContact": {
|
|
||||||
"type": "hasMany",
|
|
||||||
"model": "SupplierContact",
|
|
||||||
"foreignKey": "supplierFk"
|
|
||||||
},
|
|
||||||
"currency": {
|
"currency": {
|
||||||
"type": "belongsTo",
|
"type": "belongsTo",
|
||||||
"model": "Currency",
|
"model": "Currency",
|
||||||
|
|
|
@ -6,13 +6,15 @@ class Controller extends ModuleCard {
|
||||||
const filter = {
|
const filter = {
|
||||||
include: [
|
include: [
|
||||||
{
|
{
|
||||||
relation: 'supplier'
|
relation: 'supplier',
|
||||||
},
|
scope: {
|
||||||
{
|
include: {
|
||||||
relation: 'supplierContact',
|
relation: 'contacts',
|
||||||
scope: {
|
scope: {
|
||||||
where: {
|
where: {
|
||||||
email: {neq: null}
|
email: {neq: null},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
</vn-item>
|
</vn-item>
|
||||||
<vn-item
|
<vn-item
|
||||||
ng-if="$ctrl.isAgricultural()"
|
ng-if="$ctrl.isAgricultural()"
|
||||||
ng-click="sendPdfConfirmation.show({email: $ctrl.entity.supplierContact[0].email})"
|
ng-click="sendPdfConfirmation.show({email: $ctrl.entity.supplier.contacts[0].email})"
|
||||||
translate>
|
translate>
|
||||||
Send agricultural receipt as PDF
|
Send agricultural receipt as PDF
|
||||||
</vn-item>
|
</vn-item>
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
show-field="description"
|
show-field="description"
|
||||||
rule
|
rule
|
||||||
vn-focus>
|
vn-focus>
|
||||||
<tpl-item>{{id | zeroFill:8}}: {{description}}</tpl-item>
|
<tpl-item>{{id}}: {{description}}</tpl-item>
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
<vn-input-number
|
<vn-input-number
|
||||||
label="Amount"
|
label="Amount"
|
||||||
|
|
|
@ -141,7 +141,7 @@
|
||||||
</vn-thead>
|
</vn-thead>
|
||||||
<vn-tbody>
|
<vn-tbody>
|
||||||
<vn-tr ng-repeat="intrastat in $ctrl.summary.invoiceInIntrastat">
|
<vn-tr ng-repeat="intrastat in $ctrl.summary.invoiceInIntrastat">
|
||||||
<vn-td>{{::intrastat.intrastatFk | zeroFill:8}}: {{::intrastat.intrastat.description}}</vn-td>
|
<vn-td>{{::intrastat.intrastatFk}}: {{::intrastat.intrastat.description}}</vn-td>
|
||||||
<vn-td>{{::intrastat.amount | currency: 'EUR':2}}</vn-td>
|
<vn-td>{{::intrastat.amount | currency: 'EUR':2}}</vn-td>
|
||||||
<vn-td>{{::intrastat.net}}</vn-td>
|
<vn-td>{{::intrastat.net}}</vn-td>
|
||||||
<vn-td>{{::intrastat.stems}}</vn-td>
|
<vn-td>{{::intrastat.stems}}</vn-td>
|
||||||
|
|
|
@ -35,8 +35,7 @@ module.exports = Self => {
|
||||||
const tickets = await models.Ticket.find(filter, myOptions);
|
const tickets = await models.Ticket.find(filter, myOptions);
|
||||||
|
|
||||||
const ticketsIds = tickets.map(ticket => ticket.id);
|
const ticketsIds = tickets.map(ticket => ticket.id);
|
||||||
|
const refundedTickets = await models.Ticket.refund(ticketsIds, true, myOptions);
|
||||||
const refundedTickets = await models.Ticket.refund(ticketsIds, myOptions);
|
|
||||||
|
|
||||||
if (tx) await tx.commit();
|
if (tx) await tx.commit();
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ describe('InvoiceOut refund()', () => {
|
||||||
try {
|
try {
|
||||||
const result = await models.InvoiceOut.refund('T1111111', options);
|
const result = await models.InvoiceOut.refund('T1111111', options);
|
||||||
|
|
||||||
expect(result.length).toEqual(2);
|
expect(result.length).toEqual(1);
|
||||||
|
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -72,4 +72,45 @@ describe('upsertFixedPrice()', () => {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it(`should recalculate rate2 if change rate3`, async() => {
|
||||||
|
const tx = await models.FixedPrice.beginTransaction({});
|
||||||
|
|
||||||
|
const tomorrow = new Date(now);
|
||||||
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||||
|
|
||||||
|
const rate2 = 2;
|
||||||
|
const firstRate3 = 1;
|
||||||
|
const secondRate3 = 2;
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
const ctx = {args: {
|
||||||
|
id: undefined,
|
||||||
|
itemFk: 1,
|
||||||
|
warehouseFk: 1,
|
||||||
|
started: tomorrow,
|
||||||
|
ended: tomorrow,
|
||||||
|
rate2: rate2,
|
||||||
|
rate3: firstRate3,
|
||||||
|
minPrice: 0,
|
||||||
|
hasMinPrice: false
|
||||||
|
}};
|
||||||
|
|
||||||
|
// create new fixed price
|
||||||
|
const newFixedPrice = await models.FixedPrice.upsertFixedPrice(ctx, options);
|
||||||
|
|
||||||
|
// change rate3 to same fixed price id
|
||||||
|
ctx.args.id = newFixedPrice.id;
|
||||||
|
ctx.args.rate3 = secondRate3;
|
||||||
|
|
||||||
|
const result = await models.FixedPrice.upsertFixedPrice(ctx, options);
|
||||||
|
|
||||||
|
expect(result.rate2).not.toEqual(rate2);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -72,6 +72,16 @@ module.exports = Self => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
delete args.ctx; // removed unwanted data
|
delete args.ctx; // removed unwanted data
|
||||||
|
|
||||||
|
if (args.id) {
|
||||||
|
const beforeFixedPrice = await models.FixedPrice.findById(args.id, {fields: ['rate3']}, myOptions);
|
||||||
|
const [result] = await Self.rawSql(`SELECT vn.priceFixed_getRate2(?, ?) as rate2`,
|
||||||
|
[args.id, args.rate3], myOptions);
|
||||||
|
|
||||||
|
if (beforeFixedPrice.rate3 != args.rate3 && result && result.rate2)
|
||||||
|
args.rate2 = result.rate2;
|
||||||
|
}
|
||||||
|
|
||||||
const fixedPrice = await models.FixedPrice.upsert(args, myOptions);
|
const fixedPrice = await models.FixedPrice.upsert(args, myOptions);
|
||||||
const targetItem = await models.Item.findById(args.itemFk, null, myOptions);
|
const targetItem = await models.Item.findById(args.itemFk, null, myOptions);
|
||||||
|
|
||||||
|
|
|
@ -98,9 +98,8 @@ module.exports = Self => {
|
||||||
summary.tags = res[1];
|
summary.tags = res[1];
|
||||||
[summary.botanical] = res[2];
|
[summary.botanical] = res[2];
|
||||||
|
|
||||||
const userConfig = await models.UserConfig.getUserConfig(ctx, myOptions);
|
const itemConfig = await models.ItemConfig.findOne(null, myOptions);
|
||||||
|
res = await models.Item.getVisibleAvailable(summary.item.id, itemConfig.warehouseFk, undefined, myOptions);
|
||||||
res = await models.Item.getVisibleAvailable(summary.item.id, userConfig.warehouseFk, null, myOptions);
|
|
||||||
|
|
||||||
summary.available = res.available;
|
summary.available = res.available;
|
||||||
summary.visible = res.visible;
|
summary.visible = res.visible;
|
||||||
|
|
|
@ -12,7 +12,7 @@ describe('item getVisibleAvailable()', () => {
|
||||||
|
|
||||||
const result = await models.Item.getVisibleAvailable(itemFk, warehouseFk, dated, options);
|
const result = await models.Item.getVisibleAvailable(itemFk, warehouseFk, dated, options);
|
||||||
|
|
||||||
expect(result.available).toEqual(187);
|
expect(result.available).toEqual(185);
|
||||||
expect(result.visible).toEqual(92);
|
expect(result.visible).toEqual(92);
|
||||||
|
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
|
|
|
@ -25,6 +25,9 @@
|
||||||
},
|
},
|
||||||
"defaultTag": {
|
"defaultTag": {
|
||||||
"type": "int"
|
"type": "int"
|
||||||
|
},
|
||||||
|
"warehouseFk": {
|
||||||
|
"type": "int"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"relations": {
|
"relations": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<vn-portal slot="menu">
|
<vn-portal slot="menu">
|
||||||
<vn-item-descriptor
|
<vn-item-descriptor
|
||||||
warehouse-fk="$ctrl.vnConfig.warehouseFk"
|
warehouse-fk="$ctrl.warehouseFk"
|
||||||
item="$ctrl.item"
|
item="$ctrl.item"
|
||||||
card-reload="$ctrl.reload()"></vn-item-descriptor>
|
card-reload="$ctrl.reload()"></vn-item-descriptor>
|
||||||
<vn-left-menu source="card"></vn-left-menu>
|
<vn-left-menu source="card"></vn-left-menu>
|
||||||
|
|
|
@ -36,6 +36,16 @@
|
||||||
<p translate>Available</p>
|
<p translate>Available</p>
|
||||||
<p>{{$ctrl.available | dashIfEmpty}}</p>
|
<p>{{$ctrl.available | dashIfEmpty}}</p>
|
||||||
</vn-one>
|
</vn-one>
|
||||||
|
<vn-one>
|
||||||
|
<p>
|
||||||
|
<vn-icon
|
||||||
|
ng-if="$ctrl.showIcon"
|
||||||
|
icon="info_outline"
|
||||||
|
vn-tooltip="{{$ctrl.warehouseText}}"
|
||||||
|
pointer>
|
||||||
|
</vn-icon>
|
||||||
|
</p>
|
||||||
|
</vn-one>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
</slot-before>
|
</slot-before>
|
||||||
<slot-body>
|
<slot-body>
|
||||||
|
|
|
@ -30,7 +30,10 @@ class Controller extends Descriptor {
|
||||||
|
|
||||||
set warehouseFk(value) {
|
set warehouseFk(value) {
|
||||||
this._warehouseFk = value;
|
this._warehouseFk = value;
|
||||||
if (value) this.updateStock();
|
if (value) {
|
||||||
|
this.updateStock();
|
||||||
|
this.getWarehouseName(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadData() {
|
loadData() {
|
||||||
|
@ -89,6 +92,22 @@ class Controller extends Descriptor {
|
||||||
this.$.photo.setAttribute('src', newSrc);
|
this.$.photo.setAttribute('src', newSrc);
|
||||||
this.$.photo.setAttribute('zoom-image', newZoomSrc);
|
this.$.photo.setAttribute('zoom-image', newZoomSrc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getWarehouseName(warehouseFk) {
|
||||||
|
this.showIcon = false;
|
||||||
|
|
||||||
|
const filter = {
|
||||||
|
where: {id: warehouseFk}
|
||||||
|
};
|
||||||
|
this.$http.get('Warehouses/findOne', {filter})
|
||||||
|
.then(res => {
|
||||||
|
this.warehouseText = this.$t('WarehouseFk', {
|
||||||
|
warehouseName: res.data.name
|
||||||
|
});
|
||||||
|
|
||||||
|
this.showIcon = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller.$inject = ['$element', '$scope', '$rootScope'];
|
Controller.$inject = ['$element', '$scope', '$rootScope'];
|
||||||
|
@ -100,6 +119,6 @@ ngModule.vnComponent('vnItemDescriptor', {
|
||||||
item: '<',
|
item: '<',
|
||||||
dated: '<',
|
dated: '<',
|
||||||
cardReload: '&',
|
cardReload: '&',
|
||||||
warehouseFk: '<?'
|
warehouseFk: '<'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,6 +37,7 @@ class Controller extends Section {
|
||||||
set warehouseFk(value) {
|
set warehouseFk(value) {
|
||||||
if (value && value != this._warehouseFk) {
|
if (value && value != this._warehouseFk) {
|
||||||
this._warehouseFk = value;
|
this._warehouseFk = value;
|
||||||
|
this.card.warehouseFk = value;
|
||||||
|
|
||||||
this.$state.go(this.$state.current.name, {
|
this.$state.go(this.$state.current.name, {
|
||||||
warehouseFk: value
|
warehouseFk: value
|
||||||
|
@ -76,5 +77,8 @@ ngModule.vnComponent('vnItemDiary', {
|
||||||
controller: Controller,
|
controller: Controller,
|
||||||
bindings: {
|
bindings: {
|
||||||
item: '<'
|
item: '<'
|
||||||
|
},
|
||||||
|
require: {
|
||||||
|
card: '?^vnItemCard'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,6 +14,7 @@ describe('Item', () => {
|
||||||
controller = $componentController('vnItemDiary', {$element, $scope});
|
controller = $componentController('vnItemDiary', {$element, $scope});
|
||||||
controller.$.model = crudModel;
|
controller.$.model = crudModel;
|
||||||
controller.$params = {id: 1};
|
controller.$params = {id: 1};
|
||||||
|
controller.card = {};
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('set item()', () => {
|
describe('set item()', () => {
|
||||||
|
|
|
@ -131,7 +131,7 @@
|
||||||
class="dense"
|
class="dense"
|
||||||
vn-focus
|
vn-focus
|
||||||
ng-model="price.rate3"
|
ng-model="price.rate3"
|
||||||
on-change="$ctrl.upsertPrice(price); $ctrl.recalculateRate2(price)"
|
on-change="$ctrl.upsertPrice(price);"
|
||||||
step="0.01"s>
|
step="0.01"s>
|
||||||
</vn-input-number>
|
</vn-input-number>
|
||||||
</field>
|
</field>
|
||||||
|
|
|
@ -113,24 +113,6 @@ export default class Controller extends Section {
|
||||||
return {[param]: value};
|
return {[param]: value};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
recalculateRate2(price) {
|
|
||||||
if (!price.id || !price.rate3) return;
|
|
||||||
|
|
||||||
const query = 'FixedPrices/getRate2';
|
|
||||||
const params = {
|
|
||||||
fixedPriceId: price.id,
|
|
||||||
rate3: price.rate3
|
|
||||||
};
|
|
||||||
this.$http.get(query, {params})
|
|
||||||
.then(res => {
|
|
||||||
const rate2 = res.data.rate2;
|
|
||||||
if (rate2) {
|
|
||||||
price.rate2 = rate2;
|
|
||||||
this.upsertPrice(price);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngModule.vnComponent('vnFixedPrice', {
|
ngModule.vnComponent('vnFixedPrice', {
|
||||||
|
|
|
@ -85,25 +85,5 @@ describe('fixed price', () => {
|
||||||
expect(controller.$.model.remove).toHaveBeenCalled();
|
expect(controller.$.model.remove).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('recalculateRate2()', () => {
|
|
||||||
it(`should rate2 recalculate`, () => {
|
|
||||||
jest.spyOn(controller.vnApp, 'showSuccess');
|
|
||||||
const price = {
|
|
||||||
id: 1,
|
|
||||||
itemFk: 1,
|
|
||||||
rate2: 2,
|
|
||||||
rate3: 2
|
|
||||||
};
|
|
||||||
const response = {rate2: 1};
|
|
||||||
controller.recalculateRate2(price);
|
|
||||||
|
|
||||||
const query = `FixedPrices/getRate2?fixedPriceId=${price.id}&rate3=${price.rate3}`;
|
|
||||||
$httpBackend.expectGET(query).respond(response);
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
expect(price.rate2).toEqual(response.rate2);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
<vn-card class="vn-w-md vn-pa-md">
|
<vn-card class="vn-w-md vn-pa-md">
|
||||||
<vn-horizontal>
|
<vn-horizontal>
|
||||||
<vn-date-picker class="vn-pa-xs"
|
<vn-date-picker class="vn-pa-xs"
|
||||||
|
vn-one
|
||||||
|
label="Since"
|
||||||
vn-one
|
vn-one
|
||||||
label="Since"
|
label="Since"
|
||||||
ng-model="$ctrl.dateFrom">
|
ng-model="$ctrl.dateFrom">
|
||||||
|
@ -35,7 +37,7 @@
|
||||||
<vn-th field="warehouseFk">Warehouse</vn-th>
|
<vn-th field="warehouseFk">Warehouse</vn-th>
|
||||||
<vn-th field="landed">Landed</vn-th>
|
<vn-th field="landed">Landed</vn-th>
|
||||||
<vn-th number>Entry</vn-th>
|
<vn-th number>Entry</vn-th>
|
||||||
<vn-th vn-tooltip="Grouping / Packing">PVP</vn-th>
|
<vn-th vn-tooltip="Grouping / Packing" expand>PVP</vn-th>
|
||||||
<vn-th number class="expendable">Label</vn-th>
|
<vn-th number class="expendable">Label</vn-th>
|
||||||
<vn-th number>Packing</vn-th>
|
<vn-th number>Packing</vn-th>
|
||||||
<vn-th number>Grouping</vn-th>
|
<vn-th number>Grouping</vn-th>
|
||||||
|
@ -64,7 +66,7 @@
|
||||||
{{::entry.entryFk | dashIfEmpty}}
|
{{::entry.entryFk | dashIfEmpty}}
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td title="{{::entry.price2 | currency: 'EUR':2 | dashIfEmpty}} / {{::entry.price3 | currency: 'EUR':2 | dashIfEmpty}}">
|
<vn-td title="Grouping / Packing">
|
||||||
{{::entry.price2 | currency: 'EUR':2 | dashIfEmpty}} / {{::entry.price3 | currency: 'EUR':2 | dashIfEmpty}}
|
{{::entry.price2 | currency: 'EUR':2 | dashIfEmpty}} / {{::entry.price3 | currency: 'EUR':2 | dashIfEmpty}}
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td number class="expendable">{{entry.stickers | dashIfEmpty}}</vn-td>
|
<vn-td number class="expendable">{{entry.stickers | dashIfEmpty}}</vn-td>
|
||||||
|
|
|
@ -22,6 +22,16 @@
|
||||||
<p translate>Available</p>
|
<p translate>Available</p>
|
||||||
<p>{{$ctrl.summary.available}}</p>
|
<p>{{$ctrl.summary.available}}</p>
|
||||||
</vn-one>
|
</vn-one>
|
||||||
|
<vn-one>
|
||||||
|
<p>
|
||||||
|
<vn-icon
|
||||||
|
ng-if="$ctrl.warehouseText != null"
|
||||||
|
icon="info_outline"
|
||||||
|
vn-tooltip="{{$ctrl.warehouseText}}"
|
||||||
|
pointer>
|
||||||
|
</vn-icon>
|
||||||
|
</p>
|
||||||
|
</vn-one>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
</vn-one>
|
</vn-one>
|
||||||
<vn-one name="basicData">
|
<vn-one name="basicData">
|
||||||
|
|
|
@ -7,6 +7,24 @@ class Controller extends Summary {
|
||||||
this.$http.get(`Items/${this.item.id}/getSummary`).then(response => {
|
this.$http.get(`Items/${this.item.id}/getSummary`).then(response => {
|
||||||
this.summary = response.data;
|
this.summary = response.data;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.$http.get('ItemConfigs/findOne')
|
||||||
|
.then(res => {
|
||||||
|
if (this.card) this.card.warehouseFk = res.data.warehouseFk;
|
||||||
|
this.getWarehouseName(res.data.warehouseFk);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getWarehouseName(warehouseFk) {
|
||||||
|
const filter = {
|
||||||
|
where: {id: warehouseFk}
|
||||||
|
};
|
||||||
|
this.$http.get('Warehouses/findOne', {filter})
|
||||||
|
.then(res => {
|
||||||
|
this.warehouseText = this.$t('WarehouseFk', {
|
||||||
|
warehouseName: res.data.name
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$onChanges() {
|
$onChanges() {
|
||||||
|
@ -37,4 +55,7 @@ ngModule.vnComponent('vnItemSummary', {
|
||||||
bindings: {
|
bindings: {
|
||||||
item: '<',
|
item: '<',
|
||||||
},
|
},
|
||||||
|
require: {
|
||||||
|
card: '?^vnItemCard'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -14,12 +14,15 @@ describe('Item', () => {
|
||||||
const $element = angular.element('<vn-item-summary></vn-item-summary>');
|
const $element = angular.element('<vn-item-summary></vn-item-summary>');
|
||||||
controller = $componentController('vnItemSummary', {$element, $scope});
|
controller = $componentController('vnItemSummary', {$element, $scope});
|
||||||
controller.item = {id: 1};
|
controller.item = {id: 1};
|
||||||
|
controller.card = {};
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('getSummary()', () => {
|
describe('getSummary()', () => {
|
||||||
it('should perform a query to set summary', () => {
|
it('should perform a query to set summary', () => {
|
||||||
let data = {id: 1, name: 'Gem of mind'};
|
let data = {id: 1, name: 'Gem of mind'};
|
||||||
$httpBackend.expect('GET', `Items/1/getSummary`).respond(200, data);
|
$httpBackend.expect('GET', `Items/1/getSummary`).respond(200, data);
|
||||||
|
$httpBackend.expect('GET', `ItemConfigs/findOne`).respond({});
|
||||||
|
$httpBackend.expect('GET', `Warehouses/findOne`).respond({});
|
||||||
controller.getSummary();
|
controller.getSummary();
|
||||||
$httpBackend.flush();
|
$httpBackend.flush();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
WarehouseFk: Calculated on the warehouse of {{ warehouseName }}
|
|
@ -1,3 +1,4 @@
|
||||||
Barcode: Códigos de barras
|
Barcode: Códigos de barras
|
||||||
Other data: Otros datos
|
Other data: Otros datos
|
||||||
Go to the item: Ir al artículo
|
Go to the item: Ir al artículo
|
||||||
|
WarehouseFk: Calculado sobre el almacén de {{ warehouseName }}
|
||||||
|
|
|
@ -31,5 +31,9 @@ vn-item-summary {
|
||||||
&:nth-child(1) {
|
&:nth-child(1) {
|
||||||
border-right: 1px solid white;
|
border-right: 1px solid white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:nth-child(2) {
|
||||||
|
border-right: 1px solid white;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -40,6 +40,7 @@ class Controller extends Section {
|
||||||
this.$.model.refresh();
|
this.$.model.refresh();
|
||||||
this.$.watcher.notifySaved();
|
this.$.watcher.notifySaved();
|
||||||
this.$.watcher.updateOriginalData();
|
this.$.watcher.updateOriginalData();
|
||||||
|
this.card.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,11 @@ module.exports = Self => {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
description: `The old version number`
|
description: `The old version number`
|
||||||
|
}, {
|
||||||
|
arg: 'description',
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
description: `The description of changes`
|
||||||
}, {
|
}, {
|
||||||
arg: 'unlock',
|
arg: 'unlock',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
@ -42,8 +47,7 @@ module.exports = Self => {
|
||||||
verb: 'POST'
|
verb: 'POST'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Self.upload = async(ctx, options) => {
|
||||||
Self.upload = async(ctx, appName, toVersion, branch, fromVersion, unlock, options) => {
|
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
const myOptions = {};
|
const myOptions = {};
|
||||||
const $t = ctx.req.__; // $translate
|
const $t = ctx.req.__; // $translate
|
||||||
|
@ -51,6 +55,12 @@ module.exports = Self => {
|
||||||
const AccessContainer = models.AccessContainer;
|
const AccessContainer = models.AccessContainer;
|
||||||
const fileOptions = {};
|
const fileOptions = {};
|
||||||
let tx;
|
let tx;
|
||||||
|
const appName = ctx.args.appName;
|
||||||
|
const toVersion = ctx.args.toVersion;
|
||||||
|
const branch = ctx.args.branch;
|
||||||
|
const fromVersion = ctx.args.fromVersion;
|
||||||
|
let description = ctx.args.description;
|
||||||
|
const unlock = ctx.args.unlock;
|
||||||
|
|
||||||
if (typeof options == 'object')
|
if (typeof options == 'object')
|
||||||
Object.assign(myOptions, options);
|
Object.assign(myOptions, options);
|
||||||
|
@ -132,13 +142,46 @@ module.exports = Self => {
|
||||||
await fs.symlink(rootRelative, destinationRoot);
|
await fs.symlink(rootRelative, destinationRoot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (description) {
|
||||||
|
let formatDesc;
|
||||||
|
const mainBranches = new Set(['master', 'test', 'dev']);
|
||||||
|
if (mainBranches.has(branch))
|
||||||
|
formatDesc = `> :branch_${branch}: `;
|
||||||
|
else
|
||||||
|
formatDesc = `> :branch: `;
|
||||||
|
|
||||||
|
formatDesc += `*${appName.toUpperCase()}* v.${toVersion} `;
|
||||||
|
|
||||||
|
const oldVersion = await models.MdbVersionTree.findOne({
|
||||||
|
where: {version: fromVersion},
|
||||||
|
fields: ['branchFk']
|
||||||
|
}, myOptions);
|
||||||
|
|
||||||
|
if (branch == oldVersion.branchFk)
|
||||||
|
formatDesc += `[*${branch}*]: `;
|
||||||
|
else
|
||||||
|
formatDesc += `[*${oldVersion.branchFk}* » *${branch}*]: `;
|
||||||
|
|
||||||
|
const params = await models.MdbConfig.findOne(myOptions);
|
||||||
|
const issueTrackerUrl = params.issueTrackerUrl;
|
||||||
|
const issueNumberRegex = params.issueNumberRegex;
|
||||||
|
const chatDestination = params.chatDestination;
|
||||||
|
|
||||||
|
const regex = new RegExp(issueNumberRegex, 'g');
|
||||||
|
formatDesc += description.replace(regex, (match, issueId) => {
|
||||||
|
const newUrl = issueTrackerUrl.replace('{index}', issueId);
|
||||||
|
return `[#${issueId}](${newUrl})`;
|
||||||
|
});
|
||||||
|
|
||||||
|
await models.Chat.send(ctx, chatDestination, formatDesc, myOptions);
|
||||||
|
}
|
||||||
await models.MdbVersionTree.create({
|
await models.MdbVersionTree.create({
|
||||||
app: appName,
|
app: appName,
|
||||||
version: toVersion,
|
version: toVersion,
|
||||||
branchFk: branch,
|
branchFk: branch,
|
||||||
fromVersion,
|
fromVersion,
|
||||||
userFk: userId
|
userFk: userId,
|
||||||
|
description,
|
||||||
}, myOptions);
|
}, myOptions);
|
||||||
|
|
||||||
await models.MdbVersion.upsert({
|
await models.MdbVersion.upsert({
|
||||||
|
|
|
@ -11,6 +11,9 @@
|
||||||
"MdbVersionTree": {
|
"MdbVersionTree": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
"MdbConfig": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
"AccessContainer": {
|
"AccessContainer": {
|
||||||
"dataSource": "accessStorage"
|
"dataSource": "accessStorage"
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,11 @@
|
||||||
"id": true,
|
"id": true,
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Identifier"
|
"description": "Identifier"
|
||||||
|
},
|
||||||
|
"dsName": {
|
||||||
|
"id": true,
|
||||||
|
"type": "string",
|
||||||
|
"description": "ODBC name"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"name": "MdbConfig",
|
||||||
|
"base": "VnModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "mdbConfig"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string",
|
||||||
|
"id": true
|
||||||
|
},
|
||||||
|
"issueTrackerUrl": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"issueNumberRegex": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"chatDestination": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,9 @@
|
||||||
},
|
},
|
||||||
"userFk": {
|
"userFk": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"relations": {
|
"relations": {
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
<vn-td number>
|
<vn-td number>
|
||||||
<span ng-click="itemDescriptor.show($event, row.itemFk)"
|
<span ng-click="itemDescriptor.show($event, row.itemFk)"
|
||||||
class="link">
|
class="link">
|
||||||
{{::row.itemFk | zeroFill:6}}
|
{{::row.itemFk}}
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td vn-fetched-tags>
|
<vn-td vn-fetched-tags>
|
||||||
|
|
|
@ -95,7 +95,7 @@
|
||||||
<span
|
<span
|
||||||
ng-click="itemDescriptor.show($event, row.itemFk)"
|
ng-click="itemDescriptor.show($event, row.itemFk)"
|
||||||
class="link">
|
class="link">
|
||||||
{{::row.itemFk | zeroFill:6}}
|
{{::row.itemFk}}
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
<vn-td vn-fetched-tags>
|
<vn-td vn-fetched-tags>
|
||||||
|
|
|
@ -29,5 +29,12 @@
|
||||||
"pickingOrder": {
|
"pickingOrder": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"relations": {
|
||||||
|
"saleGroup": {
|
||||||
|
"type": "hasMany",
|
||||||
|
"model": "saleGroup",
|
||||||
|
"foreignKey": "parkingFk"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,3 +21,5 @@ companyFk: empresa
|
||||||
agencyModeFk: agencia
|
agencyModeFk: agencia
|
||||||
ticketFk: ticket
|
ticketFk: ticket
|
||||||
mergedTicket: ticket fusionado
|
mergedTicket: ticket fusionado
|
||||||
|
withWarningAccept: aviso negativos
|
||||||
|
isWithoutNegatives: sin negativos
|
||||||
|
|
|
@ -12,6 +12,11 @@ module.exports = Self => {
|
||||||
arg: 'servicesIds',
|
arg: 'servicesIds',
|
||||||
type: ['number']
|
type: ['number']
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
arg: 'createSingleTicket',
|
||||||
|
type: 'boolean',
|
||||||
|
required: false
|
||||||
|
}
|
||||||
],
|
],
|
||||||
returns: {
|
returns: {
|
||||||
type: ['number'],
|
type: ['number'],
|
||||||
|
@ -23,7 +28,7 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.refund = async(salesIds, servicesIds, options) => {
|
Self.refund = async(salesIds, servicesIds, createSingleTicket = false, options) => {
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
const myOptions = {};
|
const myOptions = {};
|
||||||
let tx;
|
let tx;
|
||||||
|
@ -63,11 +68,93 @@ module.exports = Self => {
|
||||||
const ticketsIds = [...new Set(sales.map(sale => sale.ticketFk))];
|
const ticketsIds = [...new Set(sales.map(sale => sale.ticketFk))];
|
||||||
|
|
||||||
const refundTickets = [];
|
const refundTickets = [];
|
||||||
|
|
||||||
const now = Date.vnNew();
|
|
||||||
const mappedTickets = new Map();
|
const mappedTickets = new Map();
|
||||||
|
const now = Date.vnNew();
|
||||||
|
|
||||||
|
const [firstTicketId] = ticketsIds;
|
||||||
|
if (createSingleTicket) {
|
||||||
|
await createTicketRefund(
|
||||||
|
firstTicketId,
|
||||||
|
refundTickets,
|
||||||
|
mappedTickets,
|
||||||
|
now,
|
||||||
|
refundAgencyMode,
|
||||||
|
refoundZoneId,
|
||||||
|
myOptions
|
||||||
|
);
|
||||||
|
} else {
|
||||||
for (let ticketId of ticketsIds) {
|
for (let ticketId of ticketsIds) {
|
||||||
|
await createTicketRefund(
|
||||||
|
ticketId,
|
||||||
|
refundTickets,
|
||||||
|
mappedTickets,
|
||||||
|
now,
|
||||||
|
refundAgencyMode,
|
||||||
|
refoundZoneId,
|
||||||
|
myOptions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const sale of sales) {
|
||||||
|
const refundTicketId = await getTicketRefundId(createSingleTicket, sale.ticketFk, refundTickets, mappedTickets);
|
||||||
|
|
||||||
|
const createdSale = await models.Sale.create({
|
||||||
|
ticketFk: refundTicketId,
|
||||||
|
itemFk: sale.itemFk,
|
||||||
|
quantity: - sale.quantity,
|
||||||
|
concept: sale.concept,
|
||||||
|
price: sale.price,
|
||||||
|
discount: sale.discount,
|
||||||
|
}, myOptions);
|
||||||
|
|
||||||
|
const components = sale.components();
|
||||||
|
for (const component of components)
|
||||||
|
component.saleFk = createdSale.id;
|
||||||
|
|
||||||
|
await models.SaleComponent.create(components, myOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (servicesIds && servicesIds.length > 0) {
|
||||||
|
const servicesFilter = {
|
||||||
|
where: {id: {inq: servicesIds}}
|
||||||
|
};
|
||||||
|
const services = await models.TicketService.find(servicesFilter, myOptions);
|
||||||
|
|
||||||
|
for (const service of services) {
|
||||||
|
const refundTicketId = await getTicketRefundId(createSingleTicket, service.ticketFk, refundTickets, mappedTickets);
|
||||||
|
|
||||||
|
await models.TicketService.create({
|
||||||
|
description: service.description,
|
||||||
|
quantity: - service.quantity,
|
||||||
|
price: service.price,
|
||||||
|
taxClassFk: service.taxClassFk,
|
||||||
|
ticketFk: refundTicketId,
|
||||||
|
ticketServiceTypeFk: service.ticketServiceTypeFk,
|
||||||
|
}, myOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tx) await tx.commit();
|
||||||
|
|
||||||
|
return refundTickets;
|
||||||
|
} catch (e) {
|
||||||
|
if (tx) await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function createTicketRefund(
|
||||||
|
ticketId,
|
||||||
|
refundTickets,
|
||||||
|
mappedTickets,
|
||||||
|
now,
|
||||||
|
refundAgencyMode,
|
||||||
|
refoundZoneId,
|
||||||
|
myOptions
|
||||||
|
) {
|
||||||
|
const models = Self.app.models;
|
||||||
|
|
||||||
const filter = {include: {relation: 'address'}};
|
const filter = {include: {relation: 'address'}};
|
||||||
const ticket = await models.Ticket.findById(ticketId, filter, myOptions);
|
const ticket = await models.Ticket.findById(ticketId, filter, myOptions);
|
||||||
|
|
||||||
|
@ -93,50 +180,10 @@ module.exports = Self => {
|
||||||
}, myOptions);
|
}, myOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const sale of sales) {
|
async function getTicketRefundId(createSingleTicket, ticketId, refundTickets, mappedTickets) {
|
||||||
const refundTicketId = mappedTickets.get(sale.ticketFk);
|
if (createSingleTicket) {
|
||||||
const createdSale = await models.Sale.create({
|
const [firstRefundTicket] = refundTickets;
|
||||||
ticketFk: refundTicketId,
|
return firstRefundTicket.id;
|
||||||
itemFk: sale.itemFk,
|
} else return mappedTickets.get(ticketId);
|
||||||
quantity: - sale.quantity,
|
|
||||||
concept: sale.concept,
|
|
||||||
price: sale.price,
|
|
||||||
discount: sale.discount,
|
|
||||||
}, myOptions);
|
|
||||||
|
|
||||||
const components = sale.components();
|
|
||||||
for (const component of components)
|
|
||||||
component.saleFk = createdSale.id;
|
|
||||||
|
|
||||||
await models.SaleComponent.create(components, myOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (servicesIds && servicesIds.length > 0) {
|
|
||||||
const servicesFilter = {
|
|
||||||
where: {id: {inq: servicesIds}}
|
|
||||||
};
|
|
||||||
const services = await models.TicketService.find(servicesFilter, myOptions);
|
|
||||||
|
|
||||||
for (const service of services) {
|
|
||||||
const refundTicketId = mappedTickets.get(service.ticketFk);
|
|
||||||
|
|
||||||
await models.TicketService.create({
|
|
||||||
description: service.description,
|
|
||||||
quantity: - service.quantity,
|
|
||||||
price: service.price,
|
|
||||||
taxClassFk: service.taxClassFk,
|
|
||||||
ticketFk: refundTicketId,
|
|
||||||
ticketServiceTypeFk: service.ticketServiceTypeFk,
|
|
||||||
}, myOptions);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tx) await tx.commit();
|
|
||||||
|
|
||||||
return refundTickets;
|
|
||||||
} catch (e) {
|
|
||||||
if (tx) await tx.rollback();
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const models = require('vn-loopback/server/server').models;
|
const models = require('vn-loopback/server/server').models;
|
||||||
const LoopBackContext = require('loopback-context');
|
const LoopBackContext = require('loopback-context');
|
||||||
|
|
||||||
describe('sale refund()', () => {
|
describe('Sale refund()', () => {
|
||||||
const userId = 5;
|
const userId = 5;
|
||||||
const activeCtx = {
|
const activeCtx = {
|
||||||
accessToken: {userId: userId},
|
accessToken: {userId: userId},
|
||||||
|
@ -22,7 +22,7 @@ describe('sale refund()', () => {
|
||||||
try {
|
try {
|
||||||
const options = {transaction: tx};
|
const options = {transaction: tx};
|
||||||
|
|
||||||
const response = await models.Sale.refund(salesIds, servicesIds, options);
|
const response = await models.Sale.refund(salesIds, servicesIds, false, options);
|
||||||
|
|
||||||
expect(response.length).toBeGreaterThanOrEqual(1);
|
expect(response.length).toBeGreaterThanOrEqual(1);
|
||||||
|
|
||||||
|
@ -40,7 +40,8 @@ describe('sale refund()', () => {
|
||||||
try {
|
try {
|
||||||
const options = {transaction: tx};
|
const options = {transaction: tx};
|
||||||
|
|
||||||
const tickets = await models.Sale.refund(salesIds, servicesIds, options);
|
const createSingleTicket = false;
|
||||||
|
const tickets = await models.Sale.refund(salesIds, servicesIds, createSingleTicket, options);
|
||||||
|
|
||||||
const ticketsIds = tickets.map(ticket => ticket.id);
|
const ticketsIds = tickets.map(ticket => ticket.id);
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,11 @@ module.exports = Self => {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description: 'Is whithout negatives',
|
description: 'Is whithout negatives',
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'withWarningAccept',
|
||||||
|
type: 'boolean',
|
||||||
|
description: 'Has pressed in confirm message',
|
||||||
}],
|
}],
|
||||||
returns: {
|
returns: {
|
||||||
type: ['object'],
|
type: ['object'],
|
||||||
|
|
|
@ -50,15 +50,9 @@ module.exports = Self => {
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
arg: 'isNotValidated',
|
arg: 'isFullMovable',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
description: 'Origin state',
|
description: 'True when lines and stock of origin are equal',
|
||||||
required: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
arg: 'futureIsNotValidated',
|
|
||||||
type: 'boolean',
|
|
||||||
description: 'Destination state',
|
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -105,10 +99,8 @@ module.exports = Self => {
|
||||||
{'f.futureIpt': null}
|
{'f.futureIpt': null}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
case 'isNotValidated':
|
case 'isFullMovable':
|
||||||
return {'f.isNotValidated': value};
|
return {'f.isFullMovable': value};
|
||||||
case 'futureIsNotValidated':
|
|
||||||
return {'f.futureIsNotValidated': value};
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,11 @@ module.exports = Self => {
|
||||||
type: ['number'],
|
type: ['number'],
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
arg: 'createSingleTicket',
|
||||||
|
type: 'boolean',
|
||||||
|
required: false
|
||||||
|
}
|
||||||
],
|
],
|
||||||
returns: {
|
returns: {
|
||||||
type: ['number'],
|
type: ['number'],
|
||||||
|
@ -19,7 +24,7 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.refund = async(ticketsIds, options) => {
|
Self.refund = async(ticketsIds, createSingleTicket = false, options) => {
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
const myOptions = {};
|
const myOptions = {};
|
||||||
let tx;
|
let tx;
|
||||||
|
@ -41,7 +46,7 @@ module.exports = Self => {
|
||||||
const services = await models.TicketService.find(filter, myOptions);
|
const services = await models.TicketService.find(filter, myOptions);
|
||||||
const servicesIds = services.map(service => service.id);
|
const servicesIds = services.map(service => service.id);
|
||||||
|
|
||||||
const refundedTickets = await models.Sale.refund(salesIds, servicesIds, myOptions);
|
const refundedTickets = await models.Sale.refund(salesIds, servicesIds, createSingleTicket, myOptions);
|
||||||
|
|
||||||
if (tx) await tx.commit();
|
if (tx) await tx.commit();
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ describe('TicketFuture getTicketsAdvance()', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the tickets matching the origin pending state', async() => {
|
it('should return the tickets matching the fullMovable true', async() => {
|
||||||
const tx = await models.Ticket.beginTransaction({});
|
const tx = await models.Ticket.beginTransaction({});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -39,7 +39,7 @@ describe('TicketFuture getTicketsAdvance()', () => {
|
||||||
dateFuture: tomorrow,
|
dateFuture: tomorrow,
|
||||||
dateToAdvance: today,
|
dateToAdvance: today,
|
||||||
warehouseFk: 1,
|
warehouseFk: 1,
|
||||||
futureIsNotValidated: true
|
isFullMovable: true
|
||||||
};
|
};
|
||||||
|
|
||||||
const ctx = {req: {accessToken: {userId: 9}}, args};
|
const ctx = {req: {accessToken: {userId: 9}}, args};
|
||||||
|
@ -54,7 +54,7 @@ describe('TicketFuture getTicketsAdvance()', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the tickets matching the destination pending state', async() => {
|
it('should return the tickets matching the fullMovable false', async() => {
|
||||||
const tx = await models.Ticket.beginTransaction({});
|
const tx = await models.Ticket.beginTransaction({});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -64,7 +64,7 @@ describe('TicketFuture getTicketsAdvance()', () => {
|
||||||
dateFuture: tomorrow,
|
dateFuture: tomorrow,
|
||||||
dateToAdvance: today,
|
dateToAdvance: today,
|
||||||
warehouseFk: 1,
|
warehouseFk: 1,
|
||||||
isNotValidated: true
|
isFullMovable: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const ctx = {req: {accessToken: {userId: 9}}, args};
|
const ctx = {req: {accessToken: {userId: 9}}, args};
|
||||||
|
@ -95,7 +95,7 @@ describe('TicketFuture getTicketsAdvance()', () => {
|
||||||
const ctx = {req: {accessToken: {userId: 9}}, args};
|
const ctx = {req: {accessToken: {userId: 9}}, args};
|
||||||
const result = await models.Ticket.getTicketsAdvance(ctx, options);
|
const result = await models.Ticket.getTicketsAdvance(ctx, options);
|
||||||
|
|
||||||
expect(result.length).toBeLessThan(5);
|
expect(result.length).toBeGreaterThan(5);
|
||||||
|
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -120,7 +120,7 @@ describe('TicketFuture getTicketsAdvance()', () => {
|
||||||
const ctx = {req: {accessToken: {userId: 9}}, args};
|
const ctx = {req: {accessToken: {userId: 9}}, args};
|
||||||
const result = await models.Ticket.getTicketsAdvance(ctx, options);
|
const result = await models.Ticket.getTicketsAdvance(ctx, options);
|
||||||
|
|
||||||
expect(result.length).toBeLessThan(5);
|
expect(result.length).toBeGreaterThan(5);
|
||||||
|
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -86,7 +86,7 @@ describe('sale priceDifference()', () => {
|
||||||
const firstItem = result.items[0];
|
const firstItem = result.items[0];
|
||||||
const secondtItem = result.items[1];
|
const secondtItem = result.items[1];
|
||||||
|
|
||||||
expect(firstItem.movable).toEqual(410);
|
expect(firstItem.movable).toEqual(380);
|
||||||
expect(secondtItem.movable).toEqual(1790);
|
expect(secondtItem.movable).toEqual(1790);
|
||||||
|
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
|
|
|
@ -71,7 +71,7 @@ module.exports = Self => {
|
||||||
}, {
|
}, {
|
||||||
relation: 'address',
|
relation: 'address',
|
||||||
scope: {
|
scope: {
|
||||||
fields: ['street', 'city', 'provinceFk', 'phone', 'mobile', 'postalCode'],
|
fields: ['street', 'city', 'provinceFk', 'phone', 'mobile', 'postalCode', 'isEqualizated'],
|
||||||
include: {
|
include: {
|
||||||
relation: 'province',
|
relation: 'province',
|
||||||
scope: {
|
scope: {
|
||||||
|
|
|
@ -41,6 +41,12 @@
|
||||||
"SaleComponent": {
|
"SaleComponent": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
"SaleGroup": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"SaleGroupDetail": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
"SaleTracking": {
|
"SaleTracking": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
|
|
@ -75,6 +75,11 @@
|
||||||
"type": "hasOne",
|
"type": "hasOne",
|
||||||
"model": "ItemShelvingSale",
|
"model": "ItemShelvingSale",
|
||||||
"foreignKey": "saleFk"
|
"foreignKey": "saleFk"
|
||||||
|
},
|
||||||
|
"saleGroupDetail": {
|
||||||
|
"type": "hasOne",
|
||||||
|
"model": "SaleGroupDetail",
|
||||||
|
"foreignKey": "saleFk"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue