diff --git a/db/changes/10330-jun2021/01-ticket_componentMakeUpdate.sql b/db/changes/10330-jun2021/01-ticket_componentMakeUpdate.sql new file mode 100644 index 000000000..ac3e85f08 --- /dev/null +++ b/db/changes/10330-jun2021/01-ticket_componentMakeUpdate.sql @@ -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 ; + diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index a31f11b56..f65ebc95d 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -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)', diff --git a/e2e/paths/03-worker/04_time_control.spec.js b/e2e/paths/03-worker/04_time_control.spec.js index dbcebff08..3f5653aee 100644 --- a/e2e/paths/03-worker/04_time_control.spec.js +++ b/e2e/paths/03-worker/04_time_control.spec.js @@ -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'); diff --git a/e2e/paths/14-account/01_create_and_basic_data.spec.js b/e2e/paths/14-account/01_create_and_basic_data.spec.js index 15c9118d1..5a07119e7 100644 --- a/e2e/paths/14-account/01_create_and_basic_data.spec.js +++ b/e2e/paths/14-account/01_create_and_basic_data.spec.js @@ -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'); diff --git a/front/core/components/chip/index.html b/front/core/components/chip/index.html index 38a56923d..7cd56031c 100644 --- a/front/core/components/chip/index.html +++ b/front/core/components/chip/index.html @@ -1,6 +1,12 @@ +
+
diff --git a/front/core/components/chip/index.js b/front/core/components/chip/index.js index 6cffecc2b..ff0f19ba2 100644 --- a/front/core/components/chip/index.js +++ b/front/core/components/chip/index.js @@ -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: ' { 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}); }); }); }); diff --git a/front/core/components/chip/style.scss b/front/core/components/chip/style.scss index f6e4a388f..d1632890c 100644 --- a/front/core/components/chip/style.scss +++ b/front/core/components/chip/style.scss @@ -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 { diff --git a/loopback/locale/en.json b/loopback/locale/en.json index 6bdaddd9b..9bef13527 100644 --- a/loopback/locale/en.json +++ b/loopback/locale/en.json @@ -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" } \ No newline at end of file diff --git a/modules/client/front/summary/index.html b/modules/client/front/summary/index.html index 956f9d198..a93aec4ea 100644 --- a/modules/client/front/summary/index.html +++ b/modules/client/front/summary/index.html @@ -1,11 +1,12 @@ + order="shipped DESC">
@@ -292,9 +293,9 @@ Id Client - Salesperson + Packages Date - State + State Total @@ -312,13 +313,8 @@ {{::ticket.nickname}} - - - {{::ticket.userName | dashIfEmpty}} - + + {{::ticket.packages}} @@ -336,7 +332,7 @@ - {{::ticket.state}} + {{::ticket.ticketState.state.name}} diff --git a/modules/client/front/summary/index.js b/modules/client/front/summary/index.js index 40c000118..113fd2ab2 100644 --- a/modules/client/front/summary/index.js +++ b/modules/client/front/summary/index.js @@ -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'; } diff --git a/modules/client/front/summary/index.spec.js b/modules/client/front/summary/index.spec.js index 08f3f8554..397bb4240 100644 --- a/modules/client/front/summary/index.spec.js +++ b/modules/client/front/summary/index.spec.js @@ -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'); }); diff --git a/modules/invoiceIn/back/methods/invoice-in/filter.js b/modules/invoiceIn/back/methods/invoice-in/filter.js index b480df64a..3b9330eb9 100644 --- a/modules/invoiceIn/back/methods/invoice-in/filter.js +++ b/modules/invoiceIn/back/methods/invoice-in/filter.js @@ -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': diff --git a/modules/invoiceIn/front/index/index.html b/modules/invoiceIn/front/index/index.html index 3d9b787d9..62074be33 100644 --- a/modules/invoiceIn/front/index/index.html +++ b/modules/invoiceIn/front/index/index.html @@ -54,19 +54,22 @@ - - + --> + + diff --git a/modules/invoiceIn/front/search-panel/index.html b/modules/invoiceIn/front/search-panel/index.html index 90bb73725..d26bc063e 100644 --- a/modules/invoiceIn/front/search-panel/index.html +++ b/modules/invoiceIn/front/search-panel/index.html @@ -27,11 +27,12 @@ label="Account" ng-model="filter.account"> - - + ng-model="filter.amount" + step="0.01"> + {{::invoiceOut.created | date:'dd/MM/yyyy' | dashIfEmpty}} {{::invoiceOut.companyCode | dashIfEmpty}} {{::invoiceOut.dued | date:'dd/MM/yyyy' | dashIfEmpty}} - + - + { 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, diff --git a/modules/ticket/front/basic-data/step-one/index.html b/modules/ticket/front/basic-data/step-one/index.html index 98d92e7e2..24ec509a2 100644 --- a/modules/ticket/front/basic-data/step-one/index.html +++ b/modules/ticket/front/basic-data/step-one/index.html @@ -20,6 +20,16 @@ initial-data="$ctrl.clientId" order="id"> + + + + - - + ng-model="$ctrl.ticket.nickname"> + { + this.ticket.nickname = res.data.nickname; + }); + } + async onStepChange() { if (this.isFormInvalid()) { return this.vnApp.showError( diff --git a/modules/ticket/front/basic-data/step-two/index.js b/modules/ticket/front/basic-data/step-two/index.js index c578106ed..ebb93bb3d 100644 --- a/modules/ticket/front/basic-data/step-two/index.js +++ b/modules/ticket/front/basic-data/step-two/index.js @@ -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, diff --git a/modules/worker/back/methods/worker-time-control/addTimeEntry.js b/modules/worker/back/methods/worker-time-control/addTimeEntry.js index 86030d713..2079a62a3 100644 --- a/modules/worker/back/methods/worker-time-control/addTimeEntry.js +++ b/modules/worker/back/methods/worker-time-control/addTimeEntry.js @@ -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); }; }; diff --git a/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js b/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js index 97637d197..23e4c5fff 100644 --- a/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js +++ b/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js @@ -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); }; }; diff --git a/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js b/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js index 0f055bdc5..a50182f1b 100644 --- a/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js +++ b/modules/worker/back/methods/worker-time-control/specs/timeEntry.spec.js @@ -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; + } }); }); diff --git a/modules/worker/back/methods/worker-time-control/updateTimeEntry.js b/modules/worker/back/methods/worker-time-control/updateTimeEntry.js new file mode 100644 index 000000000..abeda7f8e --- /dev/null +++ b/modules/worker/back/methods/worker-time-control/updateTimeEntry.js @@ -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); + }; +}; diff --git a/modules/worker/back/models/worker-time-control.js b/modules/worker/back/models/worker-time-control.js index f0191c36f..45f4e2194 100644 --- a/modules/worker/back/models/worker-time-control.js +++ b/modules/worker/back/models/worker-time-control.js @@ -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') diff --git a/modules/worker/back/models/worker-time-control.json b/modules/worker/back/models/worker-time-control.json index 5212222bd..ab07802ca 100644 --- a/modules/worker/back/models/worker-time-control.json +++ b/modules/worker/back/models/worker-time-control.json @@ -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" diff --git a/modules/worker/front/calendar/index.html b/modules/worker/front/calendar/index.html index f012e8e55..42d363952 100644 --- a/modules/worker/front/calendar/index.html +++ b/modules/worker/front/calendar/index.html @@ -23,7 +23,7 @@
-
{{'Contract' | translate}} ID: {{$ctrl.businessId}}
+
{{'Contract' | translate}} #{{$ctrl.businessId}}
{{'Used' | translate}} {{$ctrl.contractHolidays.holidaysEnjoyed}} {{'of' | translate}} {{$ctrl.contractHolidays.totalHolidays || 0}} {{'days' | translate}} @@ -55,7 +55,7 @@ order="businessFk DESC" limit="5"> -
ID: {{businessFk}}
+
#{{businessFk}}
{{started | date: 'dd/MM/yyyy'}} - {{ended ? (ended | date: 'dd/MM/yyyy') : 'Indef.'}}
diff --git a/modules/worker/front/time-control/index.html b/modules/worker/front/time-control/index.html index a333b8585..66d6f282e 100644 --- a/modules/worker/front/time-control/index.html +++ b/modules/worker/front/time-control/index.html @@ -43,9 +43,16 @@ ng-class="::{'invisible': hour.direction == 'middle'}"> + on-remove="$ctrl.showDeleteDialog($event, hour)" + ng-click="$ctrl.edit($event, hour)" + > + + + + {{::hour.timed | date: 'HH:mm'}} @@ -97,10 +104,20 @@ + required="true"> + + @@ -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?"> - \ No newline at end of file + + + + + + + + + + + \ No newline at end of file diff --git a/modules/worker/front/time-control/index.js b/modules/worker/front/time-control/index.js index bed9e7af3..b980243c9 100644 --- a/modules/worker/front/time-control/index.js +++ b/modules/worker/front/time-control/index.js @@ -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']; diff --git a/modules/worker/front/time-control/index.spec.js b/modules/worker/front/time-control/index.spec.js index 4e9730d9c..920f13e7c 100644 --- a/modules/worker/front/time-control/index.spec.js +++ b/modules/worker/front/time-control/index.spec.js @@ -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(); + }); + }); }); }); diff --git a/modules/worker/front/time-control/locale/es.yml b/modules/worker/front/time-control/locale/es.yml index 1d28b0e2b..8b2486f05 100644 --- a/modules/worker/front/time-control/locale/es.yml +++ b/modules/worker/front/time-control/locale/es.yml @@ -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 \ No newline at end of file +Entry removed: Fichada borrada +The entry type can't be empty: El tipo de fichada no puede quedar vacía \ No newline at end of file diff --git a/modules/worker/front/time-control/style.scss b/modules/worker/front/time-control/style.scss index 99a21883f..6a46921a7 100644 --- a/modules/worker/front/time-control/style.scss +++ b/modules/worker/front/time-control/style.scss @@ -24,4 +24,8 @@ vn-worker-time-control { .totalBox { max-width: none } +} + +.edit-time-entry { + width: 200px } \ No newline at end of file