7863-devToTest_2434 #2852

Merged
alexm merged 129 commits from 7863-devToTest_2434 into test 2024-08-13 06:50:25 +00:00
147 changed files with 676 additions and 7659 deletions

View File

@ -0,0 +1,50 @@
module.exports = Self => {
Self.remoteMethodCtx('add', {
description: 'Add activity if the activity is different or is the same but have exceed time for break',
accessType: 'WRITE',
accepts: [
{
arg: 'code',
type: 'string',
description: 'Code for activity'
},
{
arg: 'model',
type: 'string',
description: 'Origin model from insert'
},
],
http: {
path: `/add`,
verb: 'POST'
}
});
Self.add = async(ctx, code, model, options) => {
const userId = ctx.req.accessToken.userId;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
return await Self.rawSql(`
INSERT INTO workerActivity (workerFk, workerActivityTypeFk, model)
SELECT ?, ?, ?
FROM workerTimeControlParams wtcp
LEFT JOIN (
SELECT wa.workerFk,
wa.created,
wat.code
FROM workerActivity wa
LEFT JOIN workerActivityType wat ON wat.code = wa.workerActivityTypeFk
WHERE wa.workerFk = ?
ORDER BY wa.created DESC
LIMIT 1
) sub ON TRUE
WHERE sub.workerFk IS NULL
OR sub.code <> ?
OR TIMESTAMPDIFF(SECOND, sub.created, util.VN_NOW()) > wtcp.dayBreak;`
, [userId, code, model, userId, code], myOptions);
};
};

View File

@ -0,0 +1,30 @@
const {models} = require('vn-loopback');
describe('workerActivity insert()', () => {
const ctx = beforeAll.getCtx(1106);
it('should insert in workerActivity', async() => {
const tx = await models.WorkerActivity.beginTransaction({});
let count = 0;
const options = {transaction: tx};
try {
await models.WorkerActivityType.create(
{'code': 'STOP', 'description': 'STOP'}, options
);
await models.WorkerActivity.add(ctx, 'STOP', 'APP', options);
count = await models.WorkerActivity.count(
{'workerFK': 1106}, options
);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
expect(count).toEqual(1);
});
});

View File

@ -28,6 +28,9 @@
"Company": { "Company": {
"dataSource": "vn" "dataSource": "vn"
}, },
"Config": {
"dataSource": "vn"
},
"Continent": { "Continent": {
"dataSource": "vn" "dataSource": "vn"
}, },

22
back/models/config.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "Config",
"base": "VnModel",
"options": {
"mysql": {
"table": "config"
}
},
"properties": {
"inventoried": {
"type": "date"
}
},
"acls": [
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
}
]
}

View File

@ -0,0 +1,3 @@
module.exports = Self => {
require('../methods/workerActivity/add')(Self);
};

View File

@ -22,6 +22,7 @@
}, },
"description": { "description": {
"type": "string" "type": "string"
}
}, },
"relations": { "relations": {
"workerFk": { "workerFk": {
@ -35,5 +36,4 @@
"foreignKey": "workerActivityTypeFk" "foreignKey": "workerActivityTypeFk"
} }
} }
}
} }

View File

@ -1,59 +1,62 @@
DELIMITER $$ DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `hedera`.`order_confirmWithUser`(vSelf INT, vUserId INT) CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `hedera`.`order_confirmWithUser`(
vSelf INT,
vUserFk INT
)
BEGIN BEGIN
/** /**
* Confirms an order, creating each of its tickets on the corresponding * Confirms an order, creating each of its tickets
* date, store and user. * on the corresponding date, store and user.
* *
* @param vSelf The order identifier * @param vSelf The order identifier
* @param vUser The user identifier * @param vUser The user identifier
*/ */
DECLARE vOk BOOL; DECLARE vHasRows BOOL;
DECLARE vDone BOOL DEFAULT FALSE; DECLARE vDone BOOL;
DECLARE vWarehouse INT; DECLARE vWarehouseFk INT;
DECLARE vShipment DATE; DECLARE vShipment DATE;
DECLARE vTicket INT; DECLARE vShipmentDayEnd DATETIME;
DECLARE vTicketFk INT;
DECLARE vNotes VARCHAR(255); DECLARE vNotes VARCHAR(255);
DECLARE vItem INT; DECLARE vItemFk INT;
DECLARE vConcept VARCHAR(30); DECLARE vConcept VARCHAR(30);
DECLARE vAmount INT; DECLARE vAmount INT;
DECLARE vAvailable INT;
DECLARE vPrice DECIMAL(10,2); DECLARE vPrice DECIMAL(10,2);
DECLARE vSale INT; DECLARE vSaleFk INT;
DECLARE vRate INT; DECLARE vRowFk INT;
DECLARE vRowId INT;
DECLARE vPriceFixed DECIMAL(10,2); DECLARE vPriceFixed DECIMAL(10,2);
DECLARE vDelivery DATE; DECLARE vLanded DATE;
DECLARE vAddress INT; DECLARE vAddressFk INT;
DECLARE vIsConfirmed BOOL; DECLARE vClientFk INT;
DECLARE vClientId INT; DECLARE vCompanyFk INT;
DECLARE vCompanyId INT; DECLARE vAgencyModeFk INT;
DECLARE vAgencyModeId INT; DECLARE vCalcFk INT;
DECLARE TICKET_FREE INT DEFAULT 2;
DECLARE vCalc INT;
DECLARE vIsLogifloraItem BOOL;
DECLARE vOldQuantity INT;
DECLARE vNewQuantity INT;
DECLARE vIsTaxDataChecked BOOL; DECLARE vIsTaxDataChecked BOOL;
DECLARE cDates CURSOR FOR DECLARE vDates CURSOR FOR
SELECT zgs.shipped, r.warehouse_id SELECT zgs.shipped, r.warehouseFk
FROM `order` o FROM `order` o
JOIN order_row r ON r.order_id = o.id JOIN orderRow r ON r.orderFk = o.id
LEFT JOIN tmp.zoneGetShipped zgs ON zgs.warehouseFk = r.warehouse_id LEFT JOIN tmp.zoneGetShipped zgs ON zgs.warehouseFk = r.warehouseFk
WHERE o.id = vSelf AND r.amount != 0 WHERE o.id = vSelf
GROUP BY r.warehouse_id; AND r.amount
GROUP BY r.warehouseFk;
DECLARE cRows CURSOR FOR DECLARE vRows CURSOR FOR
SELECT r.id, r.item_id, i.name, r.amount, r.price, r.rate, i.isFloramondo SELECT r.id,
FROM order_row r r.itemFk,
JOIN vn.item i ON i.id = r.item_id i.name,
WHERE r.amount != 0 r.amount,
AND r.warehouse_id = vWarehouse r.price
AND r.order_id = vSelf FROM orderRow r
JOIN vn.item i ON i.id = r.itemFk
WHERE r.amount
AND r.warehouseFk = vWarehouseFk
AND r.orderFk = vSelf
ORDER BY r.rate DESC; ORDER BY r.rate DESC;
DECLARE CONTINUE HANDLER FOR NOT FOUND DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
SET vDone = TRUE;
DECLARE EXIT HANDLER FOR SQLEXCEPTION DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN BEGIN
@ -62,26 +65,36 @@ BEGIN
END; END;
-- Carga los datos del pedido -- Carga los datos del pedido
SELECT o.date_send, o.address_id, o.note, a.clientFk, SELECT o.date_send,
o.company_id, o.agency_id, c.isTaxDataChecked o.address_id,
INTO vDelivery, vAddress, vNotes, vClientId, o.note,
vCompanyId, vAgencyModeId, vIsTaxDataChecked a.clientFk,
FROM hedera.`order` o o.company_id,
o.agency_id,
c.isTaxDataChecked
INTO vLanded,
vAddressFk,
vNotes,
vClientFk,
vCompanyFk,
vAgencyModeFk,
vIsTaxDataChecked
FROM `order` o
JOIN vn.address a ON a.id = o.address_id JOIN vn.address a ON a.id = o.address_id
JOIN vn.client c ON c.id = a.clientFk JOIN vn.client c ON c.id = a.clientFk
WHERE o.id = vSelf; WHERE o.id = vSelf;
-- Verifica si el cliente tiene los datos comprobados -- Verifica si el cliente tiene los datos comprobados
IF NOT vIsTaxDataChecked THEN IF NOT vIsTaxDataChecked THEN
CALL util.throw ('clientNotVerified'); CALL util.throw('clientNotVerified');
END IF; END IF;
-- Carga las fechas de salida de cada almacen -- Carga las fechas de salida de cada almacen
CALL vn.zone_getShipped (vDelivery, vAddress, vAgencyModeId, FALSE); CALL vn.zone_getShipped(vLanded, vAddressFk, vAgencyModeFk, FALSE);
-- Trabajador que realiza la accion -- Trabajador que realiza la accion
IF vUserId IS NULL THEN IF vUserFk IS NULL THEN
SELECT employeeFk INTO vUserId FROM orderConfig; SELECT employeeFk INTO vUserFk FROM orderConfig;
END IF; END IF;
START TRANSACTION; START TRANSACTION;
@ -89,207 +102,188 @@ BEGIN
CALL order_checkEditable(vSelf); CALL order_checkEditable(vSelf);
-- Check order is not empty -- Check order is not empty
SELECT COUNT(*) > 0 INTO vHasRows
FROM orderRow
WHERE orderFk = vSelf
AND amount > 0;
SELECT COUNT(*) > 0 INTO vOk IF NOT vHasRows THEN
FROM order_row WHERE order_id = vSelf AND amount > 0; CALL util.throw('ORDER_EMPTY');
IF NOT vOk THEN
CALL util.throw ('ORDER_EMPTY');
END IF; END IF;
-- Crea los tickets del pedido -- Crea los tickets del pedido
OPEN vDates;
OPEN cDates; lDates: LOOP
SET vTicketFk = NULL;
lDates:
LOOP
SET vTicket = NULL;
SET vDone = FALSE; SET vDone = FALSE;
FETCH cDates INTO vShipment, vWarehouse; FETCH vDates INTO vShipment, vWarehouseFk;
IF vDone THEN IF vDone THEN
LEAVE lDates; LEAVE lDates;
END IF; END IF;
-- Busca un ticket existente que coincida con los parametros SET vShipmentDayEnd = util.dayEnd(vShipment);
WITH tPrevia AS
(SELECT DISTINCT s.ticketFk -- Busca un ticket libre disponible
WITH tPrevia AS (
SELECT DISTINCT s.ticketFk
FROM vn.sale s FROM vn.sale s
JOIN vn.saleGroupDetail sgd ON sgd.saleFk = s.id JOIN vn.saleGroupDetail sgd ON sgd.saleFk = s.id
JOIN vn.ticket t ON t.id = s.ticketFk JOIN vn.ticket t ON t.id = s.ticketFk
WHERE t.shipped BETWEEN vShipment AND util.dayend(vShipment) WHERE t.shipped BETWEEN vShipment AND vShipmentDayEnd
) )
SELECT t.id INTO vTicket SELECT t.id INTO vTicketFk
FROM vn.ticket t FROM vn.ticket t
JOIN vn.alertLevel al ON al.code = 'FREE' JOIN vn.alertLevel al ON al.code = 'FREE'
LEFT JOIN tPrevia tp ON tp.ticketFk = t.id LEFT JOIN tPrevia tp ON tp.ticketFk = t.id
LEFT JOIN vn.ticketState tls on tls.ticketFk = t.id LEFT JOIN vn.ticketState tls ON tls.ticketFk = t.id
JOIN hedera.`order` o JOIN hedera.`order` o ON o.address_id = t.addressFk
ON o.address_id = t.addressFk AND t.shipped BETWEEN vShipment AND vShipmentDayEnd
AND vWarehouse = t.warehouseFk AND t.warehouseFk = vWarehouseFk
AND o.date_send = t.landed AND o.date_send = t.landed
AND DATE(t.shipped) = vShipment
WHERE o.id = vSelf WHERE o.id = vSelf
AND t.refFk IS NULL AND t.refFk IS NULL
AND tp.ticketFk IS NULL AND tp.ticketFk IS NULL
AND (tls.alertLevel IS NULL OR tls.alertLevel = al.id) AND (tls.alertLevel IS NULL OR tls.alertLevel = al.id)
LIMIT 1; LIMIT 1;
-- Comprobamos si hay un ticket de previa disponible
IF vTicketFk IS NULL THEN
WITH tItemPackingTypeOrder AS (
SELECT GROUP_CONCAT(
DISTINCT i.itemPackingTypeFk ORDER BY i.itemPackingTypeFk
) distinctItemPackingTypes,
o.address_id
FROM vn.item i
JOIN hedera.orderRow oro ON oro.itemFk = i.id
JOIN hedera.`order` o ON o.id = oro.orderFk
WHERE oro.orderFk = vSelf
),
tItemPackingTypeTicket AS (
SELECT t.id,
GROUP_CONCAT(
DISTINCT i.itemPackingTypeFk ORDER BY i.itemPackingTypeFk
) distinctItemPackingTypes
FROM vn.ticket t
JOIN vn.ticketState tls ON tls.ticketFk = t.id
JOIN vn.alertLevel al ON al.id = tls.alertLevel
JOIN vn.sale s ON s.ticketFk = t.id
JOIN vn.item i ON i.id = s.itemFk
JOIN tItemPackingTypeOrder ipto
WHERE t.shipped BETWEEN vShipment AND vShipmentDayEnd
AND t.refFk IS NULL
AND t.warehouseFk = vWarehouseFk
AND t.addressFk = ipto.address_id
AND al.code = 'ON_PREVIOUS'
GROUP BY t.id
)
SELECT iptt.id INTO vTicketFk
FROM tItemPackingTypeTicket iptt
JOIN tItemPackingTypeOrder ipto
WHERE INSTR(iptt.distinctItemPackingTypes, ipto.distinctItemPackingTypes)
LIMIT 1;
END IF;
-- Crea el ticket en el caso de no existir uno adecuado -- Crea el ticket en el caso de no existir uno adecuado
IF vTicket IS NULL IF vTicketFk IS NULL THEN
THEN
SET vShipment = IFNULL(vShipment, util.VN_CURDATE()); SET vShipment = IFNULL(vShipment, util.VN_CURDATE());
CALL vn.ticket_add( CALL vn.ticket_add(
vClientId, vClientFk,
vShipment, vShipment,
vWarehouse, vWarehouseFk,
vCompanyId, vCompanyFk,
vAddress, vAddressFk,
vAgencyModeId, vAgencyModeFk,
NULL, NULL,
vDelivery, vLanded,
vUserId, vUserFk,
TRUE, TRUE,
vTicket vTicketFk
); );
ELSE ELSE
INSERT INTO vn.ticketTracking INSERT INTO vn.ticketTracking
SET ticketFk = vTicket, SET ticketFk = vTicketFk,
userFk = vUserId, userFk = vUserFk,
stateFk = TICKET_FREE; stateFk = (SELECT id FROM vn.state WHERE code = 'FREE');
END IF; END IF;
INSERT IGNORE INTO vn.orderTicket INSERT IGNORE INTO vn.orderTicket
SET orderFk = vSelf, SET orderFk = vSelf,
ticketFk = vTicket; ticketFk = vTicketFk;
-- Añade las notas -- Añade las notas
IF vNotes IS NOT NULL AND vNotes <> '' THEN
IF vNotes IS NOT NULL AND vNotes != '' INSERT INTO vn.ticketObservation
THEN SET ticketFk = vTicketFk,
INSERT INTO vn.ticketObservation SET observationTypeFk = (SELECT id FROM vn.observationType WHERE code = 'salesPerson'),
ticketFk = vTicket,
observationTypeFk = 4 /* salesperson */ ,
`description` = vNotes `description` = vNotes
ON DUPLICATE KEY UPDATE ON DUPLICATE KEY UPDATE
`description` = CONCAT(VALUES(`description`),'. ', `description`); `description` = CONCAT(VALUES(`description`),'. ', `description`);
END IF; END IF;
-- Añade los movimientos y sus componentes -- Añade los movimientos y sus componentes
OPEN vRows;
OPEN cRows;
lRows: LOOP lRows: LOOP
SET vSaleFk = NULL;
SET vDone = FALSE; SET vDone = FALSE;
FETCH cRows INTO vRowId, vItem, vConcept, vAmount, vPrice, vRate, vIsLogifloraItem; FETCH vRows INTO vRowFk, vItemFk, vConcept, vAmount, vPrice;
IF vDone THEN IF vDone THEN
LEAVE lRows; LEAVE lRows;
END IF; END IF;
SET vSale = NULL; SELECT s.id INTO vSaleFk
SELECT s.id, s.quantity INTO vSale, vOldQuantity
FROM vn.sale s FROM vn.sale s
WHERE ticketFk = vTicket WHERE ticketFk = vTicketFk
AND price = vPrice AND price = vPrice
AND itemFk = vItem AND itemFk = vItemFk
AND discount = 0 AND discount = 0
LIMIT 1; LIMIT 1;
IF vSale THEN IF vSaleFk THEN
UPDATE vn.sale UPDATE vn.sale
SET quantity = quantity + vAmount, SET quantity = quantity + vAmount,
originalQuantity = quantity originalQuantity = quantity
WHERE id = vSale; WHERE id = vSaleFk;
SELECT s.quantity INTO vNewQuantity
FROM vn.sale s
WHERE id = vSale;
ELSE ELSE
-- Obtiene el coste -- Obtiene el coste
SELECT SUM(rc.`price`) valueSum INTO vPriceFixed SELECT SUM(rc.`price`) valueSum INTO vPriceFixed
FROM orderRowComponent rc FROM orderRowComponent rc
JOIN vn.component c ON c.id = rc.componentFk JOIN vn.component c ON c.id = rc.componentFk
JOIN vn.componentType ct ON ct.id = c.typeFk AND ct.isBase JOIN vn.componentType ct ON ct.id = c.typeFk
WHERE rc.rowFk = vRowId; AND ct.isBase
WHERE rc.rowFk = vRowFk;
INSERT INTO vn.sale INSERT INTO vn.sale
SET itemFk = vItem, SET itemFk = vItemFk,
ticketFk = vTicket, ticketFk = vTicketFk,
concept = vConcept, concept = vConcept,
quantity = vAmount, quantity = vAmount,
price = vPrice, price = vPrice,
priceFixed = vPriceFixed, priceFixed = vPriceFixed,
isPriceFixed = TRUE; isPriceFixed = TRUE;
SET vSale = LAST_INSERT_ID(); SET vSaleFk = LAST_INSERT_ID();
INSERT INTO vn.saleComponent INSERT INTO vn.saleComponent (saleFk, componentFk, `value`)
(saleFk, componentFk, `value`) SELECT vSaleFk, rc.componentFk, rc.price
SELECT vSale, rc.componentFk, rc.price
FROM orderRowComponent rc FROM orderRowComponent rc
JOIN vn.component c ON c.id = rc.componentFk JOIN vn.component c ON c.id = rc.componentFk
WHERE rc.rowFk = vRowId WHERE rc.rowFk = vRowFk
GROUP BY vSale, rc.componentFk; GROUP BY vSaleFk, rc.componentFk;
END IF; END IF;
UPDATE order_row SET Id_Movimiento = vSale UPDATE orderRow
WHERE id = vRowId; SET saleFk = vSaleFk
WHERE id = vRowFk;
-- Inserta en putOrder si la compra es de Floramondo
IF vIsLogifloraItem THEN
CALL cache.availableNoRaids_refresh(vCalc,FALSE,vWarehouse,vShipment);
SET @available := 0;
SELECT GREATEST(0,available) INTO @available
FROM cache.availableNoRaids
WHERE calc_id = vCalc
AND item_id = vItem;
UPDATE cache.availableNoRaids
SET available = GREATEST(0,available - vAmount)
WHERE item_id = vItem
AND calc_id = vCalc;
INSERT INTO edi.putOrder (
deliveryInformationID,
supplyResponseId,
quantity ,
EndUserPartyId,
EndUserPartyGLN,
FHAdminNumber,
saleFk
)
SELECT di.ID,
i.supplyResponseFk,
CEIL((vAmount - @available)/ sr.NumberOfItemsPerCask),
o.address_id ,
vClientId,
IFNULL(ca.fhAdminNumber, fhc.defaultAdminNumber),
vSale
FROM edi.deliveryInformation di
JOIN vn.item i ON i.supplyResponseFk = di.supplyResponseID
JOIN edi.supplyResponse sr ON sr.ID = i.supplyResponseFk
LEFT JOIN edi.clientFHAdminNumber ca ON ca.clientFk = vClientId
JOIN edi.floraHollandConfig fhc
JOIN hedera.`order` o ON o.id = vSelf
WHERE i.id = vItem
AND di.LatestOrderDateTime > util.VN_NOW()
AND vAmount > @available
LIMIT 1;
END IF;
END LOOP; END LOOP;
CLOSE vRows;
CLOSE cRows;
END LOOP; END LOOP;
CLOSE vDates;
CLOSE cDates; UPDATE `order`
SET confirmed = TRUE,
UPDATE `order` SET confirmed = TRUE, confirm_date = util.VN_NOW() confirm_date = util.VN_NOW()
WHERE id = vSelf; WHERE id = vSelf;
COMMIT; COMMIT;

View File

@ -0,0 +1,12 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `salix`.`ACL_afterDelete`
AFTER DELETE ON `ACL`
FOR EACH ROW
BEGIN
INSERT INTO ACL
SET `action` = 'delete',
`changedModel` = 'Acl',
`changedModelId` = OLD.id,
`userFk` = account.myUser_getId();
END$$
DELIMITER ;

View File

@ -0,0 +1,8 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `salix`.`ACL_beforeInsert`
BEFORE INSERT ON `ACL`
FOR EACH ROW
BEGIN
SET NEW.editorFk = account.myUser_getId();
END$$
DELIMITER ;

View File

@ -0,0 +1,8 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `salix`.`ACL_beforeUpdate`
BEFORE UPDATE ON `ACL`
FOR EACH ROW
BEGIN
SET NEW.editorFk = account.myUser_getId();
END$$
DELIMITER ;

View File

@ -189,7 +189,7 @@ BEGIN
SELECT * FROM sales SELECT * FROM sales
UNION ALL UNION ALL
SELECT * FROM orders SELECT * FROM orders
ORDER BY shipped, ORDER BY shipped DESC,
(inventorySupplierFk = entityId) DESC, (inventorySupplierFk = entityId) DESC,
alertLevel DESC, alertLevel DESC,
isTicket, isTicket,
@ -240,7 +240,7 @@ BEGIN
NULL reference, NULL reference,
NULL entityType, NULL entityType,
NULL entityId, NULL entityId,
'Inventario calculado', 'Inventario calculado' entityName,
@a invalue, @a invalue,
NULL `out`, NULL `out`,
@a balance, @a balance,

