Merge pull request '2874 - Edit time entry direction' (#666) from 2874-timeEntry_direction into dev
gitea/salix/pipeline/head This commit looks good Details

Reviewed-on: #666
Reviewed-by: Carlos Jimenez Ruiz <carlosjr@verdnatura.es>
This commit is contained in:
Carlos Jimenez Ruiz 2021-06-18 15:11:06 +00:00
commit 5238ebfd9d
20 changed files with 466 additions and 187 deletions

View File

@ -837,7 +837,8 @@ export default {
saveButton: 'vn-worker-pbx button[type=submit]' saveButton: 'vn-worker-pbx button[type=submit]'
}, },
workerTimeControl: { 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', 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', 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', 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', 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', 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', 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', 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', 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', 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', 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', 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', 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', 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', 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', 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', 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', 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', 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', 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', 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', 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"]', 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', 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', 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', 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', 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', 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', 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', 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', 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', 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', 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', 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', 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', 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)', 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)', 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)', 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'; const scanTime = '07:00';
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText'); const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText');
@ -33,7 +34,8 @@ describe('Worker time control path', () => {
const scanTime = '10:00'; const scanTime = '10:00';
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfMonday, 'innerText'); const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfMonday, 'innerText');
@ -44,7 +46,8 @@ describe('Worker time control path', () => {
const scanTime = '18:00'; const scanTime = '18:00';
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfMonday, 'innerText'); const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfMonday, 'innerText');
@ -66,7 +69,8 @@ describe('Worker time control path', () => {
const scanTime = '14:00'; const scanTime = '14:00';
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfMonday, 'innerText'); const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfMonday, 'innerText');
@ -77,7 +81,8 @@ describe('Worker time control path', () => {
const scanTime = '10:20'; const scanTime = '10:20';
await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfMonday, 'innerText'); const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfMonday, 'innerText');
@ -103,7 +108,8 @@ describe('Worker time control path', () => {
const scanTime = '08:00'; const scanTime = '08:00';
await page.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfTuesday, 'innerText'); const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfTuesday, 'innerText');
@ -114,7 +120,8 @@ describe('Worker time control path', () => {
const scanTime = '10:00'; const scanTime = '10:00';
await page.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfTuesday, 'innerText'); const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfTuesday, 'innerText');
@ -125,7 +132,8 @@ describe('Worker time control path', () => {
const scanTime = '10:20'; const scanTime = '10:20';
await page.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfTuesday, 'innerText'); const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfTuesday, 'innerText');
@ -136,7 +144,8 @@ describe('Worker time control path', () => {
const scanTime = '16:00'; const scanTime = '16:00';
await page.waitToClick(selectors.workerTimeControl.tuesdayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfTuesday, 'innerText'); const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfTuesday, 'innerText');
@ -153,7 +162,8 @@ describe('Worker time control path', () => {
const scanTime = '09:00'; const scanTime = '09:00';
await page.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfWednesday, 'innerText'); const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfWednesday, 'innerText');
@ -164,7 +174,8 @@ describe('Worker time control path', () => {
const scanTime = '10:00'; const scanTime = '10:00';
await page.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfWednesday, 'innerText'); const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfWednesday, 'innerText');
@ -175,7 +186,8 @@ describe('Worker time control path', () => {
const scanTime = '10:20'; const scanTime = '10:20';
await page.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfWednesday, 'innerText'); const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfWednesday, 'innerText');
@ -186,7 +198,8 @@ describe('Worker time control path', () => {
const scanTime = '17:00'; const scanTime = '17:00';
await page.waitToClick(selectors.workerTimeControl.wednesdayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfWednesday, 'innerText'); const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfWednesday, 'innerText');
@ -203,7 +216,8 @@ describe('Worker time control path', () => {
const scanTime = '09:59'; const scanTime = '09:59';
await page.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfThursday, 'innerText'); 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() => { it(`should joyfully scan out Hank Pym for break`, async() => {
const scanTime = '10:00'; const scanTime = '10:00';
await page.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfThursday, 'innerText'); 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() => { it(`should joyfully scan in Hank Pym from the break`, async() => {
const scanTime = '10:20'; const scanTime = '10:20';
await page.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfThursday, 'innerText'); 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() => { it(`should joyfully scan out Hank Pym for the day`, async() => {
const scanTime = '17:59'; const scanTime = '17:59';
await page.waitToClick(selectors.workerTimeControl.thursdayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfThursday, 'innerText'); 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() => { it('should smilingly scan in Hank Pym', async() => {
const scanTime = '07:30'; const scanTime = '07:30';
await page.waitToClick(selectors.workerTimeControl.fridayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfFriday, 'innerText'); 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() => { it(`should smilingly scan out Hank Pym for break`, async() => {
const scanTime = '10:00'; const scanTime = '10:00';
await page.waitToClick(selectors.workerTimeControl.fridayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfFriday, 'innerText'); 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() => { it(`should smilingly scan in Hank Pym from the break`, async() => {
const scanTime = '10:20'; const scanTime = '10:20';
await page.waitToClick(selectors.workerTimeControl.fridayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.thirdEntryOfFriday, 'innerText'); 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() => { it(`should smilingly scan out Hank Pym for the day`, async() => {
const scanTime = '15:30'; const scanTime = '15:30';
await page.waitToClick(selectors.workerTimeControl.fridayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.fourthEntryOfFriday, 'innerText'); 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() => { it('should lovingly scan in Hank Pym', async() => {
const scanTime = '06:00'; 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.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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfSaturday, 'innerText'); 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() => { it(`should lovingly scan out Hank Pym for the day with no break to leave a bit early`, async() => {
const scanTime = '13:40'; const scanTime = '13:40';
await page.waitToClick(selectors.workerTimeControl.saturdayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfSaturday, 'innerText'); 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() => { it('should gladly scan in Hank Pym', async() => {
const scanTime = '05:00'; const scanTime = '05:00';
await page.waitToClick(selectors.workerTimeControl.sundayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfSunday, 'innerText'); 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() => { it(`should gladly scan out Hank Pym for the day with no break to leave a bit early`, async() => {
const scanTime = '12:40'; const scanTime = '12:40';
await page.waitToClick(selectors.workerTimeControl.sundayAddTimeButton); 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'); await page.respondToDialog('accept');
const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfSunday, 'innerText'); 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('Descriptor option', () => {
describe('Edit role', () => { describe('Edit role', () => {
it('should edit the role using the descriptor menu', async() => { 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.menuButton);
await page.waitToClick(selectors.accountDescriptor.changeRole); await page.waitToClick(selectors.accountDescriptor.changeRole);
await page.autocompleteSearch(selectors.accountDescriptor.newRole, 'adminBoss'); 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></div>
<div
ng-transclude="append"
class="append"></div>
<vn-icon <vn-icon
ng-click="$ctrl.onRemove()" ng-click="$ctrl.onRemove($event)"
ng-if="$ctrl.removable" ng-if="$ctrl.removable"
icon="cancel" icon="cancel"
tabindex="0"> tabindex="0">

View File

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

View File

@ -18,9 +18,14 @@ describe('Component vnChip', () => {
it(`should emit remove event`, () => { it(`should emit remove event`, () => {
controller.emit = () => {}; controller.emit = () => {};
jest.spyOn(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 "variables";
@import "effects";
vn-chip { vn-chip {
border-radius: 16px; border-radius: 16px;
@ -24,25 +25,47 @@ vn-chip {
&.transparent { &.transparent {
background-color: transparent; background-color: transparent;
} }
&.colored { &.colored,
&.colored.clickable:hover,
&.colored.clickable:focus {
background-color: $color-main; background-color: $color-main;
color: $color-font-bg; 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; background-color: $color-success-medium;
} }
&.warning { &.warning,
&.warning.clickable:hover,
&.warning.clickable:focus {
background-color: $color-main-medium; background-color: $color-main-medium;
} }
&.alert { &.alert,
&.alert.clickable:hover,
&.alert.clickable:focus {
background-color: $color-alert-medium; background-color: $color-alert-medium;
} }
&.message { &.message,
&.message.clickable:hover,
&.message.clickable:focus {
color: $color-font-dark; 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 { & > div {
@ -75,6 +98,20 @@ vn-chip {
opacity: 1; opacity: 1;
} }
} }
& > .prepend {
padding: 0 5px;
padding-right: 0;
&:empty {display:none;}
}
& > .append {
padding: 0 5px;
padding-left: 0;
&:empty {display:none;}
}
} }
vn-avatar { vn-avatar {

View File

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

View File

@ -5,38 +5,54 @@ module.exports = Self => {
description: 'Adds a new hour registry', description: 'Adds a new hour registry',
accessType: 'WRITE', accessType: 'WRITE',
accepts: [{ accepts: [{
arg: 'data', arg: 'id',
type: 'object', type: 'number',
required: true, description: 'The worker id',
description: 'workerFk, timed', http: {source: 'path'}
http: {source: 'body'} },
{
arg: 'timed',
type: 'date',
required: true
},
{
arg: 'direction',
type: 'string',
required: true
}], }],
returns: [{ returns: [{
type: 'Object', type: 'Object',
root: true root: true
}], }],
http: { http: {
path: `/addTimeEntry`, path: `/:id/addTimeEntry`,
verb: 'POST' verb: 'POST'
} }
}); });
Self.addTimeEntry = async(ctx, data) => { Self.addTimeEntry = async(ctx, workerId, options) => {
const Worker = Self.app.models.Worker; const models = Self.app.models;
const myUserId = ctx.req.accessToken.userId; const args = ctx.args;
const myWorker = await Worker.findOne({where: {userFk: myUserId}}); const currentUserId = ctx.req.accessToken.userId;
const isSubordinate = await Worker.isSubordinate(ctx, data.workerFk);
const isTeamBoss = await Self.app.models.Account.hasRole(myUserId, 'teamBoss');
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`); throw new UserError(`You don't have enough privileges`);
const subordinate = await Worker.findById(data.workerFk); const timed = new Date(args.timed);
const timed = new Date(data.timed);
let [result] = await Self.rawSql('SELECT vn.workerTimeControl_add(?, ?, ?, ?) AS id', [ return models.WorkerTimeControl.create({
subordinate.userFk, null, timed, true]); userFk: workerId,
direction: args.direction,
return result; 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 currentUserId = ctx.req.accessToken.userId;
const workerModel = Self.app.models.Worker; const models = Self.app.models;
const targetTimeEntry = await Self.findById(id); let myOptions = {};
const isSubordinate = await workerModel.isSubordinate(ctx, targetTimeEntry.userFk);
const isTeamBoss = await Self.app.models.Account.hasRole(currentUserId, 'teamBoss'); 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 isHimself = currentUserId == targetTimeEntry.userFk;
const notAllowed = isSubordinate === false || (isSubordinate && isHimself && !isTeamBoss); if (isSubordinate === false || (isSubordinate && isHimself && !isTeamBoss))
if (notAllowed)
throw new UserError(`You don't have enough privileges`); throw new UserError(`You don't have enough privileges`);
return Self.rawSql('CALL vn.workerTimeControl_remove(?, ?)', [ 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 app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
const models = app.models;
describe('workerTimeControl add/delete timeEntry()', () => { describe('workerTimeControl add/delete timeEntry()', () => {
const HHRRId = 37; const HHRRId = 37;
@ -12,19 +13,6 @@ describe('workerTimeControl add/delete timeEntry()', () => {
}; };
let ctx = {req: activeCtx}; 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(() => { beforeAll(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx 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() => { it('should fail to add a time entry if the target user is not a subordinate', async() => {
activeCtx.accessToken.userId = employeeId; activeCtx.accessToken.userId = employeeId;
const workerId = 2;
let error; let error;
let data = {
workerFk: 2,
timed: new Date()
};
try { try {
await app.models.WorkerTimeControl.addTimeEntry(ctx, data); ctx.args = {timed: new Date(), direction: 'in'};
await models.WorkerTimeControl.addTimeEntry(ctx, workerId);
} catch (e) { } catch (e) {
error = 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() => { 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; activeCtx.accessToken.userId = employeeId;
const workerId = employeeId;
let error; let error;
let data = {
workerFk: 1,
timed: new Date()
};
try { try {
await app.models.WorkerTimeControl.addTimeEntry(ctx, data); ctx.args = {timed: new Date(), direction: 'in'};
await models.WorkerTimeControl.addTimeEntry(ctx, workerId);
} catch (e) { } catch (e) {
error = 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() => { it('should add if the current user is team boss and the target user is a himself', async() => {
activeCtx.accessToken.userId = teamBossId; activeCtx.accessToken.userId = teamBossId;
let todayAtSix = new Date(); const workerId = teamBossId;
const tx = await models.WorkerTimeControl.beginTransaction({});
try {
const options = {transaction: tx};
const todayAtSix = new Date();
todayAtSix.setHours(18, 30, 0, 0); todayAtSix.setHours(18, 30, 0, 0);
let data = { ctx.args = {timed: todayAtSix, direction: 'in'};
workerFk: teamBossId, const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
timed: todayAtSix
};
timeEntry = await app.models.WorkerTimeControl.addTimeEntry(ctx, data); expect(createdTimeEntry.id).toBeDefined();
createdTimeEntry = await app.models.WorkerTimeControl.findById(timeEntry.id); await tx.rollback();
} catch (e) {
expect(createdTimeEntry).toBeDefined(); await tx.rollback();
throw e;
}
}); });
it('should try but fail to delete his own time entry', async() => { it('should try but fail to delete his own time entry', async() => {
activeCtx.accessToken.userId = salesBossId; activeCtx.accessToken.userId = salesBossId;
const workerId = salesBossId;
let error; let error;
let todayAtSeven = new Date(); const tx = await models.WorkerTimeControl.beginTransaction({});
try {
const options = {transaction: tx};
const todayAtSeven = new Date();
todayAtSeven.setHours(19, 30, 0, 0); todayAtSeven.setHours(19, 30, 0, 0);
let data = { ctx.args = {timed: todayAtSeven, direction: 'in'};
workerFk: salesPersonId, const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
timed: todayAtSeven
};
timeEntry = await app.models.WorkerTimeControl.addTimeEntry(ctx, data);
createdTimeEntry = await app.models.WorkerTimeControl.findById(timeEntry.id);
try {
activeCtx.accessToken.userId = salesPersonId; 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) { } catch (e) {
error = e; error = e;
await tx.rollback();
} }
expect(error).toBeDefined(); 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() => { it('should delete the created time entry for the team boss as himself', async() => {
activeCtx.accessToken.userId = teamBossId; activeCtx.accessToken.userId = teamBossId;
const workerId = teamBossId;
let todayAtFive = new Date(); const tx = await models.WorkerTimeControl.beginTransaction({});
try {
const options = {transaction: tx};
const todayAtFive = new Date();
todayAtFive.setHours(17, 30, 0, 0); todayAtFive.setHours(17, 30, 0, 0);
let data = { ctx.args = {timed: todayAtFive, direction: 'in'};
workerFk: teamBossId, const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
timed: todayAtFive
};
timeEntry = await app.models.WorkerTimeControl.addTimeEntry(ctx, data); expect(createdTimeEntry.id).toBeDefined();
createdTimeEntry = await app.models.WorkerTimeControl.findById(timeEntry.id); await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options);
expect(createdTimeEntry).toBeDefined(); const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options);
await app.models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id); expect(deletedTimeEntry).toBeNull();
await tx.rollback();
createdTimeEntry = await app.models.WorkerTimeControl.findById(timeEntry.id); } catch (e) {
await tx.rollback();
expect(createdTimeEntry).toBeNull(); throw e;
}
}); });
it('should delete the created time entry for the team boss as HHRR', async() => { it('should delete the created time entry for the team boss as HHRR', async() => {
activeCtx.accessToken.userId = HHRRId; activeCtx.accessToken.userId = HHRRId;
const workerId = teamBossId;
let todayAtFive = new Date(); const tx = await models.WorkerTimeControl.beginTransaction({});
try {
const options = {transaction: tx};
const todayAtFive = new Date();
todayAtFive.setHours(17, 30, 0, 0); todayAtFive.setHours(17, 30, 0, 0);
let data = { ctx.args = {timed: todayAtFive, direction: 'in'};
workerFk: teamBossId, const createdTimeEntry = await models.WorkerTimeControl.addTimeEntry(ctx, workerId, options);
timed: todayAtFive
};
timeEntry = await app.models.WorkerTimeControl.addTimeEntry(ctx, data); expect(createdTimeEntry.id).toBeDefined();
createdTimeEntry = await app.models.WorkerTimeControl.findById(timeEntry.id); await models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id, options);
expect(createdTimeEntry).toBeDefined(); const deletedTimeEntry = await models.WorkerTimeControl.findById(createdTimeEntry.id, null, options);
await app.models.WorkerTimeControl.deleteTimeEntry(ctx, createdTimeEntry.id); expect(deletedTimeEntry).toBeNull();
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
createdTimeEntry = await app.models.WorkerTimeControl.findById(timeEntry.id); it('should edit the created time entry for the team boss as HHRR', async() => {
activeCtx.accessToken.userId = HHRRId;
const workerId = teamBossId;
expect(createdTimeEntry).toBeNull(); 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/filter')(Self);
require('../methods/worker-time-control/addTimeEntry')(Self); require('../methods/worker-time-control/addTimeEntry')(Self);
require('../methods/worker-time-control/deleteTimeEntry')(Self); require('../methods/worker-time-control/deleteTimeEntry')(Self);
require('../methods/worker-time-control/updateTimeEntry')(Self);
Self.rewriteDbError(function(err) { Self.rewriteDbError(function(err) {
if (err.code === 'ER_DUP_ENTRY') if (err.code === 'ER_DUP_ENTRY')

View File

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

View File

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

View File

@ -43,9 +43,16 @@
ng-class="::{'invisible': hour.direction == 'middle'}"> ng-class="::{'invisible': hour.direction == 'middle'}">
</vn-icon> </vn-icon>
<vn-chip <vn-chip
ng-class="::{'colored': hour.manual}" ng-class="::{'colored': hour.manual, 'clickable': true}"
removable="::hour.manual" 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'}} {{::hour.timed | date: 'HH:mm'}}
</vn-chip> </vn-chip>
</section> </section>
@ -97,10 +104,20 @@
<tpl-body> <tpl-body>
<vn-input-time <vn-input-time
vn-one vn-one
ng-model="$ctrl.newTime" vn-focus
ng-model="$ctrl.newTimeEntry.timed"
label="Hour" label="Hour"
vn-focus> required="true">
</vn-input-time> </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-body>
<tpl-buttons> <tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/> <input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
@ -113,3 +130,21 @@
message="This time entry will be deleted" message="This time entry will be deleted"
question="Are you sure you want to delete this entry?"> 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, $); super($element, $);
this.weekDays = []; this.weekDays = [];
this.weekdayNames = vnWeekDays.locales; 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() { $postLink() {
@ -241,21 +246,34 @@ class Controller extends Section {
const timed = new Date(weekday.dated.getTime()); const timed = new Date(weekday.dated.getTime());
timed.setHours(0, 0, 0, 0); timed.setHours(0, 0, 0, 0);
this.newTime = timed; this.newTimeEntry = {
workerFk: this.$params.id,
timed: timed
};
this.selectedWeekday = weekday; this.selectedWeekday = weekday;
this.$.addTimeDialog.show(); this.$.addTimeDialog.show();
} }
addTime() { addTime() {
let data = { try {
workerFk: this.$params.id, const entry = this.newTimeEntry;
timed: this.newTime if (!entry.direction)
}; throw new Error(`The entry type can't be empty`);
this.$http.post(`WorkerTimeControls/addTimeEntry`, data)
const query = `WorkerTimeControls/${this.worker.id}/addTimeEntry`;
this.$http.post(query, entry)
.then(() => this.fetchHours()); .then(() => this.fetchHours());
} catch (e) {
this.vnApp.showError(this.$t(e.message));
return false;
} }
showDeleteDialog(hour) { return true;
}
showDeleteDialog($event, hour) {
$event.preventDefault();
this.timeEntryToDelete = hour; this.timeEntryToDelete = hour;
this.$.deleteEntryDialog.show(); this.$.deleteEntryDialog.show();
} }
@ -268,6 +286,29 @@ class Controller extends Section {
this.vnApp.showSuccess(this.$t('Entry removed')); 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']; Controller.$inject = ['$element', '$scope', 'vnWeekDays'];

View File

@ -113,5 +113,21 @@ describe('Component vnWorkerTimeControl', () => {
expect(result).toEqual('01:00'); 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 In: Entrada
Out: Salida Out: Salida
Intermediate: Intermedio
Hour: Hora Hour: Hora
Hours: Horas Hours: Horas
Add time: Añadir hora Add time: Añadir hora
@ -9,3 +10,4 @@ This time entry will be deleted: Se eliminará la hora fichada
Are you sure you want to delete this entry?: ¿Seguro que quieres eliminarla? Are you sure you want to delete this entry?: ¿Seguro que quieres eliminarla?
Finish at: Termina a las 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

@ -25,3 +25,7 @@ vn-worker-time-control {
max-width: none max-width: none
} }
} }
.edit-time-entry {
width: 200px
}