Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2973-module_zone_transactions

This commit is contained in:
Carlos Jimenez Ruiz 2021-06-21 09:08:03 +02:00
commit 48a2bb7b10
32 changed files with 649 additions and 224 deletions

View File

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

View File

@ -837,7 +837,8 @@ export default {
saveButton: 'vn-worker-pbx button[type=submit]'
},
workerTimeControl: {
timeDialog: '.vn-dialog.shown vn-input-time[ng-model="$ctrl.newTime"]',
dialogTimeInput: '.vn-dialog.shown vn-input-time[ng-model="$ctrl.newTimeEntry.timed"]',
dialogTimeDirection: '.vn-dialog.shown vn-autocomplete[ng-model="$ctrl.newTimeEntry.direction"]',
mondayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(1) > vn-icon-button',
tuesdayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(2) > vn-icon-button',
wednesdayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(3) > vn-icon-button',
@ -845,35 +846,35 @@ export default {
fridayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(5) > vn-icon-button',
saturdayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(6) > vn-icon-button',
sundayAddTimeButton: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(2) > vn-td:nth-child(7) > vn-icon-button',
firstEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(1) > vn-chip > div',
firstEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(1) > vn-chip > div',
firstEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(1) > vn-chip > div',
firstEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(1) > vn-chip > div',
firstEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(1) > vn-chip > div',
firstEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(1) > vn-chip > div',
firstEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(1) > vn-chip > div',
secondEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(2) > vn-chip > div',
secondEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(2) > vn-chip > div',
secondEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(2) > vn-chip > div',
secondEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(2) > vn-chip > div',
secondEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(2) > vn-chip > div',
secondEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(2) > vn-chip > div',
secondEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(2) > vn-chip > div',
thirdEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(3) > vn-chip > div',
firstEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(1) > vn-chip > div:nth-child(2)',
firstEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(1) > vn-chip > div:nth-child(2)',
firstEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(1) > vn-chip > div:nth-child(2)',
firstEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(1) > vn-chip > div:nth-child(2)',
firstEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(1) > vn-chip > div:nth-child(2)',
firstEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(1) > vn-chip > div:nth-child(2)',
firstEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(1) > vn-chip > div:nth-child(2)',
secondEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(2) > vn-chip > div:nth-child(2)',
secondEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(2) > vn-chip > div:nth-child(2)',
secondEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(2) > vn-chip > div:nth-child(2)',
secondEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(2) > vn-chip > div:nth-child(2)',
secondEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(2) > vn-chip > div:nth-child(2)',
secondEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(2) > vn-chip > div:nth-child(2)',
secondEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(2) > vn-chip > div:nth-child(2)',
thirdEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(3) > vn-chip > div:nth-child(2)',
thirdEntryOfMondayDelete: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(3) > vn-chip > vn-icon[icon="cancel"]',
thirdEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(3) > vn-chip > div',
thirdEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(3) > vn-chip > div',
thirdEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(3) > vn-chip > div',
thirdEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(3) > vn-chip > div',
thirdEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(3) > vn-chip > div',
thirdEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(3) > vn-chip > div',
fourthEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(4) > vn-chip > div',
fourthEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(4) > vn-chip > div',
fourthEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(4) > vn-chip > div',
fourthEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(4) > vn-chip > div',
fourthEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(4) > vn-chip > div',
fourthEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(4) > vn-chip > div',
fourthEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(4) > vn-chip > div',
thirdEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(3) > vn-chip > div:nth-child(2)',
thirdEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(3) > vn-chip > div:nth-child(2)',
thirdEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(3) > vn-chip > div:nth-child(2)',
thirdEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(3) > vn-chip > div:nth-child(2)',
thirdEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(3) > vn-chip > div:nth-child(2)',
thirdEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(3) > vn-chip > div:nth-child(2)',
fourthEntryOfMonday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(1) > section:nth-child(4) > vn-chip > div:nth-child(2)',
fourthEntryOfTuesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(2) > section:nth-child(4) > vn-chip > div:nth-child(2)',
fourthEntryOfWednesday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(3) > section:nth-child(4) > vn-chip > div:nth-child(2)',
fourthEntryOfThursday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4) > section:nth-child(4) > vn-chip > div:nth-child(2)',
fourthEntryOfFriday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5) > section:nth-child(4) > vn-chip > div:nth-child(2)',
fourthEntryOfSaturday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(6) > section:nth-child(4) > vn-chip > div:nth-child(2)',
fourthEntryOfSunday: 'vn-worker-time-control vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7) > section:nth-child(4) > vn-chip > div:nth-child(2)',
mondayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(1)',
tuesdayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(2)',
wednesdayWorkedHours: 'vn-worker-time-control vn-table > div > vn-tfoot > vn-tr:nth-child(1) > vn-td:nth-child(3)',

View File