View File

@ -1,139 +0,0 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`supplier_statement`(
vSupplierFk INT,
vCurrencyFk INT,
vCompanyFk INT,
vOrderBy VARCHAR(15),
vIsConciliated BOOL
)
BEGIN
/**
* Crea un estado de cuenta de proveedores calculando
* los saldos en euros y en la moneda especificada.
*
* @param vSupplierFk Id del proveedor
* @param vCurrencyFk Id de la moneda
* @param vCompanyFk Id de la empresa
* @param vOrderBy Criterio de ordenación
* @param vIsConciliated Indica si está conciliado o no
* @return tmp.supplierStatement
*/
SET @euroBalance:= 0;
SET @currencyBalance:= 0;
CREATE OR REPLACE TEMPORARY TABLE tmp.supplierStatement
ENGINE = MEMORY
SELECT *,
@euroBalance:= ROUND(
@euroBalance + IFNULL(paymentEuros, 0) -
IFNULL(invoiceEuros, 0), 2
) euroBalance,
@currencyBalance:= ROUND(
@currencyBalance + IFNULL(paymentCurrency, 0) -
IFNULL(invoiceCurrency, 0), 2
) currencyBalance
FROM (
SELECT * FROM
(
SELECT NULL bankFk,
ii.companyFk,
ii.serial,
ii.id,
CASE
WHEN vOrderBy = 'issued' THEN ii.issued
WHEN vOrderBy = 'bookEntried' THEN ii.bookEntried
WHEN vOrderBy = 'booked' THEN ii.booked
WHEN vOrderBy = 'dueDate' THEN iid.dueDated
END dated,
CONCAT('S/Fra ', ii.supplierRef) sref,
IF(ii.currencyFk > 1,
ROUND(SUM(iid.foreignValue) / SUM(iid.amount), 3),
NULL
) changeValue,
CAST(SUM(iid.amount) AS DECIMAL(10,2)) invoiceEuros,
CAST(SUM(iid.foreignValue) AS DECIMAL(10,2)) invoiceCurrency,
NULL paymentEuros,
NULL paymentCurrency,
ii.currencyFk,
ii.isBooked,
c.code,
'invoiceIn' statementType
FROM invoiceIn ii
JOIN invoiceInDueDay iid ON iid.invoiceInFk = ii.id
JOIN currency c ON c.id = ii.currencyFk
WHERE ii.issued > '2014-12-31'
AND ii.supplierFk = vSupplierFk
AND vCurrencyFk IN (ii.currencyFk, 0)
AND vCompanyFk IN (ii.companyFk, 0)
AND (vIsConciliated = ii.isBooked OR NOT vIsConciliated)
GROUP BY iid.id
UNION ALL
SELECT p.bankFk,
p.companyFk,
NULL,
p.id,
CASE
WHEN vOrderBy = 'issued' THEN p.received
WHEN vOrderBy = 'bookEntried' THEN p.received
WHEN vOrderBy = 'booked' THEN p.received
WHEN vOrderBy = 'dueDate' THEN p.dueDated
END,
CONCAT(IFNULL(pm.name, ''),
IF(pn.concept <> '',
CONCAT(' : ', pn.concept),
'')
),
IF(p.currencyFk > 1, p.divisa / p.amount, NULL),
NULL,
NULL,
p.amount,
p.divisa,
p.currencyFk,
p.isConciliated,
c.code,
'payment'
FROM payment p
LEFT JOIN currency c ON c.id = p.currencyFk
LEFT JOIN accounting a ON a.id = p.bankFk
LEFT JOIN payMethod pm ON pm.id = p.payMethodFk
LEFT JOIN promissoryNote pn ON pn.paymentFk = p.id
WHERE p.received > '2014-12-31'
AND p.supplierFk = vSupplierFk
AND vCurrencyFk IN (p.currencyFk, 0)
AND vCompanyFk IN (p.companyFk, 0)
AND (vIsConciliated = p.isConciliated OR NOT vIsConciliated)
UNION ALL
SELECT NULL,
companyFk,
NULL,
se.id,
CASE
WHEN vOrderBy = 'issued' THEN se.dated
WHEN vOrderBy = 'bookEntried' THEN se.dated
WHEN vOrderBy = 'booked' THEN se.dated
WHEN vOrderBy = 'dueDate' THEN se.dueDated
END,
se.description,
1,
amount,
NULL,
NULL,
NULL,
currencyFk,
isConciliated,
c.`code`,
'expense'
FROM supplierExpense se
JOIN currency c ON c.id = se.currencyFk
WHERE se.supplierFk = vSupplierFk
AND vCurrencyFk IN (se.currencyFk,0)
AND vCompanyFk IN (se.companyFk,0)
AND (vIsConciliated = se.isConciliated OR NOT vIsConciliated)
) sub
ORDER BY (dated IS NULL AND NOT isBooked),
dated,
IF(vOrderBy = 'dueDate', id, NULL)
LIMIT 10000000000000000000
) t;
END$$
DELIMITER ;

View File

@ -0,0 +1,166 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE vn.supplier_statementWithEntries(
vSupplierFk INT,
vCurrencyFk INT,
vCompanyFk INT,
vOrderBy VARCHAR(15),
vIsConciliated BOOL,
vHasEntries BOOL
)
BEGIN
/**
* Creates a supplier statement, calculating balances in euros and the specified currency.
*
* @param vSupplierFk Supplier ID
* @param vCurrencyFk Currency ID
* @param vCompanyFk Company ID
* @param vOrderBy Order by criteria
* @param vIsConciliated Indicates whether it is reconciled or not
* @param vHasEntries Indicates if future entries must be shown
* @return tmp.supplierStatement
*/
DECLARE vBalanceStartingDate DATETIME;
SET @euroBalance:= 0;
SET @currencyBalance:= 0;
SELECT balanceStartingDate
INTO vBalanceStartingDate
FROM invoiceInConfig;
CREATE OR REPLACE TEMPORARY TABLE tmp.supplierStatement
ENGINE = MEMORY
SELECT *,
@euroBalance:= ROUND(
@euroBalance + IFNULL(paymentEuros, 0) -
IFNULL(invoiceEuros, 0), 2
) euroBalance,
@currencyBalance:= ROUND(
@currencyBalance + IFNULL(paymentCurrency, 0) -
IFNULL(invoiceCurrency, 0), 2
) currencyBalance
FROM (
SELECT NULL bankFk,
ii.companyFk,
ii.serial,
ii.id,
CASE
WHEN vOrderBy = 'issued' THEN ii.issued
WHEN vOrderBy = 'bookEntried' THEN ii.bookEntried
WHEN vOrderBy = 'booked' THEN ii.booked
WHEN vOrderBy = 'dueDate' THEN iid.dueDated
END dated,
CONCAT('S/Fra ', ii.supplierRef) sref,
IF(ii.currencyFk > 1,
ROUND(SUM(iid.foreignValue) / SUM(iid.amount), 3),
NULL
) changeValue,
CAST(SUM(iid.amount) AS DECIMAL(10,2)) invoiceEuros,
CAST(SUM(iid.foreignValue) AS DECIMAL(10,2)) invoiceCurrency,
NULL paymentEuros,
NULL paymentCurrency,
ii.currencyFk,
ii.isBooked,
c.code,
'invoiceIn' statementType
FROM invoiceIn ii
JOIN invoiceInDueDay iid ON iid.invoiceInFk = ii.id
JOIN currency c ON c.id = ii.currencyFk
WHERE ii.issued >= vBalanceStartingDate
AND ii.supplierFk = vSupplierFk
AND vCurrencyFk IN (ii.currencyFk, 0)
AND vCompanyFk IN (ii.companyFk, 0)
AND (vIsConciliated = ii.isBooked OR NOT vIsConciliated)
GROUP BY iid.id
UNION ALL
SELECT p.bankFk,
p.companyFk,
NULL,
p.id,
CASE
WHEN vOrderBy = 'issued' THEN p.received
WHEN vOrderBy = 'bookEntried' THEN p.received
WHEN vOrderBy = 'booked' THEN p.received
WHEN vOrderBy = 'dueDate' THEN p.dueDated
END,
CONCAT(IFNULL(pm.name, ''),
IF(pn.concept <> '',
CONCAT(' : ', pn.concept),
'')
),
IF(p.currencyFk > 1, p.divisa / p.amount, NULL),
NULL,
NULL,
p.amount,
p.divisa,
p.currencyFk,
p.isConciliated,
c.code,
'payment'
FROM payment p
LEFT JOIN currency c ON c.id = p.currencyFk
LEFT JOIN accounting a ON a.id = p.bankFk
LEFT JOIN payMethod pm ON pm.id = p.payMethodFk
LEFT JOIN promissoryNote pn ON pn.paymentFk = p.id
WHERE p.received >= vBalanceStartingDate
AND p.supplierFk = vSupplierFk
AND vCurrencyFk IN (p.currencyFk, 0)
AND vCompanyFk IN (p.companyFk, 0)
AND (vIsConciliated = p.isConciliated OR NOT vIsConciliated)
UNION ALL
SELECT NULL,
companyFk,
NULL,
se.id,
CASE
WHEN vOrderBy = 'issued' THEN se.dated
WHEN vOrderBy = 'bookEntried' THEN se.dated
WHEN vOrderBy = 'booked' THEN se.dated
WHEN vOrderBy = 'dueDate' THEN se.dueDated
END,
se.description,
1,
amount,
NULL,
NULL,
NULL,
currencyFk,
isConciliated,
c.`code`,
'expense'
FROM supplierExpense se
JOIN currency c ON c.id = se.currencyFk
WHERE se.supplierFk = vSupplierFk
AND vCurrencyFk IN (se.currencyFk,0)
AND vCompanyFk IN (se.companyFk,0)
AND (vIsConciliated = se.isConciliated OR NOT vIsConciliated)
UNION ALL
SELECT NULL bankFk,
e.companyFk,
'E' serial,
e.invoiceNumber id,
tr.landed dated,
CONCAT('Ent. ',e.id) sref,
1 / ((e.commission/100)+1) changeValue,
e.invoiceAmount * (1 + (e.commission/100)),
e.invoiceAmount,
NULL,
NULL,
e.currencyFk,
FALSE isBooked,
c.code,
'order'
FROM entry e
JOIN travel tr ON tr.id = e.travelFk
JOIN currency c ON c.id = e.currencyFk
WHERE e.supplierFk = vSupplierFk
AND tr.landed >= CURDATE()
AND e.invoiceInFk IS NULL
AND vHasEntries
ORDER BY (dated IS NULL AND NOT isBooked),
dated,
IF(vOrderBy = 'dueDate', id, NULL)
LIMIT 10000000000000000000
) t;
END$$
DELIMITER ;

View File