@ -22,7 +22,8 @@ describe('Worker time control path', () => {
const scanTime = '07:00';
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText');
@ -33,7 +34,8 @@ describe('Worker time control path', () => {
const scanTime = '10:00';
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfMonday, 'innerText');
@ -44,7 +46,8 @@ describe('Worker time control path', () => {
const scanTime = '18:00';
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfMonday, 'innerText');
@ -66,7 +69,8 @@ describe('Worker time control path', () => {
const scanTime = '14:00';
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfMonday, 'innerText');
@ -77,7 +81,8 @@ describe('Worker time control path', () => {
const scanTime = '10:20';
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfMonday, 'innerText');
@ -103,7 +108,8 @@ describe('Worker time control path', () => {
const scanTime = '08:00';
await page.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfTuesday, 'innerText');
@ -114,7 +120,8 @@ describe('Worker time control path', () => {
const scanTime = '10:00';
await page.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfTuesday, 'innerText');
@ -125,7 +132,8 @@ describe('Worker time control path', () => {
const scanTime = '10:20';
await page.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfTuesday, 'innerText');
@ -136,7 +144,8 @@ describe('Worker time control path', () => {
const scanTime = '16:00';
await page.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfTuesday, 'innerText');
@ -153,7 +162,8 @@ describe('Worker time control path', () => {
const scanTime = '09:00';
await page.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfWednesday, 'innerText');
@ -164,7 +174,8 @@ describe('Worker time control path', () => {
const scanTime = '10:00';
await page.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfWednesday, 'innerText');
@ -175,7 +186,8 @@ describe('Worker time control path', () => {
const scanTime = '10:20';
await page.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfWednesday, 'innerText');
@ -186,7 +198,8 @@ describe('Worker time control path', () => {
const scanTime = '17:00';
await page.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfWednesday, 'innerText');
@ -203,7 +216,8 @@ describe('Worker time control path', () => {
const scanTime = '09:59';
await page.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfThursday, 'innerText');
@ -213,7 +227,8 @@ describe('Worker time control path', () => {
it(`should joyfully scan out Hank Pym for break`, async() => {
const scanTime = '10:00';
await page.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfThursday, 'innerText');
@ -223,7 +238,8 @@ describe('Worker time control path', () => {
it(`should joyfully scan in Hank Pym from the break`, async() => {
const scanTime = '10:20';
await page.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfThursday, 'innerText');
@ -233,7 +249,8 @@ describe('Worker time control path', () => {
it(`should joyfully scan out Hank Pym for the day`, async() => {
const scanTime = '17:59';
await page.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfThursday, 'innerText');
@ -249,7 +266,8 @@ describe('Worker time control path', () => {
it('should smilingly scan in Hank Pym', async() => {
const scanTime = '07:30';
await page.waitToClick(selectors.workerTimeControl.fridayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfFriday, 'innerText');
@ -259,7 +277,8 @@ describe('Worker time control path', () => {
it(`should smilingly scan out Hank Pym for break`, async() => {
const scanTime = '10:00';
await page.waitToClick(selectors.workerTimeControl.fridayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfFriday, 'innerText');
@ -269,7 +288,8 @@ describe('Worker time control path', () => {
it(`should smilingly scan in Hank Pym from the break`, async() => {
const scanTime = '10:20';
await page.waitToClick(selectors.workerTimeControl.fridayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'intermediate');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfFriday, 'innerText');
@ -279,7 +299,8 @@ describe('Worker time control path', () => {
it(`should smilingly scan out Hank Pym for the day`, async() => {
const scanTime = '15:30';
await page.waitToClick(selectors.workerTimeControl.fridayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfFriday, 'innerText');
@ -310,8 +331,10 @@ describe('Worker time control path', () => {
it('should lovingly scan in Hank Pym', async() => {
const scanTime = '06:00';
await page.waitForTimeout(1000); // without this timeout the dialog doesn't pop up
await page.waitToClick(selectors.workerTimeControl.saturdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfSaturday, 'innerText');
@ -321,7 +344,8 @@ describe('Worker time control path', () => {
it(`should lovingly scan out Hank Pym for the day with no break to leave a bit early`, async() => {
const scanTime = '13:40';
await page.waitToClick(selectors.workerTimeControl.saturdayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfSaturday, 'innerText');
@ -337,7 +361,8 @@ describe('Worker time control path', () => {
it('should gladly scan in Hank Pym', async() => {
const scanTime = '05:00';
await page.waitToClick(selectors.workerTimeControl.sundayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfSunday, 'innerText');
@ -347,7 +372,8 @@ describe('Worker time control path', () => {
it(`should gladly scan out Hank Pym for the day with no break to leave a bit early`, async() => {
const scanTime = '12:40';
await page.waitToClick(selectors.workerTimeControl.sundayAddTimeButton);
await page.pickTime(selectors.workerTimeControl.timeDialog, scanTime);
await page.pickTime(selectors.workerTimeControl.dialogTimeInput, scanTime);
await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfSunday, 'innerText');

View File

@ -65,6 +65,7 @@ describe('Account create and basic data path', () => {
describe('Descriptor option', () => {
describe('Edit role', () => {
it('should edit the role using the descriptor menu', async() => {
await page.waitForTimeout(1000); // sometimes descriptor fails to load it's functionalities without this timeout
await page.waitToClick(selectors.accountDescriptor.menuButton);
await page.waitToClick(selectors.accountDescriptor.changeRole);
await page.autocompleteSearch(selectors.accountDescriptor.newRole, 'adminBoss');

View File

@ -1,6 +1,12 @@
<div
ng-transclude="prepend"
class="prepend"></div>
<div ng-transclude></div>
<div
ng-transclude="append"
class="append"></div>
<vn-icon
ng-click="$ctrl.onRemove()"
ng-click="$ctrl.onRemove($event)"
ng-if="$ctrl.removable"
icon="cancel"
tabindex="0">

View File

@ -3,16 +3,19 @@ import Component from '../../lib/component';
import './style.scss';
export default class Chip extends Component {
onRemove() {
if (!this.disabled) this.emit('remove');
onRemove($event) {
if (!this.disabled) this.emit('remove', {$event});
}
}
Chip.$inject = ['$element', '$scope', '$transclude'];
ngModule.vnComponent('vnChip', {
template: require('./index.html'),
transclude: {
prepend: '?prepend',
append: '?append'
},
controller: Chip,
transclude: true,
bindings: {
disabled: '<?',
removable: '<?'

View File

@ -18,9 +18,14 @@ describe('Component vnChip', () => {
it(`should emit remove event`, () => {
controller.emit = () => {};
jest.spyOn(controller, 'emit');
controller.onRemove();
expect(controller.emit).toHaveBeenCalledWith('remove');
const $event = new Event('click');
const target = document.createElement('div');
target.dispatchEvent($event);
controller.onRemove($event);
expect(controller.emit).toHaveBeenCalledWith('remove', {$event});
});
});
});

View File

@ -1,4 +1,5 @@
@import "variables";
@import "effects";
vn-chip {
border-radius: 16px;
@ -24,25 +25,47 @@ vn-chip {
&.transparent {
background-color: transparent;
}
&.colored {
&.colored,
&.colored.clickable:hover,
&.colored.clickable:focus {
background-color: $color-main;
color: $color-font-bg;
}
&.notice {
background-color: $color-notice-medium
&.notice,
&.notice.clickable:hover,
&.notice.clickable:focus {
background-color: $color-notice-medium;
}
&.success {
&.success,
&.success.clickable:hover,
&.success.clickable:focus {
background-color: $color-success-medium;
}
&.warning {
&.warning,
&.warning.clickable:hover,
&.warning.clickable:focus {
background-color: $color-main-medium;
}
&.alert {
&.alert,
&.alert.clickable:hover,
&.alert.clickable:focus {
background-color: $color-alert-medium;
}
&.message {
&.message,
&.message.clickable:hover,
&.message.clickable:focus {
color: $color-font-dark;
background-color: $color-bg-dark
background-color: $color-bg-dark;
}
&.clickable {
@extend %clickable;
opacity: 0.8;
&:hover,
&:focus {
opacity: 1;
}
}
& > div {
@ -75,6 +98,20 @@ vn-chip {
opacity: 1;
}
}
& > .prepend {
padding: 0 5px;
padding-right: 0;
&:empty {display:none;}
}
& > .append {
padding: 0 5px;
padding-left: 0;
&:empty {display:none;}
}
}
vn-avatar {

View File

@ -97,5 +97,6 @@
"Role name must be written in camelCase": "Role name must be written in camelCase",
"Client assignment has changed": "I did change the salesperson ~*\"<{{previousWorkerName}}>\"*~ by *\"<{{currentWorkerName}}>\"* from the client [{{clientName}} ({{clientId}})]({{{url}}})",
"None": "None",
"error densidad = 0": "error densidad = 0"
"error densidad = 0": "error densidad = 0",
"nickname": "nickname"
}

View File

@ -1,11 +1,12 @@
<vn-crud-model
vn-id="ticketsModel"
auto-load="true"
url="Tickets/filter"
link="{'t.clientFk': $ctrl.$params.id}"
url="Tickets"
link="{clientFk: $ctrl.$params.id}"
filter="::$ctrl.ticketFilter"
limit="5"
data="tickets"
order="shippedDate DESC, shippedHour ASC">
order="shipped DESC">
</vn-crud-model>
<vn-card class="summary">
<h5>
@ -292,9 +293,9 @@
<vn-tr>
<vn-th field="id" number>Id</vn-th>
<vn-th field="nickname" expand>Client</vn-th>
<vn-th field="salesPersonFk">Salesperson</vn-th>
<vn-th field="packages" shrink>Packages</vn-th>
<vn-th field="shipped" shrink-date>Date</vn-th>
<vn-th field="stateFk">State</vn-th>
<vn-th>State</vn-th>
<vn-th shrink>Total</vn-th>
<vn-th></vn-th>
</vn-tr>
@ -312,13 +313,8 @@
{{::ticket.nickname}}
</span>
</vn-td>
<vn-td>
<span
title="{{::ticket.userName}}"
vn-click-stop="workerDescriptor.show($event, ticket.salesPersonFk)"
class="link">
{{::ticket.userName | dashIfEmpty}}
</span>
<vn-td shrink>
{{::ticket.packages}}
</vn-td>
<vn-td shrink-date>
<span class="chip {{::$ctrl.chipColor(ticket.shipped)}}">
@ -336,7 +332,7 @@
<span
ng-show="::!ticket.refFk"
class="chip {{::$ctrl.stateColor(ticket)}}">
{{::ticket.state}}
{{::ticket.ticketState.state.name}}
</span>
</vn-td>
<vn-td shrink>

View File

@ -3,6 +3,21 @@ import Summary from 'salix/components/summary';
import './style.scss';
class Controller extends Summary {
constructor($element, $) {
super($element, $);
this.ticketFilter = {
include: {
relation: 'ticketState',
scope: {
fields: ['stateFk', 'code', 'alertLevel'],
include: {
relation: 'state'
}
}
}
};
}
$onChanges() {
if (!this.client)
return;
@ -18,6 +33,7 @@ class Controller extends Summary {
}
});
}
get isEmployee() {
return this.aclService.hasAny(['employee']);
}
@ -41,13 +57,15 @@ class Controller extends Summary {
}
stateColor(ticket) {
if (ticket.alertLevelCode === 'OK')
const ticketState = ticket.ticketState;
if (ticketState.code === 'OK')
return 'success';
else if (ticket.alertLevelCode === 'FREE')
else if (ticketState.code === 'FREE')
return 'notice';
else if (ticket.alertLevel === 1)
else if (ticketState.alertLevel === 1)
return 'warning';
else if (ticket.alertLevel === 0)
else if (ticketState.alertLevel === 0)
return 'alert';
}

View File

@ -76,25 +76,25 @@ describe('Client', () => {
describe('stateColor()', () => {
it('should return "success" when the alertLevelCode property is "OK"', () => {
const result = controller.stateColor({alertLevelCode: 'OK'});
const result = controller.stateColor({ticketState: {code: 'OK'}});
expect(result).toEqual('success');
});
it('should return "notice" when the alertLevelCode property is "FREE"', () => {
const result = controller.stateColor({alertLevelCode: 'FREE'});
const result = controller.stateColor({ticketState: {code: 'FREE'}});
expect(result).toEqual('notice');
});
it('should return "warning" when the alertLevel property is "1', () => {
const result = controller.stateColor({alertLevel: 1});
const result = controller.stateColor({ticketState: {code: 'PACKING', alertLevel: 1}});
expect(result).toEqual('warning');
});
it('should return "alert" when the alertLevel property is "0"', () => {
const result = controller.stateColor({alertLevel: 0});
const result = controller.stateColor({ticketState: {code: 'FIXING', alertLevel: 0}});
expect(result).toEqual('alert');
});

View File

@ -109,8 +109,9 @@ module.exports = Self => {
return {'ii.created': {gte: value}};
case 'to':
return {'ii.created': {lte: value}};
case 'account':
case 'fi':
return {'s.nif': value};
case 'account':
return {[`s.${param}`]: value};
case 'supplierRef':
case 'supplierFk':

View File

@ -54,19 +54,22 @@
</vn-icon-button>
</vn-td>
<vn-td shrink>
<vn-icon-button
<!-- <vn-icon-button
ng-show="invoiceIn.dmsFk"
vn-click-stop="$ctrl.openPdf(invoiceIn.dmsFk)"
icon="cloud_download"
title="Download PDF"
vn-tooltip="Download PDF">
</vn-icon-button>
</vn-icon-button> -->
</vn-td>
</a>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<vn-supplier-descriptor-popover
vn-id="supplierDescriptor">
</vn-supplier-descriptor-popover>
<vn-popup vn-id="summary">
<vn-invoice-in-summary
invoice-in="$ctrl.selectedInvoiceIn">

View File

@ -27,11 +27,12 @@
label="Account"
ng-model="filter.account">
</vn-textfield>
<vn-textfield
<vn-input-number
vn-one
label="Amount"
ng-model="filter.amount">
</vn-textfield>
ng-model="filter.amount"
step="0.01">
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-date-picker

View File

@ -36,7 +36,7 @@
<vn-td expand>{{::invoiceOut.created | date:'dd/MM/yyyy' | dashIfEmpty}}</vn-td>
<vn-td>{{::invoiceOut.companyCode | dashIfEmpty}}</vn-td>
<vn-td shrink>{{::invoiceOut.dued | date:'dd/MM/yyyy' | dashIfEmpty}}</vn-td>
<vn-td>
<vn-td shrink>
<vn-icon-button
ng-show="invoiceOut.hasPdf"
vn-click-stop="$ctrl.openPdf(invoiceOut.id)"
@ -45,7 +45,7 @@
vn-tooltip="Download PDF">
</vn-icon-button>
</vn-td>
<vn-td>
<vn-td shrink>
<vn-icon-button
vn-click-stop="$ctrl.preview(invoiceOut)"
vn-tooltip="Preview"

View File

@ -19,6 +19,11 @@ module.exports = Self => {
description: 'The client id',
required: true
},
{
arg: 'nickname',
type: 'string',
description: 'The client nickname'
},
{
arg: 'agencyModeFk',
type: 'number',
@ -145,10 +150,11 @@ module.exports = Self => {
// Force to unroute ticket
const hasToBeUnrouted = true;
const query = 'CALL vn.ticket_componentMakeUpdate(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
const query = 'CALL vn.ticket_componentMakeUpdate(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
const res = await Self.rawSql(query, [
args.id,
args.clientFk,
args.nickname,
args.agencyModeFk,
args.addressFk,
args.zoneFk,

View File

@ -20,6 +20,16 @@
initial-data="$ctrl.clientId"
order="id">
</vn-autocomplete>
<vn-autocomplete vn-one
required="true"
url="Warehouses"
label="Warehouse"
show-field="name"
value-field="id"
ng-model="$ctrl.warehouseId">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
required="true"
@ -44,14 +54,12 @@
</vn-icon-button>
</append>
</vn-autocomplete>
<vn-autocomplete vn-one
<vn-textfield
vn-one
label="Alias"
required="true"
url="Warehouses"
label="Warehouse"
show-field="name"
value-field="id"
ng-model="$ctrl.warehouseId">
</vn-autocomplete>
ng-model="$ctrl.ticket.nickname">
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete

View File

@ -40,6 +40,7 @@ class Controller extends Component {
set addressId(value) {
if (value != this.ticket.addressFk) {
this.ticket.addressFk = value;
this.onChangeAddress(value);
this.getShipped({
landed: this.ticket.landed,
addressFk: value,
@ -179,6 +180,14 @@ class Controller extends Component {
});
}
onChangeAddress(addressId) {
this.ticket.nickname = null;
const query = `Addresses/${addressId}`;
this.$http.get(query).then(res => {
this.ticket.nickname = res.data.nickname;
});
}
async onStepChange() {
if (this.isFormInvalid()) {
return this.vnApp.showError(

View File

@ -71,6 +71,7 @@ class Controller extends Component {
let query = `tickets/${this.ticket.id}/componentUpdate`;
let params = {
clientFk: this.ticket.clientFk,
nickname: this.ticket.nickname,
agencyModeFk: this.ticket.agencyModeFk,
addressFk: this.ticket.addressFk,
zoneFk: this.ticket.zoneFk,

View File

@ -5,38 +5,54 @@ module.exports = Self => {
description: 'Adds a new hour registry',
accessType: 'WRITE',
accepts: [{
arg: 'data',
type: 'object',
required: true,
description: 'workerFk, timed',
http: {source: 'body'}
arg: 'id',
type: 'number',
description: 'The worker id',
http: {source: 'path'}
},
{
arg: 'timed',
type: 'date',
required: true
},
{
arg: 'direction',
type: 'string',
required: true
}],
returns: [{
type: 'Object',
root: true
}],
http: {
path: `/addTimeEntry`,
path: `/:id/addTimeEntry`,
verb: 'POST'
}
});
Self.addTimeEntry = async(ctx, data) => {
const Worker = Self.app.models.Worker;
const myUserId = ctx.req.accessToken.userId;
const myWorker = await Worker.findOne({where: {userFk: myUserId}});
const isSubordinate = await Worker.isSubordinate(ctx, data.workerFk);
const isTeamBoss = await Self.app.models.Account.hasRole(myUserId, 'teamBoss');
Self.addTimeEntry = async(ctx, workerId, options) => {
const models = Self.app.models;
const args = ctx.args;
const currentUserId = ctx.req.accessToken.userId;
if (isSubordinate === false || (isSubordinate && myWorker.id == data.workerFk && !isTeamBoss))
let myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const isSubordinate = await models.Worker.isSubordinate(ctx, workerId, myOptions);
const isTeamBoss = await models.Account.hasRole(currentUserId, 'teamBoss', myOptions);
const isHimself = currentUserId == workerId;
if (isSubordinate === false || (isSubordinate && isHimself && !isTeamBoss))
throw new UserError(`You don't have enough privileges`);
const subordinate = await Worker.findById(data.workerFk);
const timed = new Date(data.timed);
const timed = new Date(args.timed);
let [result] = await Self.rawSql('SELECT vn.workerTimeControl_add(?, ?, ?, ?) AS id', [
subordinate.userFk, null, timed, true]);
return result;
return models.WorkerTimeControl.create({
userFk: workerId,
direction: args.direction,
timed: timed,
manual: true
}, myOptions);
};
};

View File

@ -21,21 +21,24 @@ module.exports = Self => {
}
});
Self.deleteTimeEntry = async(ctx, id) => {
Self.deleteTimeEntry = async(ctx, id, options) => {
const currentUserId = ctx.req.accessToken.userId;
const workerModel = Self.app.models.Worker;
const models = Self.app.models;
const targetTimeEntry = await Self.findById(id);
const isSubordinate = await workerModel.isSubordinate(ctx, targetTimeEntry.userFk);
const isTeamBoss = await Self.app.models.Account.hasRole(currentUserId, 'teamBoss');
let myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const targetTimeEntry = await Self.findById(id, null, myOptions);
const isSubordinate = await models.Worker.isSubordinate(ctx, targetTimeEntry.userFk, myOptions);
const isTeamBoss = await models.Account.hasRole(currentUserId, 'teamBoss', myOptions);
const isHimself = currentUserId == targetTimeEntry.userFk;
const notAllowed = isSubordinate === false || (isSubordinate && isHimself && !isTeamBoss);
if (notAllowed)
if (isSubordinate === false || (isSubordinate && isHimself && !isTeamBoss))
throw new UserError(`You don't have enough privileges`);
return Self.rawSql('CALL vn.workerTimeControl_remove(?, ?)', [
targetTimeEntry.userFk, targetTimeEntry.timed]);
targetTimeEntry.userFk, targetTimeEntry.timed], myOptions);
};
};

View File

@ -1,5 +1,6 @@
const app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
const models = app.models;
describe('workerTimeControl add/delete timeEntry()', () => {
const HHRRId = 37;
@ -12,19 +13,6 @@ describe('workerTimeControl add/delete timeEntry()', () => {
};
let ctx = {req: activeCtx};
let timeEntry;
let createdTimeEntry;
afterEach(async() => {
if (createdTimeEntry) {
try {
await app.models.WorkerTimeControl.destroyById(createdTimeEntry.id);
} catch (error) {
console.error(error);
}
}
});
beforeAll(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
@ -33,14 +21,13 @@ describe('workerTimeControl add/delete timeEntry()', () => {
it('should fail to add a time entry if the target user is not a subordinate', async() => {
activeCtx.accessToken.userId = employeeId;
const workerId = 2;
let error;
let data = {
workerFk: 2,
timed: new Date()
};
try {
await app.models.WorkerTimeControl.addTimeEntry(ctx, data);
ctx.args = {timed: new Date(), direction: 'in'};
await models.WorkerTimeControl.addTimeEntry(ctx, workerId);
} catch (e) {
error = e;
}
@ -52,14 +39,12 @@ describe('workerTimeControl add/delete timeEntry()', () => {
it('should fail to add if the current and the target user are the same and is not team boss', async() => {
activeCtx.accessToken.userId = employeeId;
const workerId = employeeId;
let error;
let data = {
workerFk: 1,
timed: new Date()
};
try {
await app.models.WorkerTimeControl.addTimeEntry(ctx, data);
ctx.args = {timed: new Date(), direction: 'in'};
await models.WorkerTimeControl.addTimeEntry(ctx, workerId);
} catch (e) {
error = e;
}
@ -71,41 +56,49 @@ describe('workerTimeControl add/delete timeEntry()', () => {
it('should add if the current user is team boss and the target user is a himself', async() => {
activeCtx.accessToken.userId = teamBossId;
let todayAtSix = new Date();
todayAtSix.setHours(18, 30, 0, 0);
const workerId = teamBossId;
let data = {
workerFk: teamBossId,
timed: todayAtSix
};
const tx = await models.WorkerTimeControl.beginTransaction({});
try {
const options = {transaction: tx};
timeEntry = await app.models.WorkerTimeControl.addTimeEntry(ctx, data);
const todayAtSix = new Date();
todayAtSix.setHours(18, 30, 0, 0);
createdTimeEntry = await app.models.WorkerTimeControl.findById(timeEntry.id);
ctx.args = {timed: todayAtSix, direction: 'in'};
const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
expect(createdTimeEntry).toBeDefined();
expect(createdTimeEntry.id).toBeDefined();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should try but fail to delete his own time entry', async() => {
activeCtx.accessToken.userId = salesBossId;
const workerId = salesBossId;
let error;
let todayAtSeven = new Date();
todayAtSeven.setHours(19, 30, 0, 0);
let data = {
workerFk: salesPersonId,
timed: todayAtSeven
};
timeEntry = await app.models.WorkerTimeControl.addTimeEntry(ctx, data);
createdTimeEntry = await app.models.WorkerTimeControl.findById(timeEntry.id);
const tx = await models.WorkerTimeControl.beginTransaction({});
try {
const options = {transaction: tx};
const todayAtSeven = new Date();
todayAtSeven.setHours(19, 30, 0, 0);
ctx.args = {timed: todayAtSeven, direction: 'in'};
const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
activeCtx.accessToken.userId = salesPersonId;
await app.models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id);
await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options);
await tx.rollback();
} catch (e) {
error = e;
await tx.rollback();
}
expect(error).toBeDefined();
@ -115,49 +108,84 @@ describe('workerTimeControl add/delete timeEntry()', () => {
it('should delete the created time entry for the team boss as himself', async() => {
activeCtx.accessToken.userId = teamBossId;
const workerId = teamBossId;
let todayAtFive = new Date();
todayAtFive.setHours(17, 30, 0, 0);
const tx = await models.WorkerTimeControl.beginTransaction({});
try {
const options = {transaction: tx};
let data = {
workerFk: teamBossId,
timed: todayAtFive
};
const todayAtFive = new Date();
todayAtFive.setHours(17, 30, 0, 0);
timeEntry = await app.models.WorkerTimeControl.addTimeEntry(ctx, data);
ctx.args = {timed: todayAtFive, direction: 'in'};
const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
createdTimeEntry = await app.models.WorkerTimeControl.findById(timeEntry.id);
expect(createdTimeEntry.id).toBeDefined();
expect(createdTimeEntry).toBeDefined();
await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options);
await app.models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id);
const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options);
createdTimeEntry = await app.models.WorkerTimeControl.findById(timeEntry.id);
expect(createdTimeEntry).toBeNull();
expect(deletedTimeEntry).toBeNull();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should delete the created time entry for the team boss as HHRR', async() => {
activeCtx.accessToken.userId = HHRRId;
const workerId = teamBossId;
let todayAtFive = new Date();
todayAtFive.setHours(17, 30, 0, 0);
const tx = await models.WorkerTimeControl.beginTransaction({});
try {
const options = {transaction: tx};
let data = {
workerFk: teamBossId,
timed: todayAtFive
};
const todayAtFive = new Date();
todayAtFive.setHours(17, 30, 0, 0);
timeEntry = await app.models.WorkerTimeControl.addTimeEntry(ctx, data);
ctx.args = {timed: todayAtFive, direction: 'in'};
const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
createdTimeEntry = await app.models.WorkerTimeControl.findById(timeEntry.id);
expect(createdTimeEntry.id).toBeDefined();
expect(createdTimeEntry).toBeDefined();
await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options);
await app.models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id);
const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options);
createdTimeEntry = await app.models.WorkerTimeControl.findById(timeEntry.id);
expect(deletedTimeEntry).toBeNull();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
expect(createdTimeEntry).toBeNull();
it('should edit the created time entry for the team boss as HHRR', async() => {
activeCtx.accessToken.userId = HHRRId;
const workerId = teamBossId;
const tx = await models.WorkerTimeControl.beginTransaction({});
try {
const options = {transaction: tx};
const todayAtFive = new Date();
todayAtFive.setHours(17, 30, 0, 0);
ctx.args = {timed: todayAtFive, direction: 'in'};
const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
expect(createdTimeEntry.id).toBeDefined();
ctx.args = {direction: 'out'};
const updatedTimeEntry = await models.WorkerTimeControl.updateTimeEntry(ctx, createdTimeEntry.id, options);
expect(updatedTimeEntry.direction).toEqual('out');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -0,0 +1,53 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('updateTimeEntry', {
description: 'Updates a time entry for a worker if the user role is above the worker',
accessType: 'READ',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'The time entry id',
http: {source: 'path'}
},
{
arg: 'direction',
type: 'string',
required: true
}],
returns: {
type: 'boolean',
root: true
},
http: {
path: `/:id/updateTimeEntry`,
verb: 'POST'
}
});
Self.updateTimeEntry = async(ctx, id, options) => {
const currentUserId = ctx.req.accessToken.userId;
const models = Self.app.models;
const args = ctx.args;
let myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const targetTimeEntry = await Self.findById(id, null, myOptions);
const isSubordinate = await models.Worker.isSubordinate(ctx, targetTimeEntry.userFk, myOptions);
const isTeamBoss = await models.Account.hasRole(currentUserId, 'teamBoss', myOptions);
const isHimself = currentUserId == targetTimeEntry.userFk;
const notAllowed = isSubordinate === false || (isSubordinate && isHimself && !isTeamBoss);
if (notAllowed)
throw new UserError(`You don't have enough privileges`);
return targetTimeEntry.updateAttributes({
direction: args.direction
}, myOptions);
};
};

View File

@ -4,6 +4,7 @@ module.exports = Self => {
require('../methods/worker-time-control/filter')(Self);
require('../methods/worker-time-control/addTimeEntry')(Self);
require('../methods/worker-time-control/deleteTimeEntry')(Self);
require('../methods/worker-time-control/updateTimeEntry')(Self);
Self.rewriteDbError(function(err) {
if (err.code === 'ER_DUP_ENTRY')

View File

@ -9,16 +9,16 @@
"properties": {
"id": {
"id": true,
"type": "Number"
"type": "number"
},
"timed": {
"type": "Date"
"type": "date"
},
"manual": {
"type": "Boolean"
"type": "boolean"
},
"order": {
"type": "Number"
"type": "number"
},
"direction": {
"type": "string"

View File

@ -23,7 +23,7 @@
<vn-side-menu side="right">
<div class="vn-pa-md">
<div class="totalBox vn-mb-sm" style="text-align: center;">
<h6>{{'Contract' | translate}} ID: {{$ctrl.businessId}}</h6>
<h6>{{'Contract' | translate}} #{{$ctrl.businessId}}</h6>
<div>
{{'Used' | translate}} {{$ctrl.contractHolidays.holidaysEnjoyed}}
{{'of' | translate}} {{$ctrl.contractHolidays.totalHolidays || 0}} {{'days' | translate}}
@ -55,7 +55,7 @@
order="businessFk DESC"
limit="5">
<tpl-item>
<div>ID: {{businessFk}}</div>
<div>#{{businessFk}}</div>
<div class="text-caption text-secondary">
{{started | date: 'dd/MM/yyyy'}} - {{ended ? (ended | date: 'dd/MM/yyyy') : 'Indef.'}}
</div>

View File

@ -43,9 +43,16 @@
ng-class="::{'invisible': hour.direction == 'middle'}">
</vn-icon>
<vn-chip
ng-class="::{'colored': hour.manual}"
ng-class="::{'colored': hour.manual, 'clickable': true}"
removable="::hour.manual"
on-remove="$ctrl.showDeleteDialog(hour)">
on-remove="$ctrl.showDeleteDialog($event, hour)"
ng-click="$ctrl.edit($event, hour)"
>
<prepend>
<vn-icon icon="edit"
vn-tooltip="Edit">
</vn-icon>
</prepend>
{{::hour.timed | date: 'HH:mm'}}
</vn-chip>
</section>
@ -97,10 +104,20 @@
<tpl-body>
<vn-input-time
vn-one
ng-model="$ctrl.newTime"
vn-focus
ng-model="$ctrl.newTimeEntry.timed"
label="Hour"
vn-focus>
required="true">
</vn-input-time>
<vn-autocomplete
label="Type"
ng-model="$ctrl.newTimeEntry.direction"
data="$ctrl.entryDirections"
select-fields="['code','description']"
show-field="description"
value-field="code"
required="true">
</vn-autocomplete>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
@ -112,4 +129,22 @@
on-accept="$ctrl.deleteTimeEntry()"
message="This time entry will be deleted"
question="Are you sure you want to delete this entry?">
</vn-confirm>
</vn-confirm>
<!-- Edit entry Popover -->
<vn-popover vn-id="editEntry">
<vn-horizontal class="vn-pa-sm edit-time-entry">
<vn-autocomplete class="dense"
ng-model="$ctrl.selectedRow.direction"
data="$ctrl.entryDirections"
select-fields="['code','description']"
show-field="description"
value-field="code">
</vn-autocomplete>
<vn-icon-button vn-none
icon="check"
vn-tooltip="Save"
ng-click="$ctrl.save()">
</vn-icon-button>
</vn-horizontal>
</vn-popover>

View File

@ -7,6 +7,11 @@ class Controller extends Section {
super($element, $);
this.weekDays = [];
this.weekdayNames = vnWeekDays.locales;
this.entryDirections = [
{code: 'in', description: this.$t('In')},
{code: 'middle', description: this.$t('Intermediate')},
{code: 'out', description: this.$t('Out')}
];
}
$postLink() {
@ -241,21 +246,34 @@ class Controller extends Section {
const timed = new Date(weekday.dated.getTime());
timed.setHours(0, 0, 0, 0);
this.newTime = timed;
this.newTimeEntry = {
workerFk: this.$params.id,
timed: timed
};
this.selectedWeekday = weekday;
this.$.addTimeDialog.show();
}
addTime() {
let data = {
workerFk: this.$params.id,
timed: this.newTime
};
this.$http.post(`WorkerTimeControls/addTimeEntry`, data)
.then(() => this.fetchHours());
try {
const entry = this.newTimeEntry;
if (!entry.direction)
throw new Error(`The entry type can't be empty`);
const query = `WorkerTimeControls/${this.worker.id}/addTimeEntry`;
this.$http.post(query, entry)
.then(() => this.fetchHours());
} catch (e) {
this.vnApp.showError(this.$t(e.message));
return false;
}
return true;
}
showDeleteDialog(hour) {
showDeleteDialog($event, hour) {
$event.preventDefault();
this.timeEntryToDelete = hour;
this.$.deleteEntryDialog.show();
}
@ -268,6 +286,29 @@ class Controller extends Section {
this.vnApp.showSuccess(this.$t('Entry removed'));
});
}
edit($event, hour) {
if ($event.defaultPrevented) return;
this.selectedRow = hour;
this.$.editEntry.show($event);
}
save() {
try {
const entry = this.selectedRow;
if (!entry.direction)
throw new Error(`The entry type can't be empty`);
const query = `WorkerTimeControls/${entry.id}/updateTimeEntry`;
this.$http.post(query, {direction: entry.direction})
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')))
.then(() => this.$.editEntry.hide())
.then(() => this.fetchHours());
} catch (e) {
this.vnApp.showError(this.$t(e.message));
}
}
}
Controller.$inject = ['$element', '$scope', 'vnWeekDays'];

View File

@ -113,5 +113,21 @@ describe('Component vnWorkerTimeControl', () => {
expect(result).toEqual('01:00');
});
});
describe('save() ', () => {
it(`should make a query an then call to the fetchHours() method`, () => {
controller.fetchHours = jest.fn();
controller.selectedRow = {id: 1, timed: new Date(), direction: 'in'};
controller.$.editEntry = {
hide: () => {}
};
const expectedParams = {direction: 'in'};
$httpBackend.expect('POST', 'WorkerTimeControls/1/updateTimeEntry', expectedParams).respond(200);
controller.save();
$httpBackend.flush();
expect(controller.fetchHours).toHaveBeenCalledWith();
});
});
});
});

View File

@ -1,5 +1,6 @@
In: Entrada
Out: Salida
Intermediate: Intermedio
Hour: Hora
Hours: Horas
Add time: Añadir hora
@ -8,4 +9,5 @@ Current week: Semana actual
This time entry will be deleted: Se eliminará la hora fichada
Are you sure you want to delete this entry?: ¿Seguro que quieres eliminarla?
Finish at: Termina a las
Entry removed: Fichada borrada
Entry removed: Fichada borrada
The entry type can't be empty: El tipo de fichada no puede quedar vacía

View File

@ -24,4 +24,8 @@ vn-worker-time-control {
.totalBox {
max-width: none
}
}
.edit-time-entry {
width: 200px
}