@ -4,7 +4,6 @@ CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_cloneWeekly`
vDateTo DATE vDateTo DATE
) )
BEGIN BEGIN
DECLARE vIsDone BOOL;
DECLARE vLanding DATE; DECLARE vLanding DATE;
DECLARE vShipment DATE; DECLARE vShipment DATE;
DECLARE vWarehouseFk INT; DECLARE vWarehouseFk INT;
@ -15,10 +14,17 @@ BEGIN
DECLARE vAgencyModeFk INT; DECLARE vAgencyModeFk INT;
DECLARE vNewTicket INT; DECLARE vNewTicket INT;
DECLARE vYear INT; DECLARE vYear INT;
DECLARE vSalesPersonFK INT; DECLARE vObservationSalesPersonFk INT
DECLARE vItemPicker INT; DEFAULT (SELECT id FROM observationType WHERE code = 'salesPerson');
DECLARE vObservationItemPickerFk INT
DEFAULT (SELECT id FROM observationType WHERE code = 'itemPicker');
DECLARE vEmail VARCHAR(255);
DECLARE vIsDuplicateMail BOOL;
DECLARE vSubject VARCHAR(100);
DECLARE vMessage TEXT;
DECLARE vDone BOOL;
DECLARE rsTicket CURSOR FOR DECLARE vTickets CURSOR FOR
SELECT tt.ticketFk, SELECT tt.ticketFk,
t.clientFk, t.clientFk,
t.warehouseFk, t.warehouseFk,
@ -31,20 +37,14 @@ BEGIN
JOIN tmp.time ti JOIN tmp.time ti
WHERE WEEKDAY(ti.dated) = tt.weekDay; WHERE WEEKDAY(ti.dated) = tt.weekDay;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vIsDone = TRUE; DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
CALL `util`.`time_generate`(vDateFrom,vDateTo); CALL `util`.`time_generate`(vDateFrom, vDateTo);
OPEN rsTicket; OPEN vTickets;
myLoop: LOOP l: LOOP
BEGIN SET vDone = FALSE;
DECLARE vSalesPersonEmail VARCHAR(150); FETCH vTickets INTO
DECLARE vIsDuplicateMail BOOL;
DECLARE vSubject VARCHAR(150);
DECLARE vMessage TEXT;
SET vIsDone = FALSE;
FETCH rsTicket INTO
vTicketFk, vTicketFk,
vClientFk, vClientFk,
vWarehouseFk, vWarehouseFk,
@ -53,11 +53,11 @@ BEGIN
vAgencyModeFk, vAgencyModeFk,
vShipment; vShipment;
IF vIsDone THEN IF vDone THEN
LEAVE myLoop; LEAVE l;
END IF; END IF;
-- busca si el ticket ya ha sido clonado -- Busca si el ticket ya ha sido clonado
IF EXISTS (SELECT TRUE FROM ticket tOrig IF EXISTS (SELECT TRUE FROM ticket tOrig
JOIN sale saleOrig ON tOrig.id = saleOrig.ticketFk JOIN sale saleOrig ON tOrig.id = saleOrig.ticketFk
JOIN saleCloned sc ON sc.saleOriginalFk = saleOrig.id JOIN saleCloned sc ON sc.saleOriginalFk = saleOrig.id
@ -67,7 +67,7 @@ BEGIN
AND tClon.isDeleted = FALSE AND tClon.isDeleted = FALSE
AND DATE(tClon.shipped) = vShipment) AND DATE(tClon.shipped) = vShipment)
THEN THEN
ITERATE myLoop; ITERATE l;
END IF; END IF;
IF vAgencyModeFk IS NULL THEN IF vAgencyModeFk IS NULL THEN
@ -107,15 +107,15 @@ BEGIN
priceFixed, priceFixed,
isPriceFixed) isPriceFixed)
SELECT vNewTicket, SELECT vNewTicket,
saleOrig.itemFk, itemFk,
saleOrig.concept, concept,
saleOrig.quantity, quantity,
saleOrig.price, price,
saleOrig.discount, discount,
saleOrig.priceFixed, priceFixed,
saleOrig.isPriceFixed isPriceFixed
FROM sale saleOrig FROM sale
WHERE saleOrig.ticketFk = vTicketFk; WHERE ticketFk = vTicketFk;
INSERT IGNORE INTO saleCloned(saleOriginalFk, saleClonedFk) INSERT IGNORE INTO saleCloned(saleOriginalFk, saleClonedFk)
SELECT saleOriginal.id, saleClon.id SELECT saleOriginal.id, saleClon.id
@ -152,15 +152,7 @@ BEGIN
attenderFk, attenderFk,
vNewTicket vNewTicket
FROM ticketRequest FROM ticketRequest
WHERE ticketFk =vTicketFk; WHERE ticketFk = vTicketFk;
SELECT id INTO vSalesPersonFK
FROM observationType
WHERE code = 'salesPerson';
SELECT id INTO vItemPicker
FROM observationType
WHERE code = 'itemPicker';
INSERT INTO ticketObservation( INSERT INTO ticketObservation(
ticketFk, ticketFk,
@ -168,7 +160,7 @@ BEGIN
description) description)
VALUES( VALUES(
vNewTicket, vNewTicket,
vSalesPersonFK, vObservationSalesPersonFk,
CONCAT('turno desde ticket: ',vTicketFk)) CONCAT('turno desde ticket: ',vTicketFk))
ON DUPLICATE KEY UPDATE description = ON DUPLICATE KEY UPDATE description =
CONCAT(ticketObservation.description,VALUES(description),' '); CONCAT(ticketObservation.description,VALUES(description),' ');
@ -178,16 +170,17 @@ BEGIN
description) description)
VALUES( VALUES(
vNewTicket, vNewTicket,
vItemPicker, vObservationItemPickerFk,
'ATENCION: Contiene lineas de TURNO') 'ATENCION: Contiene lineas de TURNO')
ON DUPLICATE KEY UPDATE description = ON DUPLICATE KEY UPDATE description =
CONCAT(ticketObservation.description,VALUES(description),' '); CONCAT(ticketObservation.description,VALUES(description),' ');
IF (vLanding IS NULL) THEN IF vLanding IS NULL THEN
SELECT IFNULL(d.notificationEmail, e.email) INTO vEmail
SELECT e.email INTO vSalesPersonEmail
FROM client c FROM client c
JOIN account.emailUser e ON e.userFk = c.salesPersonFk JOIN account.emailUser e ON e.userFk = c.salesPersonFk
LEFT JOIN workerDepartment wd ON wd.workerFk = c.salesPersonFk
LEFT JOIN department d ON d.id = wd.departmentFk
WHERE c.id = vClientFk; WHERE c.id = vClientFk;
SET vSubject = CONCAT('Turnos - No se ha podido clonar correctamente el ticket ', SET vSubject = CONCAT('Turnos - No se ha podido clonar correctamente el ticket ',
@ -199,20 +192,21 @@ BEGIN
SELECT COUNT(*) INTO vIsDuplicateMail SELECT COUNT(*) INTO vIsDuplicateMail
FROM mail FROM mail
WHERE receiver = vSalesPersonEmail WHERE receiver = vEmail
AND subject = vSubject; AND subject = vSubject;
IF NOT vIsDuplicateMail THEN IF NOT vIsDuplicateMail THEN
CALL mail_insert(vSalesPersonEmail, NULL, vSubject, vMessage); CALL mail_insert(vEmail, NULL, vSubject, vMessage);
END IF; END IF;
CALL ticket_setState(vNewTicket, 'FIXING'); CALL ticket_setState(vNewTicket, 'FIXING');
ELSE ELSE
CALL ticketCalculateClon(vNewTicket, vTicketFk); CALL ticketCalculateClon(vNewTicket, vTicketFk);
END IF; END IF;
END;
END LOOP; END LOOP;
CLOSE rsTicket; CLOSE vTickets;
DROP TEMPORARY TABLE IF EXISTS tmp.time, tmp.zoneGetLanded;
DROP TEMPORARY TABLE IF EXISTS
tmp.time,
tmp.zoneGetLanded;
END$$ END$$
DELIMITER ; DELIMITER ;

View File

@ -1,40 +1,41 @@
DELIMITER $$ DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`zone_getAddresses`( CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`zone_getAddresses`(
vSelf INT, vSelf INT,
vLanded DATE vShipped DATE,
vDepartmentFk INT
) )
BEGIN BEGIN
/** /**
* Devuelve un listado de todos los clientes activos * Devuelve un listado de todos los clientes activos
* con consignatarios a los que se les puede * con consignatarios a los que se les puede
* vender producto para esa zona y no tiene un ticket * vender producto para esa zona.
* para ese día.
* *
* @param vSelf Id de zona * @param vSelf Id de zona
* @param vDated Fecha de entrega * @param vShipped Fecha de envio
* @param vDepartmentFk Id de departamento
* @return Un select * @return Un select
*/ */
CALL zone_getPostalCode(vSelf); CALL zone_getPostalCode(vSelf);
WITH notHasTicket AS ( WITH clientWithTicket AS (
SELECT id
FROM vn.client
WHERE id NOT IN (
SELECT clientFk SELECT clientFk
FROM vn.ticket FROM vn.ticket
WHERE landed BETWEEN vLanded AND util.dayEnd(vLanded) WHERE shipped BETWEEN vShipped AND util.dayEnd(vShipped)
) )
) SELECT c.id,
SELECT c.id clientFk,
c.name, c.name,
c.phone, c.phone,
bt.description, bt.description,
c.salesPersonFk, c.salesPersonFk,
u.name username, u.name username,
aai.invoiced, aai.invoiced,
cnb.lastShipped cnb.lastShipped,
cwt.clientFk
FROM vn.client c FROM vn.client c
JOIN notHasTicket ON notHasTicket.id = c.id JOIN vn.worker w ON w.id = c.salesPersonFk
JOIN vn.workerDepartment wd ON wd.workerFk = w.id
JOIN vn.department d ON d.id = wd.departmentFk
LEFT JOIN clientWithTicket cwt ON cwt.clientFk = c.id
LEFT JOIN account.`user` u ON u.id = c.salesPersonFk LEFT JOIN account.`user` u ON u.id = c.salesPersonFk
JOIN vn.`address` a ON a.clientFk = c.id JOIN vn.`address` a ON a.clientFk = c.id
JOIN vn.postCode pc ON pc.code = a.postalCode JOIN vn.postCode pc ON pc.code = a.postalCode
@ -49,6 +50,7 @@ BEGIN
AND c.isActive AND c.isActive
AND ct.code = 'normal' AND ct.code = 'normal'
AND bt.code <> 'worker' AND bt.code <> 'worker'
AND (d.id = vDepartmentFk OR NOT vDepartmentFk)
GROUP BY c.id; GROUP BY c.id;
DROP TEMPORARY TABLE tmp.zoneNodes; DROP TEMPORARY TABLE tmp.zoneNodes;

View File

@ -0,0 +1,3 @@
ALTER TABLE vn.productionConfig
DROP COLUMN scannableCodeType,
DROP COLUMN scannablePreviusCodeType;

View File

@ -0,0 +1,25 @@
CREATE OR REPLACE TABLE `salix`.`ACLLog` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`originFk` int(11) DEFAULT NULL,
`userFk` int(10) unsigned DEFAULT NULL,
`action` set('insert','update','delete','select') NOT NULL,
`creationDate` timestamp NULL DEFAULT current_timestamp(),
`description` text CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci DEFAULT NULL,
`changedModel` enum('Acl') NOT NULL DEFAULT 'Acl',
`oldInstance` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`oldInstance`)),
`newInstance` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`newInstance`)),
`changedModelId` int(11) NOT NULL,
`changedModelValue` varchar(45) DEFAULT NULL,
`summaryId` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `logRateuserFk` (`userFk`),
KEY `ACLLog_changedModel` (`changedModel`,`changedModelId`,`creationDate`),
KEY `ACLLog_originFk` (`originFk`,`creationDate`),
CONSTRAINT `aclUserFk` FOREIGN KEY (`userFk`) REFERENCES `account`.`user` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
ALTER TABLE salix.ACL
ADD editorFk int(10) unsigned DEFAULT NULL NULL;
ALTER TABLE vn.ticket
CHANGE editorFk editorFk int(10) unsigned DEFAULT NULL NULL AFTER risk;

View File

@ -0,0 +1,2 @@
RENAME TABLE vn.silexACL TO vn.silexACL__;
ALTER TABLE vn.silexACL__ COMMENT='@deprecated 2024-08-05 refs #7820';

View File

@ -0,0 +1,2 @@
-- Place your SQL code here
ALTER TABLE vn.invoiceInConfig ADD balanceStartingDate DATE DEFAULT '2015-01-01' NOT NULL;

View File

@ -738,69 +738,6 @@ export default {
worker: 'vn-worker-autocomplete[ng-model="$ctrl.userFk"]', worker: 'vn-worker-autocomplete[ng-model="$ctrl.userFk"]',
saveStateButton: `button[type=submit]` saveStateButton: `button[type=submit]`
}, },
claimsIndex: {
searchResult: 'vn-claim-index vn-card > vn-table > div > vn-tbody > a'
},
claimDescriptor: {
moreMenu: 'vn-claim-descriptor vn-icon-button[icon=more_vert]',
moreMenuDeleteClaim: '.vn-menu [name="deleteClaim"]',
acceptDeleteClaim: '.vn-confirm.shown button[response="accept"]'
},
claimSummary: {
header: 'vn-claim-summary > vn-card > h5',
state: 'vn-claim-summary vn-label-value[label="State"] > section > span',
observation: 'vn-claim-summary vn-horizontal.text',
firstSaleItemId: 'vn-claim-summary vn-horizontal > vn-auto:nth-child(5) vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(1) > span',
firstSaleDescriptorImage: '.vn-popover.shown vn-item-descriptor img',
itemDescriptorPopover: '.vn-popover.shown vn-item-descriptor',
itemDescriptorPopoverItemDiaryButton: '.vn-popover vn-item-descriptor vn-quick-link[icon="icon-transaction"] > a',
firstDevelopmentWorker: 'vn-claim-summary vn-horizontal > vn-auto:nth-child(4) vn-table > div > vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(4) > span',
firstDevelopmentWorkerGoToClientButton: '.vn-popover vn-worker-descriptor vn-quick-link[icon="person"] > a',
firstActionTicketId: 'vn-claim-summary > vn-card > vn-horizontal > vn-auto:nth-child(5) vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > span',
firstActionTicketDescriptor: '.vn-popover.shown vn-ticket-descriptor'
},
claimBasicData: {
claimState: 'vn-claim-basic-data vn-autocomplete[ng-model="$ctrl.claim.claimStateFk"]',
packages: 'vn-input-number[ng-model="$ctrl.claim.packages"]',
saveButton: `button[type=submit]`
},
claimDetail: {
secondItemDiscount: 'vn-claim-detail > vn-vertical > vn-card > vn-vertical > vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(6) > span',
discount: '.vn-popover.shown vn-input-number[ng-model="$ctrl.newDiscount"]',
discoutPopoverMana: '.vn-popover.shown .content > div > vn-horizontal > h5',
addItemButton: 'vn-claim-detail a vn-float-button',
firstClaimableSaleFromTicket: '.vn-dialog.shown vn-tbody > vn-tr',
claimDetailLine: 'vn-claim-detail > vn-vertical > vn-card > vn-vertical > vn-table > div > vn-tbody > vn-tr',
totalClaimed: 'vn-claim-detail > vn-vertical > vn-card > vn-vertical > vn-horizontal > div > vn-label-value:nth-child(2) > section > span',
secondItemDeleteButton: 'vn-claim-detail > vn-vertical > vn-card > vn-vertical > vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td:nth-child(8) > vn-icon-button > button > vn-icon > i'
},
claimDevelopment: {
addDevelopmentButton: 'vn-claim-development > vn-vertical > vn-card > vn-vertical > vn-one > vn-icon-button > button > vn-icon',
firstDeleteDevelopmentButton: 'vn-claim-development > vn-vertical > vn-card > vn-vertical > form > vn-horizontal:nth-child(2) > vn-icon-button > button > vn-icon',
firstClaimReason: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimReasonFk"]',
firstClaimResult: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimResultFk"]',
firstClaimResponsible: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimResponsibleFk"]',
firstClaimWorker: 'vn-claim-development vn-horizontal:nth-child(1) vn-worker-autocomplete[ng-model="claimDevelopment.workerFk"]',
firstClaimRedelivery: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimRedeliveryFk"]',
secondClaimReason: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.claimReasonFk"]',
secondClaimResult: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.claimResultFk"]',
secondClaimResponsible: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.claimResponsibleFk"]',
secondClaimWorker: 'vn-claim-development vn-horizontal:nth-child(2) vn-worker-autocomplete[ng-model="claimDevelopment.workerFk"]',
secondClaimRedelivery: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.claimRedeliveryFk"]',
saveDevelopmentButton: 'button[type=submit]'
},
claimNote: {
addNoteFloatButton: 'vn-float-button',
note: 'vn-textarea[ng-model="$ctrl.note.text"]',
saveButton: 'button[type=submit]',
firstNoteText: 'vn-claim-note .text'
},
claimAction: {
importClaimButton: 'vn-claim-action vn-button[label="Import claim"]',
anyLine: 'vn-claim-action vn-tbody > vn-tr',
firstDeleteLine: 'vn-claim-action tr:nth-child(2) vn-icon-button[icon="delete"]',
isPaidWithManaCheckbox: 'vn-claim-action vn-check[ng-model="$ctrl.claim.isChargedToMana"]'
},
ordersIndex: { ordersIndex: {
secondSearchResultTotal: 'vn-order-index vn-card > vn-table > div > vn-tbody .vn-tr:nth-child(2) vn-td:nth-child(9)', secondSearchResultTotal: 'vn-order-index vn-card > vn-table > div > vn-tbody .vn-tr:nth-child(2) vn-td:nth-child(9)',
advancedSearchButton: 'vn-order-search-panel vn-submit[label="Search"]', advancedSearchButton: 'vn-order-search-panel vn-submit[label="Search"]',

View File

@ -1,29 +0,0 @@
import selectors from '../../../helpers/selectors.js';
import getBrowser from '../../../helpers/puppeteer';
describe('department summary path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('hr', 'worker');
await page.accessToSection('worker.department');
await page.doSearch('INFORMATICA');
await page.click(selectors.department.firstDepartment);
});
afterAll(async() => {
await browser.close();
});
it('should reach the employee summary section and check all properties', async() => {
expect(await page.waitToGetProperty(selectors.departmentSummary.header, 'innerText')).toEqual('INFORMATICA');
expect(await page.getProperty(selectors.departmentSummary.name, 'innerText')).toEqual('INFORMATICA');
expect(await page.getProperty(selectors.departmentSummary.code, 'innerText')).toEqual('it');
expect(await page.getProperty(selectors.departmentSummary.chat, 'innerText')).toEqual('informatica-cau');
expect(await page.getProperty(selectors.departmentSummary.bossDepartment, 'innerText')).toEqual('');
expect(await page.getProperty(selectors.departmentSummary.email, 'innerText')).toEqual('-');
expect(await page.getProperty(selectors.departmentSummary.clientFk, 'innerText')).toEqual('-');
});
});

View File

@ -1,43 +0,0 @@
import getBrowser from '../../../helpers/puppeteer';
import selectors from '../../../helpers/selectors.js';
const $ = {
form: 'vn-worker-department-basic-data form',
};
describe('department summary path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('hr', 'worker');
await page.accessToSection('worker.department');
await page.doSearch('INFORMATICA');
await page.click(selectors.department.firstDepartment);
});
beforeEach(async() => {
await page.accessToSection('worker.department.card.basicData');
});
afterAll(async() => {
await browser.close();
});
it(`should edit the department basic data and confirm the department data was edited`, async() => {
const values = {
Name: 'Informatica',
Code: 'IT',
Chat: 'informatica-cau',
Email: 'it@verdnatura.es',
};
await page.fillForm($.form, values);
const formValues = await page.fetchForm($.form, Object.keys(values));
const message = await page.sendForm($.form, values);
expect(message.isSuccess).toBeTrue();
expect(formValues).toEqual(values);
});
});

View File

@ -1,34 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Worker summary path', () => {
const workerId = 3;
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('employee', 'worker');
const httpDataResponse = page.waitForResponse(response => {
return response.status() === 200 && response.url().includes(`Workers/${workerId}`);
});
await page.accessToSearchResult('agencyNick');
await httpDataResponse;
});
afterAll(async() => {
await browser.close();
});
it('should reach the employee summary section and check all properties', async() => {
expect(await page.getProperty(selectors.workerSummary.header, 'innerText')).toEqual('agency agency');
expect(await page.getProperty(selectors.workerSummary.id, 'innerText')).toEqual('3');
expect(await page.getProperty(selectors.workerSummary.email, 'innerText')).toEqual('agency@verdnatura.es');
expect(await page.getProperty(selectors.workerSummary.department, 'innerText')).toEqual('CAMARA');
expect(await page.getProperty(selectors.workerSummary.userId, 'innerText')).toEqual('3');
expect(await page.getProperty(selectors.workerSummary.userName, 'innerText')).toEqual('agency');
expect(await page.getProperty(selectors.workerSummary.role, 'innerText')).toEqual('agency');
expect(await page.getProperty(selectors.workerSummary.extension, 'innerText')).toEqual('1101');
expect(await page.getProperty(selectors.workerSummary.locker, 'innerText')).toEqual('-');
});
});

View File

@ -1,40 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Worker basic data path', () => {
const workerId = 1106;
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('hr', 'worker');
const httpDataResponse = page.waitForResponse(response => {
return response.status() === 200 && response.url().includes(`Workers/${workerId}`);
});
await page.accessToSearchResult('David Charles Haller');
await httpDataResponse;
await page.accessToSection('worker.card.basicData');
});
afterAll(async() => {
await browser.close();
});
it('should edit the form and then reload the section and check the data was edited', async() => {
await page.overwrite(selectors.workerBasicData.name, 'David C.');
await page.overwrite(selectors.workerBasicData.surname, 'H.');
await page.overwrite(selectors.workerBasicData.phone, '444332211');
await page.click(selectors.workerBasicData.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
await page.reloadSection('worker.card.basicData');
expect(await page.waitToGetProperty(selectors.workerBasicData.name, 'value')).toEqual('David C.');
expect(await page.waitToGetProperty(selectors.workerBasicData.surname, 'value')).toEqual('H.');
expect(await page.waitToGetProperty(selectors.workerBasicData.phone, 'value')).toEqual('444332211');
});
});

View File

@ -1,32 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Worker pbx path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('hr', 'worker');
await page.accessToSearchResult('employee');
await page.accessToSection('worker.card.pbx');
});
afterAll(async() => {
await browser.close();
});
it('should receive an error when the extension exceeds 4 characters and then sucessfully save the changes', async() => {
await page.write(selectors.workerPbx.extension, '55555');
await page.click(selectors.workerPbx.saveButton);
let message = await page.waitForSnackbar();
expect(message.text).toContain('Extension format is invalid');
await page.overwrite(selectors.workerPbx.extension, '4444');
await page.click(selectors.workerPbx.saveButton);
message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved! User must access web');
});
});

View File

@ -1,65 +0,0 @@
/* eslint max-len: ["error", { "code": 150 }]*/
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Worker time control path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('salesBoss', 'worker');
await page.accessToSearchResult('HankPym');
await page.accessToSection('worker.card.timeControl');
});
afterAll(async() => {
await browser.close();
});
const eightAm = '08:00';
const fourPm = '16:00';
const hankPymId = 1107;
it('should go to the next month, go to current month and go 1 month in the past', async() => {
let date = Date.vnNew();
date.setDate(1);
date.setMonth(date.getMonth() + 1);
let month = date.toLocaleString('default', {month: 'long'});
await page.waitToClick(selectors.workerTimeControl.nextMonthButton);
let result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText');
expect(result).toContain(month);
date = Date.vnNew();
date.setDate(1);
month = date.toLocaleString('default', {month: 'long'});
await page.waitToClick(selectors.workerTimeControl.previousMonthButton);
result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText');
expect(result).toContain(month);
date = Date.vnNew();
date.setDate(1);
date.setMonth(date.getMonth() - 1);
const timestamp = Math.round(date.getTime() / 1000);
month = date.toLocaleString('default', {month: 'long'});
await page.loginAndModule('salesBoss', 'worker');
await page.goto(`http://localhost:5000/#!/worker/${hankPymId}/time-control?timestamp=${timestamp}`);
await page.waitToClick(selectors.workerTimeControl.secondWeekDay);
result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText');
expect(result).toContain(month);
});
it('should change week of month', async() => {
await page.click(selectors.workerTimeControl.thrirdWeekDay);
const result = await page.getProperty(selectors.workerTimeControl.mondayWorkedHours, 'innerText');
expect(result).toEqual('00:00 h.');
});
});

View File

@ -1,114 +0,0 @@
/* eslint-disable max-len */
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Worker calendar path', () => {
const reasonableTimeBetweenClicks = 300;
const date = Date.vnNew();
const lastYear = (date.getFullYear() - 1).toString();
let browser;
let page;
async function accessAs(user) {
await page.loginAndModule(user, 'worker');
await page.accessToSearchResult('Charles Xavier');
await page.accessToSection('worker.card.calendar');
}
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
accessAs('hr');
});
afterAll(async() => {
await browser.close();
});
describe('as hr', () => {
it('should set two days as holidays on the calendar and check the total holidays increased by 1.5', async() => {
await page.waitToClick(selectors.workerCalendar.holidays);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.penultimateMondayOfJanuary);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.absence);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.lastMondayOfMarch);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.halfHoliday);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.fistMondayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.furlough);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.secondTuesdayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.secondWednesdayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.secondThursdayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.halfFurlough);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.secondFridayOfJun);
expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 1.5 ');
});
});
describe(`as salesBoss`, () => {
it(`should log in, get to Charles Xavier's calendar, undo what was done here, and check the total holidays used are back to what it was`, async() => {
accessAs('salesBoss');
await page.waitToClick(selectors.workerCalendar.holidays);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.penultimateMondayOfJanuary);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.absence);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.lastMondayOfMarch);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.halfHoliday);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.fistMondayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.furlough);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.secondTuesdayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.secondWednesdayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.secondThursdayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.halfFurlough);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.secondFridayOfJun);
expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 0 ');
});
});
describe(`as Charles Xavier`, () => {
it('should log in and get to his calendar, make a futile attempt to add holidays, check the total holidays used are now the initial ones and use the year selector to go to the previous year', async() => {
accessAs('CharlesXavier');
await page.waitToClick(selectors.workerCalendar.holidays);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.penultimateMondayOfJanuary);
expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 0 ');
await page.autocompleteSearch(selectors.workerCalendar.year, lastYear);
expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 0 ');
});
});
});

View File

@ -1,73 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Worker create path', () => {
let browser;
let page;
let newWorker;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('hr', 'worker');
await page.waitToClick(selectors.workerCreate.newWorkerButton);
await page.waitForState('worker.create');
});
afterAll(async() => {
await browser.close();
});
it('should insert default data', async() => {
await page.write(selectors.workerCreate.firstname, 'Victor');
await page.write(selectors.workerCreate.lastname, 'Von Doom');
await page.write(selectors.workerCreate.fi, '78457139E');
await page.write(selectors.workerCreate.phone, '12356789');
await page.write(selectors.workerCreate.postcode, '46680');
await page.write(selectors.workerCreate.street, 'S/ DOOMSTADT');
await page.write(selectors.workerCreate.email, 'doctorDoom@marvel.com');
await page.write(selectors.workerCreate.iban, 'ES9121000418450200051332');
// should check for autocompleted worker code and worker user name
const workerCode = await page
.waitToGetProperty(selectors.workerCreate.code, 'value');
newWorker = await page
.waitToGetProperty(selectors.workerCreate.user, 'value');
expect(workerCode).toEqual('VVD');
expect(newWorker).toContain('victorvd');
// should fail if necessary data is void
await page.waitToClick(selectors.workerCreate.createButton);
let message = await page.waitForSnackbar();
expect(message.text).toContain('is a required argument');
// should create a new worker and go to worker basic data'
await page.pickDate(selectors.workerCreate.birth, new Date(1962, 8, 5));
await page.autocompleteSearch(selectors.workerCreate.boss, 'deliveryAssistant');
await page.waitToClick(selectors.workerCreate.createButton);
message = await page.waitForSnackbar();
await page.waitForState('worker.card.basicData');
expect(message.text).toContain('Data saved!');
// 'rollback'
await page.loginAndModule('itManagement', 'account');
await page.accessToSearchResult(newWorker);
await page.waitToClick(selectors.accountDescriptor.menuButton);
await page.waitToClick(selectors.accountDescriptor.deactivateUser);
await page.waitToClick(selectors.accountDescriptor.acceptButton);
message = await page.waitForSnackbar();
expect(message.text).toContain('User deactivated!');
await page.waitToClick(selectors.accountDescriptor.menuButton);
await page.waitToClick(selectors.accountDescriptor.disableAccount);
await page.waitToClick(selectors.accountDescriptor.acceptButton);
message = await page.waitForSnackbar();
expect(message.text).toContain('Account disabled!');
});
});

View File

@ -1,42 +0,0 @@
import selectors from '../../helpers/selectors';
import getBrowser from '../../helpers/puppeteer';
describe('Worker Add notes path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('hr', 'worker');
await page.accessToSearchResult('Bruce Banner');
await page.accessToSection('worker.card.note.index');
});
afterAll(async() => {
await browser.close();
});
it(`should reach the notes index`, async() => {
await page.waitForState('worker.card.note.index');
});
it(`should click on the add note button`, async() => {
await page.waitToClick(selectors.workerNotes.addNoteFloatButton);
await page.waitForState('worker.card.note.create');
});
it(`should create a note`, async() => {
await page.waitForSelector(selectors.workerNotes.note);
await page.type(`${selectors.workerNotes.note} textarea`, 'Meeting with Black Widow 21st 9am');
await page.waitToClick(selectors.workerNotes.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should confirm the note was created', async() => {
const result = await page.waitToGetProperty(selectors.workerNotes.firstNoteText, 'innerText');
expect(result).toEqual('Meeting with Black Widow 21st 9am');
});
});

View File

@ -19,7 +19,9 @@ describe('Ticket Edit sale path', () => {
it(`should click on the first sale claim icon to navigate over there`, async() => { it(`should click on the first sale claim icon to navigate over there`, async() => {
await page.waitToClick(selectors.ticketSales.firstSaleClaimIcon); await page.waitToClick(selectors.ticketSales.firstSaleClaimIcon);
await page.waitForState('claim.card.basicData'); await page.waitForNavigation();
await page.goBack();
await page.goBack();
}); });
it('should navigate to the tickets index', async() => { it('should navigate to the tickets index', async() => {
@ -243,29 +245,13 @@ describe('Ticket Edit sale path', () => {
await page.waitToClick(selectors.ticketSales.moreMenu); await page.waitToClick(selectors.ticketSales.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuCreateClaim); await page.waitToClick(selectors.ticketSales.moreMenuCreateClaim);
await page.waitToClick(selectors.globalItems.acceptButton); await page.waitToClick(selectors.globalItems.acceptButton);
await page.waitForState('claim.card.basicData'); await page.waitForNavigation();
});
it('should click on the Claims button of the top bar menu', async() => {
await page.waitToClick(selectors.globalItems.applicationsMenuButton);
await page.waitForSelector(selectors.globalItems.applicationsMenuVisible);
await page.waitToClick(selectors.globalItems.claimsButton);
await page.waitForState('claim.index');
});
it('should search for the claim with id 4', async() => {
await page.accessToSearchResult('4');
await page.waitForState('claim.card.summary');
});
it('should click the Tickets button of the top bar menu', async() => {
await page.waitToClick(selectors.globalItems.applicationsMenuButton);
await page.waitForSelector(selectors.globalItems.applicationsMenuVisible);
await page.waitToClick(selectors.globalItems.ticketsButton);
await page.waitForState('ticket.index');
}); });
it('should search for a ticket then access to the sales section', async() => { it('should search for a ticket then access to the sales section', async() => {
await page.goBack();
await page.goBack();
await page.loginAndModule('salesPerson', 'ticket');
await page.accessToSearchResult('16'); await page.accessToSearchResult('16');
await page.accessToSection('ticket.card.sale'); await page.accessToSection('ticket.card.sale');
}); });

View File

@ -1,61 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Claim edit basic data path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
});
afterAll(async() => {
await browser.close();
});
it(`should log in as claimManager then reach basic data of the target claim`, async() => {
await page.loginAndModule('claimManager', 'claim');
await page.accessToSearchResult('1');
await page.accessToSection('claim.card.basicData');
});
it(`should edit claim state and observation fields`, async() => {
await page.autocompleteSearch(selectors.claimBasicData.claimState, 'Resuelto');
await page.clearInput(selectors.claimBasicData.packages);
await page.write(selectors.claimBasicData.packages, '2');
await page.waitToClick(selectors.claimBasicData.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it(`should have been redirected to the next section of claims as the role is claimManager`, async() => {
await page.waitForState('claim.card.detail');
});
it('should confirm the claim state was edited', async() => {
await page.reloadSection('claim.card.basicData');
await page.waitForSelector(selectors.claimBasicData.claimState);
const result = await page.waitToGetProperty(selectors.claimBasicData.claimState, 'value');
expect(result).toEqual('Resuelto');
});
it('should confirm the claim packages was edited', async() => {
const result = await page
.waitToGetProperty(selectors.claimBasicData.packages, 'value');
expect(result).toEqual('2');
});
it(`should edit the claim to leave it untainted`, async() => {
await page.autocompleteSearch(selectors.claimBasicData.claimState, 'Pendiente');
await page.clearInput(selectors.claimBasicData.packages);
await page.write(selectors.claimBasicData.packages, '0');
await page.waitToClick(selectors.claimBasicData.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
});

View File

@ -1,54 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer.js';
describe('Claim action path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('claimManager', 'claim');
await page.accessToSearchResult('2');
await page.accessToSection('claim.card.action');
});
afterAll(async() => {
await browser.close();
});
it('should import the claim', async() => {
await page.waitToClick(selectors.claimAction.importClaimButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should delete the first line', async() => {
await page.waitToClick(selectors.claimAction.firstDeleteLine);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should refresh the view to check not have lines', async() => {
await page.reloadSection('claim.card.action');
const result = await page.countElement(selectors.claimAction.anyLine);
expect(result).toEqual(0);
});
it('should check the "is paid with mana" checkbox', async() => {
await page.waitToClick(selectors.claimAction.isPaidWithManaCheckbox);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should confirm the "is paid with mana" is checked', async() => {
await page.reloadSection('claim.card.action');
const isPaidWithManaCheckbox = await page.checkboxState(selectors.claimAction.isPaidWithManaCheckbox);
expect(isPaidWithManaCheckbox).toBe('checked');
});
});

View File

@ -1,96 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer.js';
describe('Claim summary path', () => {
let browser;
let page;
const claimId = '4';
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
});
afterAll(async() => {
await browser.close();
});
it('should navigate to the target claim summary section', async() => {
await page.loginAndModule('salesPerson', 'claim');
await page.accessToSearchResult(claimId);
await page.waitForState('claim.card.summary');
});
it(`should display details from the claim and it's client on the top of the header`, async() => {
await page.waitForTextInElement(selectors.claimSummary.header, 'Tony Stark');
const result = await page.waitToGetProperty(selectors.claimSummary.header, 'innerText');
expect(result).toContain('4 -');
expect(result).toContain('Tony Stark');
});
it('should display the claim state', async() => {
const result = await page.waitToGetProperty(selectors.claimSummary.state, 'innerText');
expect(result).toContain('Resuelto');
});
it('should display the observation', async() => {
const result = await page.waitToGetProperty(selectors.claimSummary.observation, 'innerText');
expect(result).toContain('Wisi forensibus mnesarchum in cum. Per id impetus abhorreant');
});
it('should display the claimed line(s)', async() => {
const result = await page.waitToGetProperty(selectors.claimSummary.firstSaleItemId, 'innerText');
expect(result).toContain('2');
});
it(`should click on the first sale ID making the item descriptor visible`, async() => {
const firstItem = selectors.claimSummary.firstSaleItemId;
await page.evaluate(selectors => {
document.querySelector(selectors).scrollIntoView();
}, firstItem);
await page.click(firstItem);
await page.waitImgLoad(selectors.claimSummary.firstSaleDescriptorImage);
const visible = await page.isVisible(selectors.claimSummary.itemDescriptorPopover);
expect(visible).toBeTruthy();
});
it(`should check the url for the item diary link of the descriptor is for the right item id`, async() => {
await page.waitForSelector(selectors.claimSummary.itemDescriptorPopoverItemDiaryButton, {visible: true});
await page.closePopup();
});
it('should display the claim development details', async() => {
const result = await page.waitToGetProperty(selectors.claimSummary.firstDevelopmentWorker, 'innerText');
expect(result).toContain('salesAssistantNick');
});
it(`should click on the first development worker making the worker descriptor visible`, async() => {
await page.waitToClick(selectors.claimSummary.firstDevelopmentWorker);
const visible = await page.isVisible(selectors.claimSummary.firstDevelopmentWorkerGoToClientButton);
expect(visible).toBeTruthy();
});
it(`should check the url for the go to clientlink of the descriptor is for the right client id`, async() => {
await page.waitForSelector(selectors.claimSummary.firstDevelopmentWorkerGoToClientButton, {visible: true});
await page.closePopup();
});
it(`should click on the first action ticket ID making the ticket descriptor visible`, async() => {
await page.waitToClick(selectors.claimSummary.firstActionTicketId);
await page.waitForSelector(selectors.claimSummary.firstActionTicketDescriptor);
const visible = await page.isVisible(selectors.claimSummary.firstActionTicketDescriptor);
expect(visible).toBeTruthy();
});
});

View File

@ -1,58 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer.js';
describe('Claim descriptor path', () => {
let browser;
let page;
const claimId = '1';
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
});
afterAll(async() => {
await browser.close();
});
it('should now navigate to the target claim summary section', async() => {
await page.loginAndModule('salesPerson', 'claim');
await page.accessToSearchResult(claimId);
await page.waitForState('claim.card.summary');
});
it(`should not be able to see the delete claim button of the descriptor more menu`, async() => {
await page.waitToClick(selectors.claimDescriptor.moreMenu);
await page.waitForSelector(selectors.claimDescriptor.moreMenuDeleteClaim, {hidden: true});
});
it(`should log in as claimManager and navigate to the target claim`, async() => {
await page.loginAndModule('claimManager', 'claim');
await page.accessToSearchResult(claimId);
await page.waitForState('claim.card.summary');
});
it(`should be able to see the delete claim button of the descriptor more menu`, async() => {
await page.waitToClick(selectors.claimDescriptor.moreMenu);
await page.waitForSelector(selectors.claimDescriptor.moreMenuDeleteClaim, {visible: true});
});
it(`should delete the claim`, async() => {
await page.waitToClick(selectors.claimDescriptor.moreMenuDeleteClaim);
await page.waitToClick(selectors.claimDescriptor.acceptDeleteClaim);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Claim deleted!');
});
it(`should have been relocated to the claim index`, async() => {
await page.waitForState('claim.index');
});
it(`should search for the deleted claim to find no results`, async() => {
await page.doSearch(claimId);
const nResults = await page.countElement(selectors.claimsIndex.searchResult);
expect(nResults).toEqual(0);
});
});

View File

@ -1,46 +0,0 @@
import selectors from '../../helpers/selectors';
import getBrowser from '../../helpers/puppeteer';
describe('Claim Add note path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('salesPerson', 'claim');
await page.accessToSearchResult('2');
await page.accessToSection('claim.card.note.index');
});
afterAll(async() => {
await browser.close();
});
it(`should reach the claim note index`, async() => {
await page.waitForState('claim.card.note.index');
});
it(`should click on the add new note button`, async() => {
await page.waitToClick(selectors.claimNote.addNoteFloatButton);
await page.waitForState('claim.card.note.create');
});
it(`should create a new note`, async() => {
await page.waitForSelector(selectors.claimNote.note);
await page.type(`${selectors.claimNote.note} textarea`, 'The delivery was unsuccessful');
await page.waitToClick(selectors.claimNote.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it(`should redirect back to the claim notes page`, async() => {
await page.waitForState('claim.card.note.index');
});
it('should confirm the note was created', async() => {
const result = await page.waitToGetProperty(selectors.claimNote.firstNoteText, 'innerText');
expect(result).toEqual('The delivery was unsuccessful');
});
});

View File

@ -41,7 +41,6 @@ async function test() {
`./e2e/paths/03*/*[sS]pec.js`, `./e2e/paths/03*/*[sS]pec.js`,
`./e2e/paths/04*/*[sS]pec.js`, `./e2e/paths/04*/*[sS]pec.js`,
`./e2e/paths/05*/*[sS]pec.js`, `./e2e/paths/05*/*[sS]pec.js`,
`./e2e/paths/06*/*[sS]pec.js`,
`./e2e/paths/07*/*[sS]pec.js`, `./e2e/paths/07*/*[sS]pec.js`,
`./e2e/paths/08*/*[sS]pec.js`, `./e2e/paths/08*/*[sS]pec.js`,
`./e2e/paths/09*/*[sS]pec.js`, `./e2e/paths/09*/*[sS]pec.js`,

View File

@ -1,188 +0,0 @@
<vn-crud-model vn-id="model"
url="ClaimEnds/filter"
link="{claimFk: $ctrl.$params.id}"
data="$ctrl.salesClaimed"
auto-load="true"
on-save="$ctrl.onSave()">
</vn-crud-model>
<vn-crud-model
auto-load="true"
url="ClaimDestinations"
data="claimDestinations">
</vn-crud-model>
<vn-card class="vn-mb-md vn-pa-lg vn-w-lg" style="text-align: right"
ng-if="$ctrl.salesClaimed.length > 0">
<vn-label-value label="Total claimed"
value="{{$ctrl.claimedTotal | currency: 'EUR':2}}">
</vn-label-value>
</vn-card>
<vn-card class="vn-pa-md vn-w-lg">
<smart-table
model="model"
options="$ctrl.smartTableOptions"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-actions>
<section class="header">
<vn-tool-bar class="vn-mb-md">
<vn-button
label="Import claim"
disabled="$ctrl.claim.claimStateFk == $ctrl.resolvedStateId"
vn-http-click="$ctrl.importToNewRefundTicket()"
translate-attr="{title: 'Imports claim details'}">
</vn-button>
<vn-button
label="Change destination"
disabled="$ctrl.checked.length == 0"
ng-click="changeDestination.show()">
</vn-button>
<vn-range
label="Responsability"
min-label="Company"
max-label="Sales/Client"
ng-model="$ctrl.claim.responsibility"
max="$ctrl.maxResponsibility"
min="1"
step="1"
on-change="$ctrl.save({responsibility: value})">
</vn-range>
<vn-check class="right"
vn-one
label="Is paid with mana"
ng-model="$ctrl.claim.isChargedToMana"
on-change="$ctrl.save({isChargedToMana: value})">
</vn-check>
</vn-tool-bar>
</section>
</slot-actions>
<slot-table>
<table model="model">
<thead>
<tr>
<th shrink>
<vn-multi-check
model="model"
check-field="$checked">
</vn-multi-check>
</th>
<th number field="itemFk">Id</th>
<th number field="ticketFk">Ticket</th>
<th field="claimDestinationFk">
<span translate>Destination</span>
</th>
<th expand field="landed">
<span translate>Landed</span>
</th>
<th number field="quantity">
<span translate>Quantity</span>
</th>
<th field="concept">
<span translate>Description</span>
</th>
<th number field="price">
<span translate>Price</span>
</th>
<th number field="discount">
<span translate>Disc.</span>
</th>
<th number field="total">Total</th>
</tr>
</thead>
<tbody>
<tr
ng-repeat="saleClaimed in $ctrl.salesClaimed"
vn-repeat-last on-last="$ctrl.focusLastInput()">
<td>
<vn-check
ng-model="saleClaimed.$checked"
vn-click-stop>
</vn-check>
</td>
<td number>
<vn-span
ng-click="itemDescriptor.show($event, saleClaimed.itemFk)"
class="link">
{{::saleClaimed.itemFk}}
</vn-span>
</td>
<td number>
<vn-span
class="link"
ng-click="ticketDescriptor.show($event, saleClaimed.ticketFk)">
{{::saleClaimed.ticketFk}}
</vn-span>
</td>
<td expand>
<vn-autocomplete vn-one id="claimDestinationFk"
ng-model="saleClaimed.claimDestinationFk"
data="claimDestinations"
on-change="$ctrl.updateDestination(saleClaimed, value)"
fields="['id','description']"
value-field="id"
show-field="description">
</vn-autocomplete>
</td>
<td expand>{{::saleClaimed.landed | date: 'dd/MM/yyyy'}}</td>
<td number>{{::saleClaimed.quantity}}</td>
<td expand>{{::saleClaimed.concept}}</td>
<td number>{{::saleClaimed.price | currency: 'EUR':2}}</td>
<td number>{{::saleClaimed.discount}} %</td>
<td number>{{saleClaimed.total | currency: 'EUR':2}}</td>
<td shrink>
<vn-icon-button
vn-tooltip="Remove line"
icon="delete"
ng-click="$ctrl.removeSales(saleClaimed)"
tabindex="-1">
</vn-icon-button>
</td>
</tr>
</tbody>
</table>
</slot-table>
</smart-table>
<button-bar class="vn-pa-md">
<vn-button
label="Regularize"
disabled="$ctrl.claim.claimStateFk == $ctrl.resolvedStateId"
vn-http-click="$ctrl.regularize()">
</vn-button>
</button-bar>
</vn-card>
<vn-item-descriptor-popover
vn-id="item-descriptor"
warehouse-fk="$ctrl.vnConfig.warehouseFk">
</vn-item-descriptor-popover>
<vn-ticket-descriptor-popover
vn-id="ticket-descriptor">
</vn-ticket-descriptor-popover>
<vn-confirm
vn-id="update-greuge"
question="Insert greuges on client card"
message="Do you want to insert greuges?"
on-accept="$ctrl.onUpdateGreugeAccept()">
</vn-confirm>
<!-- Dialog of change destionation -->
<vn-dialog
vn-id="changeDestination"
on-accept="$ctrl.onResponse()">
<tpl-body>
<section class="SMSDialog">
<h5 class="vn-py-sm">{{$ctrl.$t('Change destination to all selected rows', {total: $ctrl.checked.length})}}</h5>
<vn-horizontal>
<vn-autocomplete vn-one id="claimDestinationFk"
ng-model="$ctrl.newDestination"
data="claimDestinations"
fields="['id','description']"
value-field="id"
show-field="description"
vn-focus>
</vn-autocomplete>
</vn-horizontal>
</section>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Save</button>
</tpl-buttons>
</vn-dialog>

View File

@ -1,233 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.newDestination;
this.filter = {
include: [
{relation: 'sale',
scope: {
fields: ['concept', 'ticketFk', 'price', 'quantity', 'discount', 'itemFk'],
include: {
relation: 'ticket'
}
}
},
{relation: 'claimBeggining'},
{relation: 'claimDestination'}
]
};
this.getResolvedState();
this.maxResponsibility = 5;
this.smartTableOptions = {
activeButtons: {
search: true
},
columns: [
{
field: 'claimDestinationFk',
autocomplete: {
url: 'ClaimDestinations',
showField: 'description',
valueField: 'id'
}
},
{
field: 'landed',
searchable: false
}
]
};
}
exprBuilder(param, value) {
switch (param) {
case 'itemFk':
case 'ticketFk':
case 'claimDestinationFk':
case 'quantity':
case 'price':
case 'discount':
case 'total':
return {[param]: value};
case 'concept':
return {[param]: {like: `%${value}%`}};
case 'landed':
return {[param]: {between: this.dateRange(value)}};
}
}
dateRange(value) {
const minHour = new Date(value);
minHour.setHours(0, 0, 0, 0);
const maxHour = new Date(value);
maxHour.setHours(23, 59, 59, 59);
return [minHour, maxHour];
}
get checked() {
const salesClaimed = this.$.model.data || [];
const checkedSalesClaimed = [];
for (let saleClaimed of salesClaimed) {
if (saleClaimed.$checked)
checkedSalesClaimed.push(saleClaimed);
}
return checkedSalesClaimed;
}
updateDestination(saleClaimed, claimDestinationFk) {
const data = {rows: [saleClaimed], claimDestinationFk: claimDestinationFk};
this.$http.post(`Claims/updateClaimDestination`, data).then(() => {
this.vnApp.showSuccess(this.$t('Data saved!'));
}).catch(e => {
this.$.model.refresh();
throw e;
});
}
removeSales(saleClaimed) {
const params = {sales: [saleClaimed]};
this.$http.post(`ClaimEnds/deleteClamedSales`, params).then(() => {
this.$.model.refresh();
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
getResolvedState() {
const query = `ClaimStates/findOne`;
const params = {
filter: {
where: {
code: 'resolved'
}
}
};
this.$http.get(query, params).then(res =>
this.resolvedStateId = res.data.id
);
}
importToNewRefundTicket() {
let query = `ClaimBeginnings/${this.$params.id}/importToNewRefundTicket`;
return this.$http.post(query).then(() => {
this.$.model.refresh();
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
focusLastInput() {
let inputs = document.querySelectorAll('#claimDestinationFk');
inputs[inputs.length - 1].querySelector('input').focus();
this.calculateTotals();
}
calculateTotals() {
this.claimedTotal = 0;
this.salesClaimed.forEach(sale => {
const price = sale.quantity * sale.price;
const discount = (sale.discount * (sale.quantity * sale.price)) / 100;
this.claimedTotal += price - discount;
});
}
regularize() {
const query = `Claims/${this.$params.id}/regularizeClaim`;
return this.$http.post(query).then(() => {
if (this.claim.responsibility >= Math.ceil(this.maxResponsibility) / 2)
this.$.updateGreuge.show();
else
this.vnApp.showSuccess(this.$t('Data saved!'));
this.card.reload();
});
}
getGreugeTypeId() {
const params = {filter: {where: {code: 'freightPickUp'}}};
const query = `GreugeTypes/findOne`;
return this.$http.get(query, {params}).then(res => {
this.greugeTypeFreightId = res.data.id;
return res;
});
}
getGreugeConfig() {
const query = `GreugeConfigs/findOne`;
return this.$http.get(query).then(res => {
this.freightPickUpPrice = res.data.freightPickUpPrice;
return res;
});
}
onUpdateGreugeAccept() {
const promises = [];
promises.push(this.getGreugeTypeId());
promises.push(this.getGreugeConfig());
return Promise.all(promises).then(() => {
return this.updateGreuge({
clientFk: this.claim.clientFk,
description: this.$t('ClaimGreugeDescription', {
claimId: this.claim.id
}).toUpperCase(),
amount: this.freightPickUpPrice,
greugeTypeFk: this.greugeTypeFreightId,
ticketFk: this.claim.ticketFk
});
});
}
updateGreuge(data) {
return this.$http.post(`Greuges`, data).then(() => {
this.vnApp.showSuccess(this.$t('Data saved!'));
this.vnApp.showMessage(this.$t('Greuge added'));
});
}
save(data) {
const query = `Claims/${this.$params.id}/updateClaimAction`;
this.$http.patch(query, data)
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')));
}
onSave() {
this.vnApp.showSuccess(this.$t('Data saved!'));
}
onResponse() {
const rowsToEdit = [];
for (let row of this.checked)
rowsToEdit.push({id: row.id});
const data = {
rows: rowsToEdit,
claimDestinationFk: this.newDestination
};
const query = `Claims/updateClaimDestination`;
this.$http.post(query, data)
.then(() => {
this.$.model.refresh();
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
}
ngModule.vnComponent('vnClaimAction', {
template: require('./index.html'),
controller: Controller,
bindings: {
claim: '<'
},
require: {
card: '^vnClaimCard'
}
});

View File

@ -1,167 +0,0 @@
import './index.js';
import crudModel from 'core/mocks/crud-model';
describe('claim', () => {
describe('Component vnClaimAction', () => {
let controller;
let $httpBackend;
let $state;
beforeEach(ngModule('claim'));
beforeEach(inject(($componentController, _$state_, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$state = _$state_;
$state.params.id = 1;
controller = $componentController('vnClaimAction', {$element: null});
controller.claim = {ticketFk: 1};
controller.$.model = {refresh: () => {}};
controller.$.addSales = {
hide: () => {},
show: () => {}
};
controller.$.lastTicketsModel = crudModel;
controller.$.lastTicketsPopover = {
hide: () => {},
show: () => {}
};
controller.card = {reload: () => {}};
$httpBackend.expectGET(`ClaimStates/findOne`).respond({});
}));
describe('getResolvedState()', () => {
it('should return the resolved state id', () => {
$httpBackend.expectGET(`ClaimStates/findOne`).respond({id: 1});
controller.getResolvedState();
$httpBackend.flush();
expect(controller.resolvedStateId).toEqual(1);
});
});
describe('calculateTotals()', () => {
it('should calculate the total price of the items claimed', () => {
controller.salesClaimed = [
{quantity: 5, price: 2, discount: 0},
{quantity: 10, price: 2, discount: 0},
{quantity: 10, price: 2, discount: 0}
];
controller.calculateTotals();
expect(controller.claimedTotal).toEqual(50);
});
});
describe('importToNewRefundTicket()', () => {
it('should perform a post query and add lines from a new ticket', () => {
jest.spyOn(controller.$.model, 'refresh');
jest.spyOn(controller.vnApp, 'showSuccess');
$httpBackend.expect('POST', `ClaimBeginnings/1/importToNewRefundTicket`).respond({});
controller.importToNewRefundTicket();
$httpBackend.flush();
expect(controller.$.model.refresh).toHaveBeenCalled();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
describe('regularize()', () => {
it('should perform a post query and reload the claim card', () => {
jest.spyOn(controller.card, 'reload');
jest.spyOn(controller.vnApp, 'showSuccess');
$httpBackend.expect('POST', `Claims/1/regularizeClaim`).respond({});
controller.regularize();
$httpBackend.flush();
expect(controller.card.reload).toHaveBeenCalledWith();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
describe('save()', () => {
it('should perform a patch query and show a success message', () => {
jest.spyOn(controller.vnApp, 'showSuccess');
const data = {pickup: 'agency'};
$httpBackend.expect('PATCH', `Claims/1/updateClaimAction`, data).respond({});
controller.save(data);
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
describe('onUpdateGreugeAccept()', () => {
const greugeTypeId = 7;
const freightPickUpPrice = 11;
it('should make a query and get the greugeTypeId and greuge config', () => {
$httpBackend.expectRoute('GET', `GreugeTypes/findOne`).respond({id: greugeTypeId});
$httpBackend.expectGET(`GreugeConfigs/findOne`).respond({freightPickUpPrice});
controller.onUpdateGreugeAccept();
$httpBackend.flush();
expect(controller.greugeTypeFreightId).toEqual(greugeTypeId);
expect(controller.freightPickUpPrice).toEqual(freightPickUpPrice);
});
it('should perform a insert into greuges', done => {
jest.spyOn(controller, 'getGreugeTypeId').mockReturnValue(new Promise(resolve => {
return resolve({id: greugeTypeId});
}));
jest.spyOn(controller, 'getGreugeConfig').mockReturnValue(new Promise(resolve => {
return resolve({freightPickUpPrice});
}));
jest.spyOn(controller, 'updateGreuge').mockReturnValue(new Promise(resolve => {
return resolve(true);
}));
controller.claim.clientFk = 1101;
controller.claim.id = 11;
controller.onUpdateGreugeAccept().then(() => {
expect(controller.updateGreuge).toHaveBeenCalledWith(jasmine.any(Object));
done();
}).catch(done.fail);
});
});
describe('updateGreuge()', () => {
it('should make a query and then call to showSuccess() and showMessage() methods', () => {
jest.spyOn(controller.vnApp, 'showSuccess');
jest.spyOn(controller.vnApp, 'showMessage');
const freightPickUpPrice = 11;
const greugeTypeId = 7;
const expectedData = {
clientFk: 1101,
description: `claim: ${controller.claim.id}`,
amount: freightPickUpPrice,
greugeTypeFk: greugeTypeId,
ticketFk: controller.claim.ticketFk
};
$httpBackend.expect('POST', `Greuges`, expectedData).respond(200);
controller.updateGreuge(expectedData);
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
expect(controller.vnApp.showMessage).toHaveBeenCalledWith('Greuge added');
});
});
describe('onResponse()', () => {
it('should perform a post query and show a success message', () => {
jest.spyOn(controller.vnApp, 'showSuccess');
$httpBackend.expect('POST', `Claims/updateClaimDestination`).respond({});
controller.onResponse();
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
});
});

View File

@ -1 +0,0 @@
ClaimGreugeDescription: Claim id {{claimId}}

View File

@ -1,13 +0,0 @@
Destination: Destino
Action: Actuaciones
Total claimed: Total Reclamado
Import claim: Importar reclamacion
Imports claim details: Importa detalles de la reclamacion
Regularize: Regularizar
Do you want to insert greuges?: Desea insertar greuges?
Insert greuges on client card: Insertar greuges en la ficha del cliente
Greuge added: Greuge añadido
ClaimGreugeDescription: Reclamación id {{claimId}}
Change destination: Cambiar destino
Change destination to all selected rows: Cambiar destino a {{total}} fila(s) seleccionada(s)
Add observation to all selected clients: Añadir observación a {{total}} cliente(s) seleccionado(s)

View File

@ -1,46 +0,0 @@
vn-claim-action {
.header {
display: flex;
justify-content: space-between;
align-items: center;
align-content: center;
vn-tool-bar {
flex: none
}
.vn-check {
flex: none;
}
}
vn-dialog[vn-id=addSales] {
tpl-body {
width: 950px;
div {
div.buttons {
display: none;
}
vn-table{
min-width: 950px;
}
}
}
}
vn-popover.lastTicketsPopover {
vn-table {
min-width: 650px;
overflow: auto
}
div.ticketList {
overflow: auto;
max-height: 350px;
}
}
.right {
margin-left: 370px;
}
}

View File

@ -1,66 +0,0 @@
<mg-ajax path="Claims/updateClaim/{{patch.params.id}}" options="vnPatch"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.claim"
form="form"
save="patch">
</vn-watcher>
<vn-crud-model
auto-load="true"
url="ClaimStates"
data="claimStates">
</vn-crud-model>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield
label="Client"
ng-model="$ctrl.claim.client.name"
readonly="true">
</vn-textfield>
<vn-textfield
label="Created"
field="::$ctrl.claim.created | date:'yyyy-MM-dd HH:mm'"
readonly="true">
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-worker-autocomplete
disabled="false"
show-field="nickname"
ng-model="$ctrl.claim.workerFk"
departments="['VT']"
label="Attended by">
</vn-worker-autocomplete>
<vn-autocomplete
ng-model="$ctrl.claim.claimStateFk"
data="claimStates"
show-field="description"
value-field="id"
label="Claim state"
order="priority ASC"
vn-focus>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-input-number vn-one
min="0"
type="number"
label="Packages received"
ng-model="$ctrl.claim.packages">
</vn-input-number>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Save">
</vn-submit>
<vn-button
class="cancel"
label="Undo changes"
disabled="!watcher.dataChanged()"
ng-click="watcher.loadOriginalData()">
</vn-button>
</vn-button-bar>
</form>

View File

@ -1,20 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
onSubmit() {
this.$.watcher.submit().then(() => {
if (this.aclService.hasAny(['claimManager']))
this.$state.go('claim.card.detail');
});
}
}
ngModule.vnComponent('vnClaimBasicData', {
template: require('./index.html'),
controller: Controller,
bindings: {
claim: '<'
}
});

View File

@ -1,28 +0,0 @@
import './index.js';
import watcher from 'core/mocks/watcher';
describe('Claim', () => {
describe('Component vnClaimBasicData', () => {
let controller;
let $scope;
beforeEach(ngModule('claim'));
beforeEach(inject(($componentController, $rootScope) => {
$scope = $rootScope.$new();
$scope.watcher = watcher;
const $element = angular.element('<vn-claim-basic-data></vn-claim-basic-data>');
controller = $componentController('vnClaimBasicData', {$element, $scope});
}));
describe('onSubmit()', () => {
it(`should redirect to 'claim.card.detail' state`, () => {
jest.spyOn(controller.aclService, 'hasAny').mockReturnValue(true);
jest.spyOn(controller.$state, 'go');
controller.onSubmit();
expect(controller.$state.go).toHaveBeenCalledWith('claim.card.detail');
});
});
});
});

View File

@ -1,9 +0,0 @@
Contact: Contacto
Claim state: Estado de la reclamación
Is paid with mana: Cargado al maná
Responsability: Responsabilidad
Company: Empresa
Sales/Client: Comercial/Cliente
Pick up: Recoger
When checked will notify to the salesPerson: Cuando se marque enviará una notificación de recogida al comercial
Packages received: Bultos recibidos

View File

@ -1,3 +0,0 @@
vn-claim-basic-data vn-date-picker {
padding-left: 80px;
}

View File

@ -1,5 +0,0 @@
<vn-portal slot="menu">
<vn-claim-descriptor claim="$ctrl.claim"></vn-claim-descriptor>
<vn-left-menu source="card"></vn-left-menu>
</vn-portal>
<ui-view></ui-view>

View File

@ -1,68 +0,0 @@
import ngModule from '../module';
import ModuleCard from 'salix/components/module-card';
class Controller extends ModuleCard {
reload() {
let filter = {
include: [
{
relation: 'worker',
scope: {
fields: ['id'],
include: {
relation: 'user',
scope: {
fields: ['name']
}
}
}
}, {
relation: 'ticket',
scope: {
fields: ['zoneFk', 'addressFk'],
include: [
{
relation: 'zone',
scope: {
fields: ['name']
}
},
{
relation: 'address',
scope: {
fields: ['provinceFk'],
include: {
relation: 'province',
scope: {
fields: ['name']
}
}
}
}]
}
}, {
relation: 'claimState',
scope: {
fields: ['id', 'description']
}
}, {
relation: 'client',
scope: {
fields: ['salesPersonFk', 'name', 'email'],
include: {
relation: 'salesPersonUser'
}
}
}
]
};
this.$http.get(`Claims/${this.$params.id}`, {filter})
.then(res => this.claim = res.data);
}
}
ngModule.vnComponent('vnClaimCard', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,29 +0,0 @@
import './index.js';
describe('Claim', () => {
describe('Component vnClaimCard', () => {
let controller;
let $httpBackend;
let data = {id: 1, name: 'fooName'};
beforeEach(ngModule('claim'));
beforeEach(inject(($componentController, _$httpBackend_, $stateParams) => {
$httpBackend = _$httpBackend_;
let $element = angular.element('<div></div>');
controller = $componentController('vnClaimCard', {$element});
$stateParams.id = data.id;
$httpBackend.whenRoute('GET', 'Claims/:id').respond(data);
}));
it('should request data and set it on the controller', () => {
controller.reload();
$httpBackend.flush();
expect(controller.claim).toEqual(data);
});
});
});

View File

@ -29,9 +29,9 @@ class Controller extends Descriptor {
deleteClaim() { deleteClaim() {
return this.$http.delete(`Claims/${this.claim.id}`) return this.$http.delete(`Claims/${this.claim.id}`)
.then(() => { .then(async() => {
this.vnApp.showSuccess(this.$t('Claim deleted!')); this.vnApp.showSuccess(this.$t('Claim deleted!'));
this.$state.go('claim.index'); window.location.href = await this.vnApp.getUrl(`claim/`);
}); });
} }
} }

View File

@ -53,14 +53,12 @@ describe('Item Component vnClaimDescriptor', () => {
describe('deleteClaim()', () => { describe('deleteClaim()', () => {
it('should perform a query and call showSuccess if the response is accept', () => { it('should perform a query and call showSuccess if the response is accept', () => {
jest.spyOn(controller.vnApp, 'showSuccess'); jest.spyOn(controller.vnApp, 'showSuccess');
jest.spyOn(controller.$state, 'go');
$httpBackend.expectDELETE(`Claims/${claim.id}`).respond(); $httpBackend.expectDELETE(`Claims/${claim.id}`).respond();
controller.deleteClaim(); controller.deleteClaim();
$httpBackend.flush(); $httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalled(); expect(controller.vnApp.showSuccess).toHaveBeenCalled();
expect(controller.$state.go).toHaveBeenCalledWith('claim.index');
}); });
}); });
}); });

View File

@ -1,178 +0,0 @@
<vn-crud-model
vn-id="model"
auto-load="true"
url="ClaimBeginnings"
filter="$ctrl.filter"
data="$ctrl.salesClaimed"
on-data-change="$ctrl.calculateTotals()">
</vn-crud-model>
<vn-card
class="vn-mb-md vn-pa-lg vn-w-lg"
style="text-align: right"
ng-if="$ctrl.salesClaimed.length > 0">
<vn-label-value label="Total"
value="{{$ctrl.paidTotal | currency: 'EUR':2}}">
</vn-label-value>
<vn-label-value label="Total claimed"
value="{{$ctrl.claimedTotal | currency: 'EUR':2}}">
</vn-label-value>
</vn-card>
<vn-data-viewer model="model">
<vn-card>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th center expand>Landed</vn-th>
<vn-th number>Quantity</vn-th>
<vn-th>Claimed</vn-th>
<vn-th>Description</vn-th>
<vn-th number>Price</vn-th>
<vn-th number>Disc.</vn-th>
<vn-th number>Total</vn-th>
<vn-th></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="saleClaimed in $ctrl.salesClaimed" vn-repeat-last>
<vn-td center expand>{{::saleClaimed.sale.ticket.landed | date:'dd/MM/yyyy'}}</vn-td>
<vn-td number>{{::saleClaimed.sale.quantity}}</vn-td>
<vn-td>
<vn-input-number
min="0"
step="1"
disabled="!$ctrl.isRewritable"
ng-model="saleClaimed.quantity"
on-change="$ctrl.setClaimedQuantity(saleClaimed.id, saleClaimed.quantity)"
class="dense">
</vn-input-number>
</vn-td>
<vn-td expand title="{{::saleClaimed.sale.concept}}">
<span
ng-click="itemDescriptor.show($event, saleClaimed.sale.itemFk)"
class="link">
{{::saleClaimed.sale.concept}}
</span>
</vn-td>
<vn-td number>{{::saleClaimed.sale.price | currency: 'EUR':2}}</vn-td>
<vn-td number>
<span ng-class="{'link': $ctrl.isRewritable && $ctrl.isClaimManager}"
translate-attr="{title: $ctrl.isRewritable && $ctrl.isClaimManager ? 'Edit discount' : ''}"
ng-click="$ctrl.showEditPopover($event, saleClaimed)">
{{saleClaimed.sale.discount}} %
</span>
</vn-td>
<vn-td number>
{{$ctrl.getSaleTotal(saleClaimed.sale) | currency: 'EUR':2}}
</vn-td>
<vn-td shrink>
<vn-icon-button
vn-tooltip="Remove sale"
ng-if ="$ctrl.isRewritable"
icon="delete"
ng-click="$ctrl.showDeleteConfirm($index)"
tabindex="-1">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<vn-button
label="Next"
class="next"
ui-sref="claim.card.photos">
</vn-button>
<vn-float-button
icon="add"
ng-if="$ctrl.isRewritable"
ng-click="$ctrl.openAddSalesDialog()"
vn-tooltip="Add sale item" vn-bind="+"
fixed-bottom-right>
</vn-float-button>
<!-- Add Lines Dialog -->
<vn-dialog vn-id="add-sales" class="modal-form">
<tpl-title>
<span translate>Claimable sales from ticket</span> {{$ctrl.claim.ticketFk}}
</tpl-title>
<tpl-body>
<vn-horizontal class="vn-pa-md">
<vn-table>
<vn-thead>
<vn-tr>
<vn-th number>Landed</vn-th>
<vn-th number>Quantity</vn-th>
<vn-th number>Description</vn-th>
<vn-th number>Price</vn-th>
<vn-th number>Disc.</vn-th>
<vn-th number>Total</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr
ng-repeat="sale in $ctrl.salesToClaim"
ng-click="$ctrl.addClaimedSale($index)"
class="clickable">
<vn-td number>{{sale.landed | date: 'dd/MM/yyyy'}}</vn-td>
<vn-td number>{{sale.quantity}}</vn-td>
<vn-td expand title="{{::sale.concept}}">
<span
vn-click-stop="itemDescriptor.show($event, sale.itemFk)"
class="link">
{{sale.itemFk}} - {{sale.concept}}
</span>
</vn-td>
<vn-td number>{{sale.price | currency: 'EUR':2}}</vn-td>
<vn-td number>{{sale.discount}} %</vn-td>
<vn-td number>
{{(sale.quantity * sale.price) - ((sale.discount * (sale.quantity * sale.price))/100) | currency: 'EUR':2}}
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-horizontal>
</tpl-body>
</vn-dialog>
<vn-item-descriptor-popover
vn-id="item-descriptor"
warehouse-fk="$ctrl.vnConfig.warehouseFk">
</vn-item-descriptor-popover>
<vn-popover
class="edit"
vn-id="edit-popover"
on-open="$ctrl.getSalespersonMana()"
on-close="$ctrl.mana = null">
<div class="discount-popover">
<vn-spinner
ng-if="$ctrl.mana == null"
style="padding: 1em;"
enable="true">
</vn-spinner>
<div ng-if="$ctrl.mana != null">
<vn-horizontal class="header vn-pa-md">
<h5>MANÁ: {{$ctrl.mana | currency: 'EUR':0}}</h5>
</vn-horizontal>
<div class="vn-pa-md">
<vn-input-number
vn-focus
label="Discount"
ng-model="$ctrl.newDiscount"
type="text"
step="0.01"
on-change="$ctrl.updateDiscount()"
suffix="€">
</vn-input-number>
<div class="simulator">
<p class="simulatorTitle" translate>Total claimed price</p>
<p>{{$ctrl.newPrice | currency: 'EUR':2}}
</p>
</div>
</div>
</div>
</div>
</vn-popover>
<vn-confirm
vn-id="confirm"
question="Delete sale from claim?"
on-accept="$ctrl.deleteClaimedSale()">
</vn-confirm>

View File

@ -1,203 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
constructor($element, $) {
super($element, $);
this.edit = {};
this.filter = {
where: {claimFk: this.$params.id},
include: [
{
relation: 'sale',
scope: {
fields: ['concept', 'ticketFk', 'price', 'quantity', 'discount', 'itemFk'],
include: {
relation: 'ticket'
}
}
}
]
};
}
get claim() {
return this._claim;
}
set claim(value) {
this._claim = value;
if (value) {
this.isClaimEditable();
this.isTicketEditable();
}
}
set salesClaimed(value) {
this._salesClaimed = value;
if (value) this.calculateTotals();
}
get salesClaimed() {
return this._salesClaimed;
}
get newDiscount() {
return this._newDiscount;
}
set newDiscount(value) {
this._newDiscount = value;
this.updateNewPrice();
}
get isClaimManager() {
return this.aclService.hasAny(['claimManager']);
}
openAddSalesDialog() {
this.getClaimableFromTicket();
this.$.addSales.show();
}
getClaimableFromTicket() {
let config = {params: {ticketFk: this.claim.ticketFk}};
let query = `Sales/getClaimableFromTicket`;
this.$http.get(query, config).then(res => {
if (res.data)
this.salesToClaim = res.data;
});
}
addClaimedSale(index) {
let sale = this.salesToClaim[index];
let saleToAdd = {saleFk: sale.saleFk, claimFk: this.claim.id, quantity: sale.quantity};
let query = `ClaimBeginnings/`;
this.$http.post(query, saleToAdd).then(() => {
this.$.addSales.hide();
this.$.model.refresh();
this.vnApp.showSuccess(this.$t('Data saved!'));
if (this.aclService.hasAny(['claimManager']))
this.$state.go('claim.card.development');
});
}
showDeleteConfirm($index) {
this.claimedIndex = $index;
this.$.confirm.show();
}
deleteClaimedSale() {
this.$.model.remove(this.claimedIndex);
this.$.model.save().then(() => {
this.vnApp.showSuccess(this.$t('Data saved!'));
this.calculateTotals();
});
}
setClaimedQuantity(id, claimedQuantity) {
let params = {quantity: claimedQuantity};
let query = `ClaimBeginnings/${id}`;
this.$http.patch(query, params).then(() => {
this.vnApp.showSuccess(this.$t('Data saved!'));
this.calculateTotals();
});
}
calculateTotals() {
this.paidTotal = 0.0;
this.claimedTotal = 0.0;
if (!this._salesClaimed) return;
this._salesClaimed.forEach(sale => {
let orgSale = sale.sale;
this.paidTotal += this.getSaleTotal(orgSale);
const price = sale.quantity * orgSale.price;
const discount = ((orgSale.discount * price) / 100);
this.claimedTotal += price - discount;
});
}
getSaleTotal(sale) {
let total = 0.0;
const price = sale.quantity * sale.price;
const discount = ((sale.discount * price) / 100);
total += price - discount;
return total;
}
getSalespersonMana() {
this.$http.get(`Tickets/${this.claim.ticketFk}/getSalesPersonMana`).then(res => {
this.mana = res.data;
});
}
isTicketEditable() {
if (!this.claim) return;
this.$http.get(`Tickets/${this.claim.ticketFk}/isEditable`).then(res => {
this.isEditable = res.data;
});
}
isClaimEditable() {
if (!this.claim) return;
this.$http.get(`ClaimStates/${this.claim.claimStateFk}/isEditable`).then(res => {
this.isRewritable = res.data;
});
}
showEditPopover(event, saleClaimed) {
if (this.aclService.hasAny(['claimManager'])) {
this.saleClaimed = saleClaimed;
this.$.editPopover.parent = event.target;
this.$.editPopover.show();
}
}
updateDiscount() {
const claimedSale = this.saleClaimed.sale;
if (this.newDiscount != claimedSale.discount) {
const params = {salesIds: [claimedSale.id], newDiscount: this.newDiscount};
const query = `Tickets/${claimedSale.ticketFk}/updateDiscount`;
this.$http.post(query, params).then(() => {
claimedSale.discount = this.newDiscount;
this.calculateTotals();
this.clearDiscount();
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
this.$.editPopover.hide();
}
updateNewPrice() {
this.newPrice = (this.saleClaimed.quantity * this.saleClaimed.sale.price) -
((this.newDiscount * (this.saleClaimed.quantity * this.saleClaimed.sale.price)) / 100);
}
clearDiscount() {
this.newDiscount = null;
}
}
Controller.$inject = ['$element', '$scope'];
ngModule.vnComponent('vnClaimDetail', {
template: require('./index.html'),
controller: Controller,
bindings: {
claim: '<'
}
});

View File

@ -1,150 +0,0 @@
import './index.js';
import crudModel from 'core/mocks/crud-model';
describe('claim', () => {
describe('Component vnClaimDetail', () => {
let $scope;
let controller;
let $httpBackend;
beforeEach(ngModule('claim'));
beforeEach(inject(($componentController, _$httpBackend_, $rootScope) => {
$scope = $rootScope.$new();
$scope.descriptor = {
show: () => {}
};
$httpBackend = _$httpBackend_;
$httpBackend.whenGET('Claims/ClaimBeginnings').respond({});
$httpBackend.whenGET(`Tickets/1/isEditable`).respond(true);
$httpBackend.whenGET(`ClaimStates/2/isEditable`).respond(true);
const $element = angular.element('<vn-claim-detail></vn-claim-detail>');
controller = $componentController('vnClaimDetail', {$element, $scope});
controller.claim = {
ticketFk: 1,
id: 2,
claimStateFk: 2}
;
controller.salesToClaim = [{saleFk: 1}, {saleFk: 2}];
controller.salesClaimed = [{id: 1, sale: {}}];
controller.$.model = crudModel;
controller.$.addSales = {
hide: () => {},
show: () => {}
};
controller.$.editPopover = {
hide: () => {}
};
jest.spyOn(controller.aclService, 'hasAny').mockReturnValue(true);
}));
describe('openAddSalesDialog()', () => {
it('should call getClaimableFromTicket and $.addSales.show', () => {
jest.spyOn(controller, 'getClaimableFromTicket');
jest.spyOn(controller.$.addSales, 'show');
controller.openAddSalesDialog();
expect(controller.getClaimableFromTicket).toHaveBeenCalledWith();
expect(controller.$.addSales.show).toHaveBeenCalledWith();
});
});
describe('getClaimableFromTicket()', () => {
it('should make a query and set salesToClaim', () => {
$httpBackend.expectGET(`Sales/getClaimableFromTicket?ticketFk=1`).respond(200, 1);
controller.getClaimableFromTicket();
$httpBackend.flush();
expect(controller.salesToClaim).toEqual(1);
});
});
describe('addClaimedSale(index)', () => {
it('should make a post and call refresh, hide and showSuccess', () => {
jest.spyOn(controller.$.addSales, 'hide');
jest.spyOn(controller.$state, 'go');
$httpBackend.expectPOST(`ClaimBeginnings/`).respond({});
controller.addClaimedSale(1);
$httpBackend.flush();
expect(controller.$.addSales.hide).toHaveBeenCalledWith();
expect(controller.$state.go).toHaveBeenCalledWith('claim.card.development');
});
});
describe('deleteClaimedSale()', () => {
it('should make a delete and call refresh and showSuccess', () => {
const claimedIndex = 1;
controller.claimedIndex = claimedIndex;
jest.spyOn(controller.$.model, 'remove');
jest.spyOn(controller.$.model, 'save');
jest.spyOn(controller.vnApp, 'showSuccess');
controller.deleteClaimedSale();
expect(controller.$.model.remove).toHaveBeenCalledWith(claimedIndex);
expect(controller.$.model.save).toHaveBeenCalledWith();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
describe('setClaimedQuantity(id, claimedQuantity)', () => {
it('should make a patch and call refresh and showSuccess', () => {
const id = 1;
const claimedQuantity = 1;
jest.spyOn(controller.vnApp, 'showSuccess');
$httpBackend.expectPATCH(`ClaimBeginnings/${id}`).respond({});
controller.setClaimedQuantity(id, claimedQuantity);
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
describe('calculateTotals()', () => {
it('should set paidTotal and claimedTotal to 0 if salesClaimed has no data', () => {
controller.salesClaimed = [];
controller.calculateTotals();
expect(controller.paidTotal).toEqual(0);
expect(controller.claimedTotal).toEqual(0);
});
});
describe('updateDiscount()', () => {
it('should perform a query if the new discount differs from the claim discount', () => {
controller.saleClaimed = {sale: {
discount: 5,
id: 7,
ticketFk: 1,
price: 2,
quantity: 10}};
controller.newDiscount = 10;
jest.spyOn(controller.vnApp, 'showSuccess');
jest.spyOn(controller, 'calculateTotals');
jest.spyOn(controller, 'clearDiscount');
jest.spyOn(controller.$.editPopover, 'hide');
$httpBackend.when('POST', 'Tickets/1/updateDiscount').respond({});
controller.updateDiscount();
$httpBackend.flush();
expect(controller.calculateTotals).toHaveBeenCalledWith();
expect(controller.clearDiscount).toHaveBeenCalledWith();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
expect(controller.$.editPopover.hide).toHaveBeenCalledWith();
});
});
describe('isTicketEditable()', () => {
it('should check if the ticket assigned to the claim is editable', () => {
controller.isTicketEditable();
$httpBackend.flush();
expect(controller.isEditable).toBeTruthy();
});
});
});
});

View File

@ -1,11 +0,0 @@
Claimed: Reclamados
Disc.: Dto.
Attended by: Atendida por
Landed: F. entrega
Price: Precio
Claimable sales from ticket: Lineas reclamables del ticket
Detail: Detalles
Add sale item: Añadir artículo
Insuficient permisos: Permisos insuficientes
Total claimed price: Precio total reclamado
Delete sale from claim?: ¿Borrar la linea de la reclamación?

View File

@ -1,30 +0,0 @@
@import "variables";
.vn-popover .discount-popover {
width: 256px;
.header {
background-color: $color-main;
color: $color-font-dark;
h5 {
color: inherit;
margin: 0 auto;
}
}
.simulatorTitle {
margin-bottom: 0;
font-size: .75rem;
color: $color-main;
}
vn-label-value {
padding-bottom: 20px;
}
.simulator{
text-align: center;
}
}
.next{
float: right;
}

View File

@ -1,2 +0,0 @@
<vn-card>
</vn-card>

View File

@ -1,21 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
class Controller extends Section {
constructor($element, $) {
super($element, $);
}
async $onInit() {
this.$state.go('claim.card.summary', {id: this.$params.id});
window.location.href = await this.vnApp.getUrl(`claim/${this.$params.id}/development`);
}
}
ngModule.vnComponent('vnClaimDevelopment', {
template: require('./index.html'),
controller: Controller,
bindings: {
claim: '<'
}
});

View File

@ -1,16 +1,4 @@
export * from './module'; export * from './module';
import './main'; import './main';
import './index/';
import './action';
import './basic-data';
import './card';
import './detail';
import './descriptor'; import './descriptor';
import './development';
import './search-panel';
import './summary';
import './photos';
import './log';
import './note/index';
import './note/create';

View File

@ -1,83 +0,0 @@
<vn-auto-search
model="model">
</vn-auto-search>
<vn-card>
<smart-table
model="model"
options="$ctrl.smartTableOptions"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-table>
<table>
<thead>
<tr>
<th field="clientFk" shrink>
<span translate>Id</span>
</th>
<th field="clientName">
<span translate>Client</span>
</th>
<th field="created" center shrink-date>
<span translate>Created</span>
</th>
<th field="workerFk">
<span translate>Worker</span>
</th>
<th field="claimStateFk">
<span translate>State</span>
</th>
<th></th>
</tr>
</thead>
<tbody>
<tr
ng-repeat="claim in model.data"
vn-anchor="::{
state: 'claim.card.summary',
params: {id: claim.id}
}">
<td>{{::claim.id}}</td>
<td>
<span
vn-click-stop="clientDescriptor.show($event, claim.clientFk)"
class="link">
{{::claim.clientName}}
</span>
</td>
<td center shrink-date>{{::claim.created | date:'dd/MM/yyyy'}}</td>
<td>
<span
vn-click-stop="workerDescriptor.show($event, claim.workerFk)"
class="link" >
{{::claim.workerName}}
</span>
</td>
<td>
<span class="chip {{::$ctrl.stateColor(claim.stateCode)}}">
{{::claim.stateDescription}}
</span>
</td>
<td shrink>
<vn-icon-button
vn-click-stop="$ctrl.preview(claim)"
vn-tooltip="Preview"
icon="preview">
</vn-icon-button>
</td>
</tr>
</tbody>
</table>
</slot-table>
</smart-table>
</vn-card>
<vn-client-descriptor-popover
vn-id="clientDescriptor">
</vn-client-descriptor-popover>
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>
<vn-popup vn-id="summary">
<vn-claim-summary
claim="$ctrl.claimSelected"
parent-reload="$ctrl.reload()">
</vn-claim-summary>
</vn-popup>

View File

@ -1,82 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
class Controller extends Section {
constructor($element, $) {
super($element, $);
this.smartTableOptions = {
activeButtons: {
search: true
},
columns: [
{
field: 'clientName',
autocomplete: {
url: 'Clients',
showField: 'name',
valueField: 'name'
}
},
{
field: 'workerFk',
autocomplete: {
url: 'Workers/activeWithInheritedRole',
where: `{role: 'salesPerson'}`,
searchFunction: '{firstName: $search}',
showField: 'name',
valueField: 'id',
}
},
{
field: 'claimStateFk',
autocomplete: {
url: 'ClaimStates',
showField: 'description',
valueField: 'id',
}
},
{
field: 'created',
searchable: false
}
]
};
}
exprBuilder(param, value) {
switch (param) {
case 'clientName':
return {'cl.clientName': {like: `%${value}%`}};
case 'clientFk':
case 'claimStateFk':
case 'workerFk':
return {[`cl.${param}`]: value};
}
}
stateColor(code) {
switch (code) {
case 'pending':
return 'warning';
case 'managed':
return 'notice';
case 'resolved':
return 'success';
}
}
preview(claim) {
this.claimSelected = claim;
this.$.summary.show();
}
reload() {
this.$.model.refresh();
}
}
ngModule.vnComponent('vnClaimIndex', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,4 +0,0 @@
<vn-log
url="ClaimLogs"
origin-id="$ctrl.$params.id">
</vn-log>

View File

@ -1,7 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
ngModule.vnComponent('vnClaimLog', {
template: require('./index.html'),
controller: Section,
});

View File

@ -1,19 +0,0 @@
<vn-crud-model
vn-id="model"
url="Claims/filter"
limit="20"
order="priority ASC, created DESC"
auto-load="true">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
vn-focus
panel="vn-claim-search-panel"
info="Search claim by id or client name"
model="model">
</vn-searchbar>
</vn-portal>
<vn-portal slot="menu">
<vn-left-menu></vn-left-menu>
</vn-portal>
<ui-view></ui-view>

View File

@ -1,7 +1,18 @@
import ngModule from '../module'; import ngModule from '../module';
import ModuleMain from 'salix/components/module-main'; import ModuleMain from 'salix/components/module-main';
export default class Claim extends ModuleMain {
constructor($element, $) {
super($element, $);
}
async $onInit() {
this.$state.go('home');
window.location.href = await this.vnApp.getUrl(`Claim/`);
}
}
ngModule.vnComponent('vnClaim', { ngModule.vnComponent('vnClaim', {
controller: ModuleMain, controller: Claim,
template: require('./index.html') template: require('./index.html')
}); });

View File

@ -1,30 +0,0 @@
<vn-watcher
vn-id="watcher"
url="claimObservations"
id-field="id"
data="$ctrl.note"
insert-mode="true"
form="form">
</vn-watcher>
<form name="form" ng-submit="watcher.submitGo('claim.card.note.index')" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textarea
vn-one
label="Note"
ng-model="$ctrl.note.text"
vn-focus>
</vn-textarea>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit
ng-if="watcher.dataChanged()"
label="Save">
</vn-submit>
<vn-button
ng-click="$ctrl.cancel()"
label="Cancel">
</vn-button>
</vn-button-bar>
</form>

View File

@ -1,22 +0,0 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.note = {
claimFk: parseInt(this.$params.id),
workerFk: window.localStorage.currentUserWorkerId,
text: null
};
}
cancel() {
this.$state.go('claim.card.note.index', {id: this.$params.id});
}
}
ngModule.vnComponent('vnClaimNoteCreate', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,32 +0,0 @@
<vn-crud-model
vn-id="model"
url="ClaimObservations"
filter="$ctrl.filter"
where="{claimFk: $ctrl.$params.id}"
include="$ctrl.include"
data="notes"
auto-load="true">
</vn-crud-model>
<vn-data-viewer
model="model"
class="vn-w-md">
<vn-card class="vn-pa-md">
<div
ng-repeat="note in notes"
class="note vn-pa-sm border-solid border-radius vn-mb-md">
<vn-horizontal class="vn-mb-sm" style="color: #666">
<vn-one>{{::note.worker.firstName}} {{::note.worker.lastName}}</vn-one>
<vn-auto>{{::note.created | date:'dd/MM/yyyy HH:mm'}}</vn-auto>
</vn-horizontal>
<vn-horizontal class="text">
{{::note.text}}
</vn-horizontal>
</div>
</vn-card>
</vn-data-viewer>
<a vn-tooltip="New note"
ui-sref="claim.card.note.create({id: $ctrl.$params.id})"
vn-bind="+"
fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>
</a>

View File

@ -1,25 +0,0 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
import './style.scss';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.filter = {
order: 'created DESC',
};
this.include = {
relation: 'worker',
scope: {
fields: ['id', 'firstName', 'lastName']
}
};
}
}
Controller.$inject = ['$element', '$scope'];
ngModule.vnComponent('vnClaimNote', {
template: require('./index.html'),
controller: Controller,
});

View File

@ -1,5 +0,0 @@
vn-client-note {
.note:last-child {
margin-bottom: 0;
}
}

View File

@ -1 +0,0 @@

View File

@ -1,21 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
class Controller extends Section {
constructor($element, $) {
super($element, $);
}
async $onInit() {
const url = await this.vnApp.getUrl(`claim/${this.$params.id}/photos`);
window.location.href = url;
}
}
ngModule.vnComponent('vnClaimPhotos', {
template: require('./index.html'),
controller: Controller,
bindings: {
claim: '<'
}
});

View File

@ -1,84 +0,0 @@
<div class="search-panel">
<form ng-submit="$ctrl.onSearch()">
<vn-horizontal>
<vn-textfield
vn-one
label="General search"
ng-model="filter.search"
info="Search claim by id or client name"
vn-focus>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Client Id"
ng-model="filter.clientFk">
</vn-textfield>
<vn-textfield
vn-one
label="Client"
ng-model="filter.clientName">
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-worker-autocomplete
vn-one
ng-model="filter.salesPersonFk"
departments="['VT']"
label="Salesperson">
</vn-worker-autocomplete>
<vn-worker-autocomplete
vn-one
ng-model="filter.attenderFk"
departments="['VT']"
label="Attended by">
</vn-worker-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
label="State"
ng-model="filter.claimStateFk"
url="ClaimStates"
show-field="description"
value-field="id">
<tpl-item>{{description}}</tpl-item>
</vn-autocomplete>
<vn-date-picker
vn-one
label="Created"
ng-model="filter.created">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one class="dense"
label="Item"
url="Items/withName"
ng-model="filter.itemFk"
show-field="name"
value-field="id"
search-function="$ctrl.itemSearchFunc($search)"
order="id DESC">
<tpl-item>{{::id}} - {{::name}}</tpl-item>
</vn-autocomplete>
<vn-autocomplete
vn-one
ng-model="filter.claimResponsibleFk"
url="ClaimResponsibles"
show-field="description"
value-field="id"
label="Responsible">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-check
vn-one
label="My team"
ng-model="filter.myTeam"
triple-state="true">
</vn-horizontal>
<vn-horizontal class="vn-mt-lg">
<vn-submit label="Search"></vn-submit>
</vn-horizontal>
</form>
</div>

View File

@ -1,14 +0,0 @@
import ngModule from '../module';
import SearchPanel from 'core/components/searchbar/search-panel';
class Controller extends SearchPanel {
itemSearchFunc($search) {
return /^\d+$/.test($search)
? {id: $search}
: {name: {like: '%' + $search + '%'}};
}
}
ngModule.vnComponent('vnClaimSearchPanel', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,7 +0,0 @@
Ticket id: Id ticket
Client id: Id cliente
Nickname: Alias
From: Desde
To: Hasta
Agency: Agencia
Warehouse: Almacén

View File

@ -1,273 +0,0 @@
<vn-crud-model
vn-id="model"
url="ClaimDms"
filter="::$ctrl.filter"
data="photos">
</vn-crud-model>
<vn-card class="summary">
<h5>
<a
ng-if="::$ctrl.summary.claim.id"
vn-tooltip="Go to the claim"
ui-sref="claim.card.summary({id: {{::$ctrl.summary.claim.id}}})"
name="goToSummary">
<vn-icon-button icon="launch"></vn-icon-button>
</a>
<span>{{::$ctrl.summary.claim.id}} - {{::$ctrl.summary.claim.client.name}}</span>
<vn-button-menu
disabled="!$ctrl.summary.isEditable"
class="message"
label="Change state"
value-field="id"
show-field="description"
url="claimStates"
on-change="$ctrl.changeState(value)">
</vn-button-menu>
</h5>
<vn-horizontal>
<vn-auto>
<h4>
<a
ui-sref="claim.card.basicData({id:$ctrl.claim.id})"
target="_self">
<span translate vn-tooltip="Go to">Basic data</span>
</a>
</h4>
<vn-label-value
label="Created"
value="{{$ctrl.summary.claim.created | date: 'dd/MM/yyyy'}}">
</vn-label-value>
<vn-label-value
label="State"
value="{{$ctrl.summary.claim.claimState.description}}">
</vn-label-value>
<vn-label-value
label="Salesperson"
value="{{$ctrl.summary.claim.client.salesPersonUser.name}}">
</vn-label-value>
<vn-label-value
label="Attended by"
value="{{$ctrl.summary.claim.worker.user.nickname}}">
</vn-label-value>
</vn-auto>
<vn-auto>
<h4 ng-show="$ctrl.isSalesPerson && $ctrl.summary.observations.length">
<a
ui-sref="claim.card.note.index({id:$ctrl.claim.id})"
target="_self">
<span translate vn-tooltip="Go to">Observations</span>
</a>
</h4>
<h4
ng-show="!$ctrl.isSalesPerson && $ctrl.summary.observations.length"
translate>
Observations
</h4>
<div
ng-repeat="note in $ctrl.summary.observations"
class="note vn-pa-sm border-solid border-radius vn-mb-md">
<vn-horizontal class="vn-mb-sm" style="color: #666">
<vn-one>{{::note.worker.firstName}} {{::note.worker.lastName}}</vn-one>
<vn-auto>{{::note.created | date:'dd/MM/yyyy HH:mm'}}</vn-auto>
</vn-horizontal>
<vn-horizontal class="text">
{{::note.text}}
</vn-horizontal>
</div>
</vn-auto>
<vn-auto>
<h4 ng-show="$ctrl.isSalesPerson">
<a
ui-sref="claim.card.detail({id:$ctrl.claim.id})"
target="_self">
<span translate vn-tooltip="Go to">Detail</span>
</a>
</h4>
<h4
ng-show="!$ctrl.isSalesPerson"
translate>
Detail
</h4>
<vn-data-viewer data="::$ctrl.summary.salesClaimed">
<vn-table>
<vn-thead>
<vn-tr>
<vn-th number>Item</vn-th>
<vn-th expand>Landed</vn-th>
<vn-th number>Quantity</vn-th>
<vn-th number>Claimed</vn-th>
<vn-th>Description</vn-th>
<vn-th number>Price</vn-th>
<vn-th number>Disc.</vn-th>
<vn-th number>Total</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="saleClaimed in $ctrl.summary.salesClaimed">
<vn-td number>
<span
ng-click="itemDescriptor.show($event, saleClaimed.sale.itemFk, saleClaimed.sale.id)"
class="link">
{{::saleClaimed.sale.itemFk}}
</span>
</vn-td>
<vn-td expand>{{::saleClaimed.sale.ticket.landed | date: 'dd/MM/yyyy'}}</vn-td>
<vn-td number>{{::saleClaimed.sale.quantity}}</vn-td>
<vn-td number>{{::saleClaimed.quantity}}</vn-td>
<vn-td expand>{{::saleClaimed.sale.concept}}</vn-td>
<vn-td number>{{::saleClaimed.sale.price | currency: 'EUR':2}}</vn-td>
<vn-td number>{{::saleClaimed.sale.discount}} %</vn-td>
<vn-td number>
{{saleClaimed.sale.quantity * saleClaimed.sale.price *
((100 - saleClaimed.sale.discount) / 100) | currency: 'EUR':2}}
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-data-viewer>
</vn-auto>
<vn-auto ng-if="photos.length > 0">
<h4 translate>Photos</h4>
<vn-horizontal class="photo-list">
<section class="photo" ng-repeat="photo in photos">
<section class="image" on-error-src
ng-style="{'background': 'url(' + $ctrl.getImagePath(photo.dmsFk) + ')'}"
zoom-image="{{$ctrl.getImagePath(photo.dmsFk)}}"
ng-if="photo.dms.contentType != 'video/mp4'">
</section>
<video id="videobcg" muted="muted" controls ng-if="photo.dms.contentType == 'video/mp4'"
class="video">
<source src="{{$ctrl.getImagePath(photo.dmsFk)}}" type="video/mp4">
</video>
</section>
</vn-horizontal>
</vn-auto>
<vn-auto>
<h4 ng-show="$ctrl.isClaimManager">
<a
ui-sref="claim.card.development({id:$ctrl.claim.id})"
target="_self">
<span translate vn-tooltip="Go to">Development</span>
</a>
</h4>
<h4
translate
ng-show="!$ctrl.isClaimManager">
Development
</h4>
<vn-data-viewer data="::$ctrl.summary.developments">
<vn-table>
<vn-thead>
<vn-tr>
<vn-th>Reason</vn-th>
<vn-th>Result</vn-th>
<vn-th>Responsible</vn-th>
<vn-th>Worker</vn-th>
<vn-th>Redelivery</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="development in $ctrl.summary.developments">
<vn-td>{{::development.claimReason.description}}</vn-td>
<vn-td>{{::development.claimResult.description}}</vn-td>
<vn-td>{{::development.claimResponsible.description}}</vn-td>
<vn-td expand>
<span
class="link"
ng-click="workerDescriptor.show($event, development.workerFk)">
{{::development.worker.user.nickname}}
</span>
</vn-td>
<vn-td>{{::development.claimRedelivery.description}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-data-viewer>
</vn-auto>
<vn-auto>
<h4 ng-show="$ctrl.isClaimManager">
<a
ui-sref="claim.card.action({id:$ctrl.claim.id})"
target="_self">
<span translate vn-tooltip="Go to">Action</span>
</a>
</h4>
<h4
translate
ng-show="!$ctrl.isClaimManager">
Action
</h4>
<vn-horizontal>
<vn-one>
<vn-range
vn-one
disabled="true"
label="Responsability"
min-label="Company"
max-label="Sales/Client"
ng-model="$ctrl.summary.claim.responsibility"
max="5"
min="1"
step="1"
vn-acl="claimManager">
</vn-range>
</vn-one>
</vn-horizontal>
<vn-data-viewer data="::$ctrl.summary.actions">
<vn-table>
<vn-thead>
<vn-tr>
<vn-th number>Item</vn-th>
<vn-th number>Ticket</vn-th>
<vn-th>Destination</vn-th>
<vn-th number>Landed</vn-th>
<vn-th number>Quantity</vn-th>
<vn-th>Description</vn-th>
<vn-th number>Price</vn-th>
<vn-th number>Disc.</vn-th>
<vn-th number>Total</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="action in $ctrl.summary.actions">
<vn-td number>
<span
ng-click="itemDescriptor.show($event, action.sale.itemFk, action.sale.id)"
class="link">
{{::action.sale.itemFk}}
</span>
</vn-td>
<vn-td number>
<span
ng-click="ticketDescriptor.show($event, action.sale.ticket.id)"
class="link">
{{::action.sale.ticket.id}}
</span>
</vn-td>
<vn-td expand>{{::action.claimBeggining.description}}</vn-td>
<vn-td number>{{::action.sale.ticket.landed | date: 'dd/MM/yyyy'}}</vn-td>
<vn-td number>{{::action.sale.quantity}}</vn-td>
<vn-td expand>{{::action.sale.concept}}</vn-td>
<vn-td number>{{::action.sale.price}}</vn-td>
<vn-td number>{{::action.sale.discount}} %</vn-td>
<vn-td number>
{{action.sale.quantity * action.sale.price *
((100 - action.sale.discount) / 100) | currency: 'EUR':2}}
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-data-viewer>
</vn-auto>
</vn-horizontal>
</vn-card>
<vn-item-descriptor-popover
vn-id="item-descriptor"
warehouse-fk="$ctrl.vnConfig.warehouseFk">
</vn-item-descriptor-popover>
<vn-worker-descriptor-popover
vn-id="worker-descriptor">
</vn-worker-descriptor-popover>
<vn-ticket-descriptor-popover
vn-id="ticket-descriptor">
</vn-ticket-descriptor-popover>

View File

@ -1,103 +0,0 @@
import ngModule from '../module';
import Summary from 'salix/components/summary';
import './style.scss';
class Controller extends Summary {
constructor($element, $, vnFile) {
super($element, $);
this.vnFile = vnFile;
this.filter = {
include: [
{
relation: 'dms'
}
]
};
}
$onChanges() {
if (this.claim && this.claim.id)
this.loadData();
}
loadData() {
return this.$http.get(`Claims/${this.claim.id}/getSummary`).then(res => {
if (res && res.data)
this.summary = res.data;
});
}
reload() {
this.loadData()
.then(() => {
if (this.card)
this.card.reload();
if (this.parentReload)
this.parentReload();
});
}
get isSalesPerson() {
return this.aclService.hasAny(['salesPerson']);
}
get isClaimManager() {
return this.aclService.hasAny(['claimManager']);
}
get claim() {
return this._claim;
}
set claim(value) {
this._claim = value;
// Get DMS on summary load
if (value) {
this.$.$applyAsync(() => this.loadDms());
this.loadData();
}
}
loadDms() {
this.$.model.where = {
claimFk: this.claim.id
};
this.$.model.refresh();
}
getImagePath(dmsId) {
return this.vnFile.getPath(`/api/dms/${dmsId}/downloadFile`);
}
changeState(value) {
const params = {
id: this.claim.id,
claimStateFk: value
};
this.$http.patch(`Claims/updateClaim/${this.claim.id}`, params)
.then(() => {
this.reload();
})
.then(() => {
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
}
Controller.$inject = ['$element', '$scope', 'vnFile'];
ngModule.vnComponent('vnClaimSummary', {
template: require('./index.html'),
controller: Controller,
bindings: {
claim: '<',
model: '<?',
parentReload: '&'
},
require: {
card: '?^vnClaimCard'
}
});

View File

@ -1,55 +0,0 @@
import './index.js';
import crudModel from 'core/mocks/crud-model';
describe('Claim', () => {
describe('Component summary', () => {
let controller;
let $httpBackend;
let $scope;
beforeEach(ngModule('claim'));
beforeEach(inject(($componentController, _$httpBackend_, $rootScope) => {
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
const $element = angular.element('<vn-claim-summary></vn-claim-summary>');
controller = $componentController('vnClaimSummary', {$element, $scope});
controller.claim = {id: 1};
controller.$.model = crudModel;
}));
describe('loadData()', () => {
it('should perform a query to set summary', () => {
$httpBackend.when('GET', `Claims/1/getSummary`).respond(200, 24);
controller.loadData();
$httpBackend.flush();
expect(controller.summary).toEqual(24);
});
});
describe('changeState()', () => {
it('should make an HTTP post query, then call the showSuccess()', () => {
jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis();
const expectedParams = {id: 1, claimStateFk: 1};
$httpBackend.when('GET', `Claims/1/getSummary`).respond(200, 24);
$httpBackend.expect('PATCH', `Claims/updateClaim/1`, expectedParams).respond(200);
controller.changeState(1);
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
describe('$onChanges()', () => {
it('should call loadData when $onChanges is called', () => {
jest.spyOn(controller, 'loadData');
controller.$onChanges();
expect(controller.loadData).toHaveBeenCalledWith();
});
});
});
});

View File

@ -1,28 +0,0 @@
@import "./variables";
vn-claim-summary {
section.photo {
height: 248px;
}
.photo .image {
border-radius: 3px;
}
vn-textarea *{
height: 80px;
}
.video {
width: 100%;
height: 100%;
object-fit: cover;
cursor: pointer;
box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),
0 3px 1px -2px rgba(0,0,0,.2),
0 1px 5px 0 rgba(0,0,0,.12);
border: 2px solid transparent;
}
.video:hover {
border: 2px solid $color-primary
}
}

View File

@ -119,6 +119,7 @@ module.exports = Self => {
} }
let correctings; let correctings;
let correcteds; let correcteds;
if (args.correctedFk) { if (args.correctedFk) {
correctings = await models.InvoiceInCorrection.find({ correctings = await models.InvoiceInCorrection.find({
@ -154,6 +155,7 @@ module.exports = Self => {
case 'awbCode': case 'awbCode':
return {'sub.code': value}; return {'sub.code': value};
case 'correctingFk': case 'correctingFk':
if (!correcteds.length && !args.correctingFk) return;
return args.correctingFk return args.correctingFk
? {'ii.id': {inq: correcteds.map(x => x.correctingFk)}} ? {'ii.id': {inq: correcteds.map(x => x.correctingFk)}}
: {'ii.id': {nin: correcteds.map(x => x.correctingFk)}}; : {'ii.id': {nin: correcteds.map(x => x.correctingFk)}};

View File

@ -59,6 +59,10 @@ module.exports = Self => {
arg: 'showEmpty', arg: 'showEmpty',
type: 'boolean', type: 'boolean',
description: 'Show empty orders' description: 'Show empty orders'
}, {
arg: 'sourceApp',
type: 'string',
description: 'Application'
} }
], ],
returns: { returns: {

View File

@ -81,7 +81,7 @@
</vn-check> </vn-check>
</vn-td> </vn-td>
<vn-td shrink> <vn-td shrink>
<a ui-sref="claim.card.basicData({id: sale.claim.claimFk})"> <a ng-click="$ctrl.goToLilium('basic-data', sale.claim.claimFk)">
<vn-icon icon="icon-claims" <vn-icon icon="icon-claims"
ng-show="sale.claim.claimFk" ng-show="sale.claim.claimFk"
vn-tooltip="{{::$ctrl.$t('Claim')}}: {{::sale.claim.claimFk}}"> vn-tooltip="{{::$ctrl.$t('Claim')}}: {{::sale.claim.claimFk}}">

View File

@ -214,7 +214,7 @@ class Controller extends Section {
const params = {ticketId: this.ticket.id, sales: sales}; const params = {ticketId: this.ticket.id, sales: sales};
this.resetChanges(); this.resetChanges();
this.$http.post(`Claims/createFromSales`, params) this.$http.post(`Claims/createFromSales`, params)
.then(res => this.$state.go('claim.card.basicData', {id: res.data.id})); .then(async res => window.location.href = await this.vnApp.getUrl(`claim/${res.data.id}/basic-data`));
} }
showTransferPopover(event) { showTransferPopover(event) {
@ -558,6 +558,10 @@ class Controller extends Section {
changedModelId: saleId changedModelId: saleId
}); });
} }
async goToLilium(section, id) {
window.location.href = await this.vnApp.getUrl(`claim/${id}/${section}`);
}
} }
ngModule.vnComponent('vnTicketSale', { ngModule.vnComponent('vnTicketSale', {

View File

@ -295,20 +295,26 @@ describe('Ticket', () => {
describe('onCreateClaimAccepted()', () => { describe('onCreateClaimAccepted()', () => {
it('should perform a query and call window open', () => { it('should perform a query and call window open', () => {
jest.spyOn(controller, 'resetChanges').mockReturnThis(); jest.spyOn(controller, 'resetChanges').mockReturnThis();
jest.spyOn(controller.$state, 'go').mockReturnThis(); jest.spyOn(controller.vnApp, 'getUrl').mockReturnThis();
Object.defineProperty(window, 'location', {
value: {
href: () => {}
},
});
jest.spyOn(controller.window.location, 'href');
const newEmptySale = {quantity: 10}; const newEmptySale = {quantity: 10};
controller.sales.push(newEmptySale); controller.sales.push(newEmptySale);
const firstSale = controller.sales[0]; const firstSale = controller.sales[0];
const claimId = 1;
firstSale.checked = true; firstSale.checked = true;
const expectedParams = {ticketId: 1, sales: [firstSale]}; const expectedParams = {ticketId: 1, sales: [firstSale]};
$httpBackend.expect('POST', `Claims/createFromSales`, expectedParams).respond(200, {id: 1}); $httpBackend.expect('POST', `Claims/createFromSales`, expectedParams).respond(200, {id: claimId});
controller.onCreateClaimAccepted(); controller.onCreateClaimAccepted();
$httpBackend.flush(); $httpBackend.flush();
expect(controller.resetChanges).toHaveBeenCalledWith(); expect(controller.resetChanges).toHaveBeenCalledWith();
expect(controller.$state.go).toHaveBeenCalledWith('claim.card.basicData', {id: 1});
}); });
}); });

View File

@ -152,13 +152,13 @@
<vn-tbody> <vn-tbody>
<vn-tr ng-repeat="sale in $ctrl.summary.sales track by sale.id"> <vn-tr ng-repeat="sale in $ctrl.summary.sales track by sale.id">
<vn-td shrink> <vn-td shrink>
<a ui-sref="claim.card.basicData({id: sale.claim.claimFk})"> <a ng-click="$ctrl.goToLilium('basic-data', sale.claim.claimFk)">
<vn-icon icon="icon-claims" <vn-icon icon="icon-claims"
ng-show="sale.claim.claimFk" ng-show="sale.claim.claimFk"
vn-tooltip="{{::$ctrl.$t('Claim')}}: {{::sale.claim.claimFk}}"> vn-tooltip="{{::$ctrl.$t('Claim')}}: {{::sale.claim.claimFk}}">
</vn-icon> </vn-icon>
</a> </a>
<a ui-sref="claim.card.basicData({id: sale.claimBeginning.claimFk})"> <a ng-click="$ctrl.goToLilium('basic-data', sale.claimBeginning.claimFk)">
<vn-icon <vn-icon
ng-show="sale.claimBeginning.claimFk" ng-show="sale.claimBeginning.claimFk"
icon="icon-claims" icon="icon-claims"

View File

@ -83,6 +83,10 @@ class Controller extends Summary {
return 'Acepted'; return 'Acepted';
} }
} }
async goToLilium(section, id) {
window.location.href = await this.vnApp.getUrl(`claim/${id}/${section}`);
}
} }
ngModule.vnComponent('vnTicketSummary', { ngModule.vnComponent('vnTicketSummary', {

View File

@ -1,93 +0,0 @@
<mg-ajax path="Workers/{{patch.params.id}}" options="vnPatch"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.worker"
form="form"
save="patch">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-vertical>
<vn-horizontal>
<vn-textfield
vn-one
label="Name"
ng-model="$ctrl.worker.firstName"
rule>
</vn-textfield>
<vn-textfield
vn-one
label="Last name"
ng-model="$ctrl.worker.lastName"
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Business phone"
ng-model="$ctrl.worker.phone"
rule>
</vn-textfield>
<vn-textfield
vn-one
label="Mobile extension"
ng-model="$ctrl.worker.mobileExtension"
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-worker-autocomplete
ng-model="$ctrl.worker.bossFk"
show-field="nickname"
label="Boss">
</vn-worker-autocomplete>
<vn-autocomplete
label="Marital status"
data="$ctrl.maritalStatus"
show-field="name"
value-field="code"
ng-model="$ctrl.worker.maritalStatus">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
ng-model="$ctrl.worker.originCountryFk"
url="Countries"
fields="['id', 'name', 'code']"
show-field="name"
value-field="id"
label="Origin country">
</vn-autocomplete>
<vn-autocomplete
ng-model="$ctrl.worker.educationLevelFk"
url="EducationLevels"
fields="['id', 'name']"
show-field="name"
value-field="id"
label="Education level">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="SSN"
ng-model="$ctrl.worker.SSN"
rule>
</vn-textfield>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Save">
</vn-submit>
<vn-button
class="cancel"
label="Undo changes"
disabled="!watcher.dataChanged()"
ng-click="watcher.loadOriginalData()">
</vn-button>
</vn-button-bar>
</form>

View File

@ -1,27 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
class Controller extends Section {
constructor($element, $) {
super($element, $);
this.maritalStatus = [
{code: 'M', name: this.$t('Married')},
{code: 'S', name: this.$t('Single')}
];
}
onSubmit() {
return this.$.watcher.submit()
.then(() => this.card.reload());
}
}
ngModule.vnComponent('vnWorkerBasicData', {
template: require('./index.html'),
controller: Controller,
bindings: {
worker: '<'
},
require: {
card: '^vnWorkerCard'
}
});

View File

@ -1,9 +0,0 @@
Marital status: Estado civil
Origin country: País origen
Education level: Nivel educación
SSN: NSS
Married: Casado/a
Single: Soltero/a
Business phone: Teléfono de empresa
Mobile extension: Extensión móvil
Locker: Taquilla

View File

@ -1,114 +0,0 @@
<vn-crud-model
url="AbsenceTypes"
data="absenceTypes"
auto-load="true">
</vn-crud-model>
<div ng-if="$ctrl.card.hasWorkCenter">
<div class="vn-w-lg">
<vn-card class="vn-pa-sm calendars">
<vn-icon ng-if="::$ctrl.isSubordinate" icon="info" color-marginal
vn-tooltip="To start adding absences, click an absence type from the right menu and then on the day you want to add an absence">
</vn-icon>
<vn-calendar
ng-repeat="month in $ctrl.months"
data="$ctrl.events"
default-date="month"
format-day="$ctrl.formatDay($day, $element)"
display-controls="false"
hide-contiguous="true"
hide-year="true"
on-selection="$ctrl.onSelection($event, $days)">
</vn-calendar>
</vn-card>
</div>
</div>
<div
ng-if="!$ctrl.card.hasWorkCenter"
class="bg-title"
translate>
Autonomous worker
</div>
<vn-side-menu side="right">
<div class="vn-pa-md">
<div class="totalBox vn-mb-sm" style="text-align: center;">
<h6>{{'Contract' | translate}} #{{$ctrl.businessId}}</h6>
<div>
{{'Used' | translate}} {{$ctrl.contractHolidays.holidaysEnjoyed || 0}}
{{'of' | translate}} {{$ctrl.contractHolidays.totalHolidays || 0}} {{'days' | translate}}
</div>
<div>
{{'Spent' | translate}} {{$ctrl.contractHolidays.hoursEnjoyed || 0}}
{{'of' | translate}} {{$ctrl.contractHolidays.totalHours || 0}} {{'hours' | translate}}
</div>
<div>
{{'Paid holidays' | translate}} {{$ctrl.contractHolidays.payedHolidays || 0}} {{'days' | translate}}
</div>
</div>
<div class="totalBox" style="text-align: center;">
<h6>{{'Year' | translate}} {{$ctrl.year}}</h6>
<div>
{{'Used' | translate}} {{$ctrl.yearHolidays.holidaysEnjoyed || 0}}
{{'of' | translate}} {{$ctrl.yearHolidays.totalHolidays || 0}} {{'days' | translate}}
</div>
<div>
{{'Spent' | translate}} {{$ctrl.yearHolidays.hoursEnjoyed || 0}}
{{'of' | translate}} {{$ctrl.yearHolidays.totalHours || 0}} {{'hours' | translate}}
</div>
</div>
<div class="vn-pt-md">
<vn-autocomplete label="Year"
data="$ctrl.yearFilter"
ng-model="$ctrl.year"
show-field="year"
value-field="year"
order="DESC">
</vn-autocomplete>
<vn-autocomplete label="Contract"
url="Workers/{{$ctrl.$params.id}}/contracts"
fields="['started', 'ended']"
ng-model="$ctrl.businessId"
search-function="{businessFk: $search}"
show-field="businessFk"
value-field="businessFk"
order="businessFk DESC"
limit="5">
<tpl-item>
<div>#{{businessFk}}</div>
<div class="text-caption text-secondary">
{{started | date: 'dd/MM/yyyy'}} - {{ended ? (ended | date: 'dd/MM/yyyy') : 'Indef.'}}
</div>
</tpl-item>
</vn-autocomplete>
</div>
<div name="absenceTypes" class="input vn-py-md" style="overflow: hidden;">
<vn-chip ng-repeat="absenceType in absenceTypes" ng-class="::{'selectable': $ctrl.isSubordinate}" ng-click="$ctrl.pick(absenceType)">
<vn-avatar ng-style="{backgroundColor: absenceType.rgb}">
<vn-icon class="check" icon="check" ng-if="absenceType.id == $ctrl.absenceType.id"></vn-icon>
</vn-avatar>
{{absenceType.name}}
</vn-chip>
</div>
<div class="vn-py-md">
<vn-chip>
<vn-avatar class="festive">
</vn-avatar>
<span translate>Festive</span>
</vn-chip>
<vn-chip>
<vn-avatar class="today">
</vn-avatar>
<span translate>Current day</span>
</vn-chip>
</div>
</div>
</vn-side-menu>
<vn-confirm
vn-id="confirm"
message="This item will be deleted"
question="Are you sure you want to continue?">
</vn-confirm>

View File

@ -1,302 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
constructor($element, $) {
super($element, $);
this.date = Date.vnNew();
this.events = {};
this.buildYearFilter();
}
get year() {
return this.date.getFullYear();
}
set year(value) {
const newYear = Date.vnNew();
newYear.setFullYear(value);
this.date = newYear;
this.refresh()
.then(() => this.repaint())
.then(() => this.getContractHolidays())
.then(() => this.getYearHolidays());
}
get businessId() {
return this._businessId;
}
set businessId(value) {
if (!this.card.hasWorkCenter) return;
this._businessId = value;
if (value) {
this.refresh()
.then(() => this.repaint())
.then(() => this.getContractHolidays())
.then(() => this.getYearHolidays());
}
}
get date() {
return this._date;
}
set date(value) {
this._date = value;
value.setHours(0, 0, 0, 0);
this.months = new Array(12);
for (let i = 0; i < this.months.length; i++) {
const now = new Date(value.getTime());
now.setDate(1);
now.setMonth(i);
this.months[i] = now;
}
}
get worker() {
return this._worker;
}
set worker(value) {
this._worker = value;
if (value) {
this.getIsSubordinate();
this.getActiveContract();
}
}
buildYearFilter() {
const now = Date.vnNew();
now.setFullYear(now.getFullYear() + 1);
const maxYear = now.getFullYear();
const minRange = maxYear - 5;
const years = [];
for (let i = maxYear; i > minRange; i--)
years.push({year: i});
this.yearFilter = years;
}
getIsSubordinate() {
this.$http.get(`Workers/${this.worker.id}/isSubordinate`)
.then(res => this.isSubordinate = res.data);
}
getActiveContract() {
this.$http.get(`Workers/${this.worker.id}/activeContract`)
.then(res => {
if (res.data) this.businessId = res.data.businessFk;
});
}
getContractHolidays() {
this.getHolidays({
businessFk: this.businessId,
year: this.year
}, data => this.contractHolidays = data);
}
getYearHolidays() {
this.getHolidays({
year: this.year
}, data => this.yearHolidays = data);
}
getHolidays(params, cb) {
this.$http.get(`Workers/${this.worker.id}/holidays`, {params})
.then(res => cb(res.data));
}
onData(data) {
this.events = {};
this.calendar = data.calendar;
let addEvent = (day, newEvent) => {
const timestamp = new Date(day).getTime();
const event = this.events[timestamp];
if (event) {
const oldName = event.name;
Object.assign(event, newEvent);
event.name = `${oldName}, ${event.name}`;
} else
this.events[timestamp] = newEvent;
};
if (data.holidays) {
data.holidays.forEach(holiday => {
const holidayDetail = holiday.detail && holiday.detail.name;
const holidayType = holiday.type && holiday.type.name;
const holidayName = holidayDetail || holidayType;
addEvent(holiday.dated, {
name: holidayName,
className: 'festive'
});
});
}
if (data.absences) {
data.absences.forEach(absence => {
let type = absence.absenceType;
addEvent(absence.dated, {
name: type.name,
color: type.rgb,
type: type.code,
absenceId: absence.id
});
});
}
}
repaint() {
let calendars = this.element.querySelectorAll('vn-calendar');
for (let calendar of calendars)
calendar.$ctrl.repaint();
}
formatDay(day, element) {
let event = this.events[day.getTime()];
if (!event) return;
let dayNumber = element.firstElementChild;
dayNumber.title = event.name;
dayNumber.style.backgroundColor = event.color;
if (event.border)
dayNumber.style.border = event.border;
if (event.className)
dayNumber.classList.add(event.className);
}
pick(absenceType) {
if (!this.isSubordinate) return;
if (absenceType == this.absenceType)
absenceType = null;
this.absenceType = absenceType;
}
onSelection($event, $days) {
if (!this.absenceType)
return this.vnApp.showMessage(this.$t('Choose an absence type from the right menu'));
const day = $days[0];
const stamp = day.getTime();
const event = this.events[stamp];
const calendar = $event.target.closest('vn-calendar').$ctrl;
if (event && event.absenceId) {
if (event.type == this.absenceType.code)
this.delete(calendar, day, event);
else
this.edit(calendar, event);
} else
this.create(calendar, day);
}
create(calendar, dated) {
const absenceType = this.absenceType;
const params = {
dated: dated,
absenceTypeId: absenceType.id,
businessFk: this.businessId
};
const path = `Workers/${this.$params.id}/createAbsence`;
this.$http.post(path, params).then(res => {
const newEvent = res.data;
this.events[dated.getTime()] = {
name: absenceType.name,
color: absenceType.rgb,
type: absenceType.code,
absenceId: newEvent.id
};
this.repaintCanceller(() =>
this.refresh()
.then(calendar.repaint())
.then(() => this.getContractHolidays())
.then(() => this.getYearHolidays())
.then(() => this.repaint())
);
});
}
edit(calendar, event) {
const absenceType = this.absenceType;
const params = {
absenceId: event.absenceId,
absenceTypeId: absenceType.id
};
const path = `Workers/${this.$params.id}/updateAbsence`;
this.$http.patch(path, params).then(() => {
event.color = absenceType.rgb;
event.name = absenceType.name;
event.type = absenceType.code;
this.repaintCanceller(() =>
this.refresh()
.then(calendar.repaint())
.then(() => this.getContractHolidays())
.then(() => this.getYearHolidays())
);
});
}
delete(calendar, day, event) {
const params = {absenceId: event.absenceId};
const path = `Workers/${this.$params.id}/deleteAbsence`;
this.$http.delete(path, {params}).then(() => {
delete this.events[day.getTime()];
this.repaintCanceller(() =>
this.refresh()
.then(calendar.repaint())
.then(() => this.getContractHolidays())
.then(() => this.getYearHolidays())
.then(() => this.repaint())
);
});
}
repaintCanceller(cb) {
if (this.canceller) {
clearTimeout(this.canceller);
this.canceller = null;
}
this.canceller = setTimeout(
() => cb(), 500);
}
refresh() {
const params = {
workerFk: this.$params.id,
businessFk: this.businessId,
year: this.year
};
return this.$http.get(`Calendars/absences`, {params})
.then(res => this.onData(res.data));
}
}
ngModule.vnComponent('vnWorkerCalendar', {
template: require('./index.html'),
controller: Controller,
bindings: {
worker: '<'
},
require: {
card: '^vnWorkerCard'
}
});

View File

@ -1,346 +0,0 @@
import './index';
describe('Worker', () => {
describe('Component vnWorkerCalendar', () => {
let $httpBackend;
let $httpParamSerializer;
let $scope;
let controller;
let year = Date.vnNew().getFullYear();
beforeEach(ngModule('worker'));
beforeEach(inject(($componentController, $rootScope, _$httpParamSerializer_, _$httpBackend_) => {
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
const $element = angular.element('<vn-worker-calendar></vn-worker-calendar>');
controller = $componentController('vnWorkerCalendar', {$element, $scope});
controller.isSubordinate = true;
controller.absenceType = {id: 1, name: 'Holiday', code: 'holiday', rgb: 'red'};
controller.$params.id = 1106;
controller._worker = {id: 1106};
controller.card = {
hasWorkCenter: true
};
}));
describe('year() getter', () => {
it(`should return the year number of the calendar date`, () => {
expect(controller.year).toEqual(year);
});
});
describe('year() setter', () => {
it(`should set the year of the calendar date`, () => {
jest.spyOn(controller, 'refresh').mockReturnValue(Promise.resolve());
const previousYear = year - 1;
controller.year = previousYear;
expect(controller.year).toEqual(previousYear);
expect(controller.date.getFullYear()).toEqual(previousYear);
expect(controller.refresh).toHaveBeenCalledWith();
});
});
describe('businessId() setter', () => {
it(`should set the contract id and then call to the refresh method`, () => {
jest.spyOn(controller, 'refresh').mockReturnValue(Promise.resolve());
controller.businessId = 1106;
expect(controller.refresh).toHaveBeenCalledWith();
});
});
describe('months property', () => {
it(`should return an array of twelve months length`, () => {
const started = new Date(year, 0, 1);
const ended = new Date(year, 11, 1);
expect(controller.months.length).toEqual(12);
expect(controller.months[0]).toEqual(started);
expect(controller.months[11]).toEqual(ended);
});
});
describe('worker() setter', () => {
it(`should perform a get query and set the reponse data on the model`, () => {
controller.getIsSubordinate = jest.fn();
controller.getActiveContract = jest.fn();
let today = Date.vnNew();
let tomorrow = new Date(today.getTime());
tomorrow.setDate(tomorrow.getDate() + 1);
let yesterday = new Date(today.getTime());
yesterday.setDate(yesterday.getDate() - 1);
controller.worker = {id: 1107};
expect(controller.getIsSubordinate).toHaveBeenCalledWith();
expect(controller.getActiveContract).toHaveBeenCalledWith();
});
});
describe('getIsSubordinate()', () => {
it(`should return whether the worker is a subordinate`, () => {
$httpBackend.expect('GET', `Workers/1106/isSubordinate`).respond(true);
controller.getIsSubordinate();
$httpBackend.flush();
expect(controller.isSubordinate).toBe(true);
});
});
describe('getActiveContract()', () => {
it(`should return the current contract and then set the businessId property`, () => {
jest.spyOn(controller, 'refresh').mockReturnValue(Promise.resolve());
$httpBackend.expect('GET', `Workers/1106/activeContract`).respond({businessFk: 1106});
controller.getActiveContract();
$httpBackend.flush();
expect(controller.businessId).toEqual(1106);
});
});
describe('getContractHolidays()', () => {
it(`should return the worker holidays amount and then set the contractHolidays property`, () => {
const today = Date.vnNew();
const year = today.getFullYear();
const serializedParams = $httpParamSerializer({year});
$httpBackend.expect('GET', `Workers/1106/holidays?${serializedParams}`).respond({totalHolidays: 28});
controller.getContractHolidays();
$httpBackend.flush();
expect(controller.contractHolidays).toEqual({totalHolidays: 28});
});
});
describe('formatDay()', () => {
it(`should set the day element style`, () => {
const today = Date.vnNew();
controller.events[today.getTime()] = {
name: 'Holiday',
color: '#000'
};
const dayElement = angular.element('<div><section></section></div>')[0];
const dayNumber = dayElement.firstElementChild;
controller.formatDay(today, dayElement);
expect(dayNumber.title).toEqual('Holiday');
expect(dayNumber.style.backgroundColor).toEqual('rgb(0, 0, 0)');
});
});
describe('pick()', () => {
it(`should set the absenceType property to null if they match with the current one`, () => {
const absenceType = {id: 1, name: 'Holiday'};
controller.absenceType = absenceType;
controller.pick(absenceType);
expect(controller.absenceType).toBeNull();
});
it(`should set the absenceType property`, () => {
const absenceType = {id: 1, name: 'Holiday'};
const expectedAbsence = {id: 2, name: 'Leave of absence'};
controller.absenceType = absenceType;
controller.pick(expectedAbsence);
expect(controller.absenceType).toEqual(expectedAbsence);
});
});
describe('onSelection()', () => {
it(`should show an snackbar message if no absence type is selected`, () => {
jest.spyOn(controller.vnApp, 'showMessage').mockReturnThis();
const $event = {};
const $days = [];
controller.absenceType = null;
controller.onSelection($event, $days);
expect(controller.vnApp.showMessage).toHaveBeenCalledWith('Choose an absence type from the right menu');
});
it(`should call to the create() method`, () => {
jest.spyOn(controller, 'create').mockReturnThis();
const selectedDay = Date.vnNew();
const $event = {
target: {
closest: () => {
return {$ctrl: {}};
}
}
};
const $days = [selectedDay];
controller.absenceType = {id: 1};
controller.onSelection($event, $days);
expect(controller.create).toHaveBeenCalledWith(jasmine.any(Object), selectedDay);
});
it(`should call to the delete() method`, () => {
jest.spyOn(controller, 'delete').mockReturnThis();
const selectedDay = Date.vnNew();
const expectedEvent = {
dated: selectedDay,
type: 'holiday',
absenceId: 1
};
const $event = {
target: {
closest: () => {
return {$ctrl: {}};
}
}
};
const $days = [selectedDay];
controller.events[selectedDay.getTime()] = expectedEvent;
controller.absenceType = {id: 1, code: 'holiday'};
controller.onSelection($event, $days);
expect(controller.delete).toHaveBeenCalledWith(jasmine.any(Object), selectedDay, expectedEvent);
});
it(`should call to the edit() method`, () => {
jest.spyOn(controller, 'edit').mockReturnThis();
const selectedDay = Date.vnNew();
const expectedEvent = {
dated: selectedDay,
type: 'leaveOfAbsence',
absenceId: 1
};
const $event = {
target: {
closest: () => {
return {$ctrl: {}};
}
}
};
const $days = [selectedDay];
controller.events[selectedDay.getTime()] = expectedEvent;
controller.absenceType = {id: 1, code: 'holiday'};
controller.onSelection($event, $days);
expect(controller.edit).toHaveBeenCalledWith(jasmine.any(Object), expectedEvent);
});
});
describe('create()', () => {
it(`should make a HTTP POST query and then call to the repaintCanceller() method`, () => {
jest.spyOn(controller, 'repaintCanceller').mockReturnThis();
const dated = Date.vnNew();
const calendarElement = {};
const expectedResponse = {id: 10};
$httpBackend.expect('POST', `Workers/1106/createAbsence`).respond(200, expectedResponse);
controller.create(calendarElement, dated);
$httpBackend.flush();
const createdEvent = controller.events[dated.getTime()];
const absenceType = controller.absenceType;
expect(createdEvent.absenceId).toEqual(expectedResponse.id);
expect(createdEvent.color).toEqual(absenceType.rgb);
expect(controller.repaintCanceller).toHaveBeenCalled();
});
});
describe('edit()', () => {
it(`should make a HTTP PATCH query and then call to the repaintCanceller() method`, () => {
jest.spyOn(controller, 'repaintCanceller').mockReturnThis();
const event = {absenceId: 10};
const calendarElement = {};
const newAbsenceType = {
id: 2,
name: 'Leave of absence',
code: 'leaveOfAbsence',
rgb: 'purple'
};
controller.absenceType = newAbsenceType;
const expectedParams = {absenceId: 10, absenceTypeId: 2};
$httpBackend.expect('PATCH', `Workers/1106/updateAbsence`, expectedParams).respond(200);
controller.edit(calendarElement, event);
$httpBackend.flush();
expect(event.name).toEqual(newAbsenceType.name);
expect(event.color).toEqual(newAbsenceType.rgb);
expect(event.type).toEqual(newAbsenceType.code);
expect(controller.repaintCanceller).toHaveBeenCalled();
});
});
describe('delete()', () => {
it(`should make a HTTP DELETE query and then call to the repaintCanceller() method`, () => {
jest.spyOn(controller, 'repaintCanceller').mockReturnThis();
const expectedParams = {absenceId: 10};
const calendarElement = {};
const selectedDay = Date.vnNew();
const expectedEvent = {
dated: selectedDay,
type: 'leaveOfAbsence',
absenceId: 10
};
controller.events[selectedDay.getTime()] = expectedEvent;
const serializedParams = $httpParamSerializer(expectedParams);
$httpBackend.expect('DELETE', `Workers/1106/deleteAbsence?${serializedParams}`).respond(200);
controller.delete(calendarElement, selectedDay, expectedEvent);
$httpBackend.flush();
const event = controller.events[selectedDay.getTime()];
expect(event).toBeUndefined();
expect(controller.repaintCanceller).toHaveBeenCalled();
});
});
describe('repaintCanceller()', () => {
it(`should cancell the callback execution timer`, () => {
jest.spyOn(window, 'clearTimeout');
jest.spyOn(window, 'setTimeout');
const timeoutId = 90;
controller.canceller = timeoutId;
controller.repaintCanceller(() => {
return 'My callback';
});
expect(window.clearTimeout).toHaveBeenCalledWith(timeoutId);
expect(window.setTimeout).toHaveBeenCalledWith(jasmine.any(Function), 500);
});
});
describe('refresh()', () => {
it(`should make a HTTP GET query and then call to the onData() method`, () => {
jest.spyOn(controller, 'onData').mockReturnThis();
const expecteResponse = [{id: 1}];
const expectedParams = {workerFk: controller.worker.id, year: year};
const serializedParams = $httpParamSerializer(expectedParams);
$httpBackend.expect('GET', `Calendars/absences?${serializedParams}`).respond(200, expecteResponse);
controller.refresh();
$httpBackend.flush();
expect(controller.onData).toHaveBeenCalledWith(expecteResponse);
});
});
});
});

View File

@ -1,15 +0,0 @@
Calendar: Calendario
Contract: Contrato
Festive: Festivo
Used: Utilizados
Spent: Utilizadas
Year: Año
of: de
days: días
hours: horas
Choose an absence type from the right menu: Elige un tipo de ausencia desde el menú de la derecha
To start adding absences, click an absence type from the right menu and then on the day you want to add an absence: Para empezar a añadir ausencias, haz clic en un tipo de ausencia desde el menu de la derecha y después en el día que quieres añadir la ausencia
You can just add absences within the current year: Solo puedes añadir ausencias dentro del año actual
Current day: Día actual
Paid holidays: Vacaciones pagadas
Autonomous worker: Trabajador autónomo

View File

@ -1,65 +0,0 @@
@import "variables";
vn-worker-calendar {
.calendars {
position: relative;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
box-sizing: border-box;
padding: $spacing-md;
& > vn-calendar {
border: $border-thin;
margin: $spacing-md;
padding: $spacing-xs;
max-width: 288px;
}
}
vn-chip.selectable {
cursor: pointer
}
vn-chip.selectable:hover {
opacity: 0.8
}
vn-chip vn-avatar {
text-align: center;
color: white
}
vn-icon[icon="info"] {
position: absolute;
top: 16px;
right: 16px
}
vn-side-menu div > .input {
border-bottom: $border-thin;
}
.festive,
vn-avatar.today {
color: $color-font;
width: 24px;
min-width: 24px;
height: 24px
}
.festive {
border: 2px solid $color-alert
}
vn-avatar.today {
border: 2px solid $color-font-link
}
.check {
margin-top: 0.5px;
margin-left: -3px;
font-size: 125%;
}
}

View File

@ -1,198 +0,0 @@
<vn-watcher
vn-id="watcher"
url="Workers/new"
data="$ctrl.worker"
insert-mode="true"
form="form">
</vn-watcher>
<form name="form" vn-http-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield
vn-one
label="Firstname"
ng-model="$ctrl.worker.firstName"
rule
on-change="$ctrl.generateCodeUser()"
vn-focus>
</vn-textfield>
<vn-textfield
vn-one
label="Lastname"
on-change="$ctrl.generateCodeUser()"
ng-model="$ctrl.worker.lastNames"
rule>
</vn-textfield>
<vn-date-picker
vn-one
label="Birth"
ng-model="$ctrl.worker.birth">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Fi"
ng-model="$ctrl.worker.fi"
rule>
</vn-textfield>
<vn-textfield
vn-one
label="Worker code"
ng-model="$ctrl.worker.code"
maxLength="3"
on-change="$ctrl.worker.code = $ctrl.worker.code.toUpperCase()"
rule>
</vn-textfield>
<vn-textfield
vn-one
label="Phone"
ng-model="$ctrl.worker.phone"
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-datalist
label="Postcode"
vn-one
ng-model="$ctrl.worker.postcode"
selection="$ctrl.postcode"
url="Postcodes/location"
fields="['code','townFk']"
order="code, townFk"
value-field="code"
show-field="code"
rule>
<tpl-item>
{{code}} - {{town.name}} ({{town.province.name}},
{{town.province.country.name}})
</tpl-item>
<append>
<vn-icon-button
icon="add_circle"
vn-tooltip="New postcode"
ng-click="postcode.open()"
vn-acl="deliveryAssistant"
vn-acl-action="remove">
</vn-icon-button>
</append>
</vn-datalist>
<vn-autocomplete
vn-id="province"
label="Province"
ng-model="$ctrl.worker.provinceFk"
selection="$ctrl.province"
url="Provinces/location"
fields="['id', 'name', 'countryFk']"
rule>
<tpl-item>{{name}} ({{country.name}})</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-datalist
vn-id="town"
label="City"
ng-model="$ctrl.worker.city"
selection="$ctrl.town"
url="Towns/location"
fields="['id', 'name', 'provinceFk']"
value-field="name">
<tpl-item>
{{name}}, {{province.name}}
({{province.country.name}})
</tpl-item>
</vn-datalist>
<vn-textfield
vn-two
label="Street"
ng-model="$ctrl.worker.street"
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
label="Web user"
ng-model="$ctrl.worker.name"
rule>
</vn-textfield>
<vn-textfield
label="Personal email"
ng-model="$ctrl.worker.email"
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
vn-id="company"
ng-model="$ctrl.worker.companyFk"
url="Companies"
show-field="code"
value-field="id"
label="Company">
</vn-autocomplete>
<vn-worker-autocomplete
vn-one
ng-model="$ctrl.worker.bossFk"
show-field="nickname"
label="Boss">
</vn-worker-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
label="Pay method"
url="Paymethods"
ng-model="$ctrl.worker.payMethodFk"
initial-data="$ctrl.workerConfig.payMethodFk">
</vn-autocomplete>
<vn-textfield
vn-one
label="IBAN"
ng-model="$ctrl.worker.iban"
on-change="$ctrl.autofillBic()"
rule>
</vn-textfield>
<vn-autocomplete
vn-one
label="Swift / BIC"
url="BankEntities"
ng-model="$ctrl.worker.bankEntityFk"
fields="['name']"
initial-data="$ctrl.worker.bankEntityFk"
on-change="$ctrl.autofillBic()"
search-function="{or: [{bic: {like: $search +'%'}}, {name: {like: '%'+ $search +'%'}}]}"
value-field="id"
show-field="bic"
vn-acl="salesAssistant, hr"
disabled="$ctrl.ibanCountry == 'ES'">
<tpl-item>{{bic}} {{name}}</tpl-item>
<append>
<vn-icon-button
vn-auto
icon="add_circle"
vn-click-stop="bankEntity.show({countryFk: $ctrl.worker.countryFk})"
vn-tooltip="New bank entity"
vn-acl="salesAssistant, hr">
</vn-icon-button>
</append>
</vn-autocomplete>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Create">
</vn-submit>
<vn-button
class="cancel"
label="Cancel"
ui-sref="worker.index">
</vn-button>
</vn-button-bar>
</form>
<!-- New postcode dialog -->
<vn-geo-postcode
vn-id="postcode"
on-response="$ctrl.onResponse($response)">
</vn-geo-postcode>

View File

@ -1,141 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.worker = {companyFk: this.vnConfig.user.companyFk};
this.$http.get(`WorkerConfigs/findOne`, {field: ['payMethodFk']}).then(res => {
if (res.data) this.worker.payMethodFk = res.data.payMethodFk;
});
}
onSubmit() {
if (!this.worker.iban && !this.worker.bankEntityFk) {
delete this.worker.iban;
delete this.worker.bankEntityFk;
}
return this.$.watcher.submit().then(json => {
this.$state.go('worker.card.basicData', {id: json.data.id});
});
}
get ibanCountry() {
if (!this.worker || !this.worker.iban) return false;
let countryCode = this.worker.iban.substr(0, 2);
return countryCode;
}
autofillBic() {
if (!this.worker || !this.worker.iban) return;
let bankEntityId = parseInt(this.worker.iban.substr(4, 4));
let filter = {where: {id: bankEntityId}};
this.$http.get(`BankEntities`, {filter}).then(response => {
const hasData = response.data && response.data[0];
if (hasData)
this.worker.bankEntityFk = response.data[0].id;
else if (!hasData)
this.worker.bankEntityFk = null;
});
}
generateCodeUser() {
if (!this.worker.firstName || !this.worker.lastNames) return;
const totalName = this.worker.firstName.concat(' ' + this.worker.lastNames).toLowerCase();
const totalNameArray = totalName.split(' ');
let newCode = '';
for (let part of totalNameArray)
newCode += part.charAt(0);
this.worker.code = newCode.toUpperCase().slice(0, 3);
this.worker.name = totalNameArray[0] + newCode.slice(1);
if (!this.worker.companyFk)
this.worker.companyFk = this.vnConfig.user.companyFk;
}
get province() {
return this._province;
}
// Province auto complete
set province(selection) {
this._province = selection;
if (!selection) return;
const country = selection.country;
if (!this.worker.countryFk)
this.worker.countryFk = country.id;
}
get town() {
return this._town;
}
// Town auto complete
set town(selection) {
this._town = selection;
if (!selection) return;
const province = selection.province;
const country = province.country;
const postcodes = selection.postcodes;
if (!this.worker.provinceFk)
this.worker.provinceFk = province.id;
if (!this.worker.countryFk)
this.worker.countryFk = country.id;
if (postcodes.length === 1)
this.worker.postcode = postcodes[0].code;
}
get postcode() {
return this._postcode;
}
// Postcode auto complete
set postcode(selection) {
this._postcode = selection;
if (!selection) return;
const town = selection.town;
const province = town.province;
const country = province.country;
if (!this.worker.city)
this.worker.city = town.name;
if (!this.worker.provinceFk)
this.worker.provinceFk = province.id;
if (!this.worker.countryFk)
this.worker.countryFk = country.id;
}
onResponse(response) {
this.worker.postcode = response.code;
this.worker.city = response.city;
this.worker.provinceFk = response.provinceFk;
}
}
Controller.$inject = ['$element', '$scope'];
ngModule.vnComponent('vnWorkerCreate', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,133 +0,0 @@
import './index';
describe('Worker', () => {
describe('Component vnWorkerCreate', () => {
let $scope;
let $state;
let controller;
beforeEach(ngModule('worker'));
beforeEach(inject(($componentController, $rootScope, _$state_) => {
$scope = $rootScope.$new();
$state = _$state_;
$scope.watcher = {
submit: () => {
return {
then: callback => {
callback({data: {id: '1234'}});
}
};
}
};
const $element = angular.element('<vn-worker-create></vn-worker-create>');
controller = $componentController('vnWorkerCreate', {$element, $scope});
controller.worker = {};
controller.vnConfig = {user: {companyFk: 1}};
}));
describe('onSubmit()', () => {
it(`should call submit() on the watcher then expect a callback`, () => {
jest.spyOn($state, 'go');
controller.onSubmit();
expect(controller.$state.go).toHaveBeenCalledWith('worker.card.basicData', {id: '1234'});
});
});
describe('province() setter', () => {
it(`should set countryFk property`, () => {
controller.worker.countryFk = null;
controller.province = {
id: 1,
name: 'New york',
country: {
id: 2,
name: 'USA'
}
};
expect(controller.worker.countryFk).toEqual(2);
});
});
describe('town() setter', () => {
it(`should set provinceFk property`, () => {
controller.town = {
provinceFk: 1,
code: 46001,
province: {
id: 1,
name: 'New york',
country: {
id: 2,
name: 'USA'
}
},
postcodes: []
};
expect(controller.worker.provinceFk).toEqual(1);
});
it(`should set provinceFk property and fill the postalCode if there's just one`, () => {
controller.town = {
provinceFk: 1,
code: 46001,
province: {
id: 1,
name: 'New york',
country: {
id: 2,
name: 'USA'
}
},
postcodes: [{code: '46001'}]
};
expect(controller.worker.provinceFk).toEqual(1);
expect(controller.worker.postcode).toEqual('46001');
});
});
describe('postcode() setter', () => {
it(`should set the town, provinceFk and contryFk properties`, () => {
controller.postcode = {
townFk: 1,
code: 46001,
town: {
id: 1,
name: 'New York',
province: {
id: 1,
name: 'New york',
country: {
id: 2,
name: 'USA'
}
}
}
};
expect(controller.worker.city).toEqual('New York');
expect(controller.worker.provinceFk).toEqual(1);
expect(controller.worker.countryFk).toEqual(2);
});
});
describe('generateCodeUser()', () => {
it(`should generate worker code, name and company `, () => {
controller.worker = {
firstName: 'default',
lastNames: 'generate worker'
};
controller.generateCodeUser();
expect(controller.worker.code).toEqual('DGW');
expect(controller.worker.name).toEqual('defaultgw');
expect(controller.worker.companyFk).toEqual(controller.vnConfig.user.companyFk);
});
});
});
});

View File

@ -1,13 +0,0 @@
Firstname: Nombre
Lastname: Apellidos
Fi: DNI/NIF/NIE
Birth: Fecha de nacimiento
Worker code: Código de trabajador
Province: Provincia
City: Población
ProfileType: Tipo de perfil
Street: Dirección
Postcode: Código postal
Web user: Usuario Web
Access permission: Permiso de acceso
Pay method: Método de pago

View File

@ -1,94 +0,0 @@
<mg-ajax path="dms/upload" options="vnPost"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.dms">
</vn-watcher>
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form
name="form"
ng-submit="$ctrl.onSubmit()"
class="vn-ma-md"
enctype="multipart/form-data">
<div class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield
vn-one
vn-focus
label="Reference"
ng-model="$ctrl.dms.reference"
rule>
</vn-textfield>
<vn-autocomplete vn-one
label="Company"
ng-model="$ctrl.dms.companyId"
url="Companies"
show-field="code"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
label="Warehouse"
ng-model="$ctrl.dms.warehouseId"
data="warehouses"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-autocomplete vn-one
label="Type"
ng-model="$ctrl.dms.dmsTypeId"
url="DmsTypes"
show-field="name"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textarea
vn-one
label="Description"
ng-model="$ctrl.dms.description"
rule>
</vn-textarea>
</vn-horizontal>
<vn-horizontal>
<vn-input-file
vn-one
label="File"
ng-model="$ctrl.dms.files"
on-change="$ctrl.onFileChange($files)"
accept="{{$ctrl.allowedContentTypes}}"
required="true"
multiple="true">
<append>
<vn-icon vn-none
color-marginal
title="{{$ctrl.contentTypesInfo}}"
icon="info">
</vn-icon>
</append>
</vn-input-file>
</vn-horizontal>
<vn-vertical>
<vn-check
label="Generate identifier for original file"
ng-model="$ctrl.dms.hasFile">
</vn-check>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit
label="Upload">
</vn-submit>
<vn-button
class="cancel"
label="Cancel"
ui-sref="worker.card.dms.index"></vn-button>
</vn-button>
</vn-button-bar>
</div>
</form>

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