@janiscommerce/app-tracking-shift 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,21 +5,29 @@ import {setObject, getObject} from './utils/storage';
5
5
 
6
6
  class OfflineData {
7
7
  get hasData() {
8
- const offlineData = getObject(OFFLINE_DATA, {});
9
- return Object.keys(offlineData).length > 0;
8
+ const offlineData = getObject(OFFLINE_DATA, []);
9
+ return offlineData.length > 0;
10
10
  }
11
11
 
12
12
  /**
13
13
  * Saves data to the offline data storage.
14
14
  *
15
- * @param {string} storageId - The ID of the storage to save the data to.
15
+ * @param {string} id - The reference id of the data.
16
16
  * @param {Object} data - The data to save.
17
17
  */
18
18
 
19
- save(storageId, data) {
19
+ save(id, data) {
20
20
  try {
21
- const offlineData = getObject(OFFLINE_DATA, {});
22
- offlineData[storageId] = {...offlineData[storageId], ...data};
21
+ const offlineData = getObject(OFFLINE_DATA, []);
22
+ const foundIdx = offlineData.findIndex((item) => item.storageId === id);
23
+
24
+ if (foundIdx === -1) {
25
+ offlineData.push({storageId: id, ...data});
26
+ } else {
27
+ const storedData = offlineData[foundIdx];
28
+ offlineData[foundIdx] = {...storedData, ...data};
29
+ }
30
+
23
31
  setObject(OFFLINE_DATA, offlineData);
24
32
  } catch (error) {
25
33
  throw new Error(error);
@@ -29,21 +37,37 @@ class OfflineData {
29
37
  /**
30
38
  * Gets data from the offline data storage.
31
39
  *
32
- * @param {string | Array<string> | null} storageId - The ID of the storage to get the data from.
40
+ * @param {string | Array<string> | null} id - The ID of the storage to get the data from.
33
41
  * @returns {Array<Object>} The data from the storage.
34
42
  */
35
43
 
36
- get(storageId = null) {
44
+ get(id = null) {
37
45
  try {
38
- const offlineData = getObject(OFFLINE_DATA, {});
46
+ const offlineData = getObject(OFFLINE_DATA, []);
39
47
 
40
- if (!isArray(storageId)) {
41
- storageId = [storageId].filter(Boolean);
48
+ if (!isArray(id)) {
49
+ id = [id].filter(Boolean);
42
50
  }
43
51
 
44
- if (isEmptyArray(storageId)) return Object.values(offlineData);
52
+ if (isEmptyArray(id)) return offlineData;
45
53
 
46
- return storageId.map((id) => offlineData[id]);
54
+ return offlineData.filter((item) => id.includes(item.storageId));
55
+ } catch (error) {
56
+ throw new Error(error);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Gets the last record from the offline data storage.
62
+ *
63
+ * @returns {Object} The last record from the storage.
64
+ */
65
+
66
+ getLastRecord() {
67
+ try {
68
+ const offlineData = this.get();
69
+ const [lastRecord] = offlineData.slice(-1);
70
+ return lastRecord;
47
71
  } catch (error) {
48
72
  throw new Error(error);
49
73
  }
@@ -52,24 +76,22 @@ class OfflineData {
52
76
  /**
53
77
  * Deletes data from the offline data storage.
54
78
  *
55
- * @param {string | Array<string>} storageId - The ID of the storage to delete the data from.
79
+ * @param {string | Array<string>} id - The reference id of the data.
56
80
  */
57
81
 
58
- delete(storageId) {
82
+ delete(id) {
59
83
  try {
60
- const offlineData = getObject(OFFLINE_DATA, {});
84
+ const offlineData = getObject(OFFLINE_DATA, []);
61
85
 
62
- if (!isArray(storageId)) {
63
- storageId = [storageId].filter(Boolean);
86
+ if (!isArray(id)) {
87
+ id = [id].filter(Boolean);
64
88
  }
65
89
 
66
- if (isEmptyArray(storageId)) return;
90
+ if (isEmptyArray(id)) return;
67
91
 
68
- storageId.forEach((id) => {
69
- delete offlineData[id];
70
- });
92
+ const filteredData = offlineData.filter((item) => !id.includes(item.storageId));
71
93
 
72
- setObject(OFFLINE_DATA, offlineData);
94
+ setObject(OFFLINE_DATA, filteredData);
73
95
  } catch (error) {
74
96
  throw new Error(error);
75
97
  }
package/lib/Shift.js CHANGED
@@ -1,10 +1,23 @@
1
- import TimeTracker from './db/TimeTrackerService';
2
1
  import StaffService from './StaffApiServices';
3
2
  import Storage from './db/StorageService';
4
3
  import Crashlytics from './utils/crashlytics';
5
4
  import ShiftWorklogs from './ShiftWorklogs';
6
- import {generateRandomId, isEmptyArray, isEmptyObject, isObject} from './utils/helpers';
7
- import {getObject, getShiftData, getStaffAuthorizationData, setObject} from './utils/storage';
5
+ import errorParser from './utils/errorParser';
6
+ import {
7
+ generateRandomId,
8
+ isEmptyArray,
9
+ isEmptyObject,
10
+ isNumber,
11
+ isObject,
12
+ isValidObject,
13
+ } from './utils/helpers';
14
+ import {
15
+ deleteStoredWorkLog,
16
+ getObject,
17
+ getShiftData,
18
+ getStaffAuthorizationData,
19
+ setObject,
20
+ } from './utils/storage';
8
21
  import {
9
22
  SHIFT_ID,
10
23
  SHIFT_STATUS,
@@ -12,9 +25,8 @@ import {
12
25
  CURRENT_WORKLOG_DATA,
13
26
  CURRENT_WORKLOG_ID,
14
27
  EXCLUDED_WORKLOG_TYPES,
15
- ONE_HOUR_EXTENSION,
28
+ DEFAULT_REOPENING_EXTENSION_TIME,
16
29
  } from './constant';
17
- import TrackerRecords from './TrackerRecords';
18
30
  import Formatter from './Formatter';
19
31
  import OfflineData from './OfflineData';
20
32
  /**
@@ -53,8 +65,9 @@ class Shift {
53
65
 
54
66
  return enabledShiftAndWorkLog;
55
67
  } catch (error) {
56
- Crashlytics.recordError(error, 'Error checking staff MS authorization');
57
- return Promise.reject(error);
68
+ const parsedError = errorParser(error);
69
+ Crashlytics.recordError(parsedError, 'Error checking staff MS authorization');
70
+ return Promise.reject(parsedError);
58
71
  }
59
72
  }
60
73
 
@@ -65,31 +78,29 @@ class Shift {
65
78
  * @returns {Promise<string>} shiftId => ID related to the shift that has just been opened for the user
66
79
  */
67
80
 
68
- async open(params = {}) {
81
+ async open() {
69
82
  try {
70
- Crashlytics.log('user open shift');
83
+ Crashlytics.log('openShift:');
71
84
 
72
85
  this._requireStaffAuthorization();
73
86
 
74
- const {date} = params;
75
87
  const {result: shift} = await StaffService.openShift();
76
88
  const {id: shiftId = ''} = shift || {};
77
89
 
78
90
  const openShift = await this.getUserOpenShift({id: shiftId});
79
91
 
80
- await this._startTracking({
81
- id: shiftId,
82
- date: date || openShift.startDate,
83
- });
84
-
85
92
  Storage.set(SHIFT_ID, shiftId);
86
93
  Storage.set(SHIFT_STATUS, 'opened');
87
- Storage.set(SHIFT_DATA, JSON.stringify(openShift));
94
+ setObject(SHIFT_DATA, openShift);
88
95
 
89
96
  return shiftId;
90
97
  } catch (error) {
91
- Crashlytics.recordError(error, 'Error opening shift in staff service');
92
- return Promise.reject(error);
98
+ const parsedError = errorParser(error);
99
+ Crashlytics.recordError(
100
+ parsedError,
101
+ 'An error occurred while trying to open a shift for the user'
102
+ );
103
+ return Promise.reject(parsedError);
93
104
  }
94
105
  }
95
106
 
@@ -102,7 +113,7 @@ class Shift {
102
113
 
103
114
  async finish(params = {}) {
104
115
  try {
105
- Crashlytics.log('user close shift');
116
+ Crashlytics.log('closeShift:');
106
117
 
107
118
  this._requireStaffAuthorization();
108
119
 
@@ -122,20 +133,19 @@ class Shift {
122
133
  const endDate = date || new Date().toISOString();
123
134
  const shiftData = getShiftData();
124
135
 
125
- await this._finishTracking({id: shiftId, date});
126
-
127
136
  const updatedShiftData = {
128
137
  ...shiftData,
129
138
  endDate,
130
139
  };
131
140
 
132
141
  Storage.set(SHIFT_STATUS, 'closed');
133
- Storage.set(SHIFT_DATA, JSON.stringify(updatedShiftData));
142
+ setObject(SHIFT_DATA, updatedShiftData);
134
143
 
135
144
  return shiftId;
136
145
  } catch (error) {
137
- Crashlytics.recordError(error, 'Error closing shift in staff service');
138
- return Promise.reject(error);
146
+ const parsedError = errorParser(error);
147
+ Crashlytics.recordError(parsedError, 'An error occurred while trying to close user shift');
148
+ return Promise.reject(parsedError);
139
149
  }
140
150
  }
141
151
 
@@ -152,15 +162,22 @@ class Shift {
152
162
 
153
163
  async openWorkLog(workLog = {}) {
154
164
  try {
155
- Crashlytics.log('user open shift worklog', workLog);
165
+ Crashlytics.log('openWorkLog:', workLog);
156
166
 
157
167
  this._requireStaffAuthorization();
158
168
 
159
169
  if (!isObject(workLog) || isEmptyObject(workLog)) return null;
170
+ const currentTime = new Date().toISOString();
171
+ const mustCloseLastWorkLog = this._isNeccesaryCloseLastWorkLog();
172
+
173
+ if (mustCloseLastWorkLog) {
174
+ const lastWorkLog = getObject(CURRENT_WORKLOG_DATA);
175
+
176
+ await this.finishWorkLog(lastWorkLog);
177
+ }
160
178
 
161
179
  const {referenceId, name, type, suggestedTime = 0} = workLog;
162
180
  const shiftId = Storage.getString(SHIFT_ID);
163
- const startTime = new Date().toISOString();
164
181
  const randomId = generateRandomId();
165
182
 
166
183
  const workLogId = Formatter.formatWorkLogId(referenceId, randomId);
@@ -168,34 +185,24 @@ class Shift {
168
185
  // TODO: uncomment this when resolve how to handle offline workLogs
169
186
  // await ShiftWorklogs.open({
170
187
  // referenceId,
171
- // startDate: startTime,
188
+ // startDate: currentTime,
172
189
  // });
173
190
 
174
- const dataForTimeTracker = {
175
- type,
176
- name,
177
- shiftId,
178
- referenceId,
179
- };
180
-
181
191
  OfflineData.save(workLogId, {
182
192
  referenceId,
183
- startDate: startTime,
193
+ startDate: currentTime,
184
194
  });
185
195
 
186
- await this._startTracking({
187
- id: workLogId,
188
- date: startTime,
189
- payload: dataForTimeTracker,
190
- });
191
-
192
- const suggestedFinishDate = new Date(startTime).getTime() + suggestedTime * 60 * 1000;
196
+ const suggestedFinishDate = new Date(currentTime).getTime() + suggestedTime * 60 * 1000;
193
197
 
194
198
  const dataForStorage = {
195
- ...dataForTimeTracker,
199
+ type,
200
+ name,
201
+ shiftId,
202
+ referenceId,
196
203
  suggestedFinishDate: new Date(suggestedFinishDate).toISOString(),
197
204
  suggestedTime,
198
- startDate: startTime,
205
+ startDate: currentTime,
199
206
  };
200
207
 
201
208
  if (!EXCLUDED_WORKLOG_TYPES.includes(referenceId)) {
@@ -207,8 +214,9 @@ class Shift {
207
214
 
208
215
  return workLogId;
209
216
  } catch (error) {
210
- Crashlytics.recordError(error, 'Error opening shift worklog');
211
- return Promise.reject(error);
217
+ const parsedError = errorParser(error);
218
+ Crashlytics.recordError(parsedError, 'An error occurred while trying to open user workLog');
219
+ return Promise.reject(parsedError);
212
220
  }
213
221
  }
214
222
 
@@ -216,66 +224,62 @@ class Shift {
216
224
  * Finish a work log in the staff MS and record this event in the time tracking database.
217
225
  * @param {Object} workLog
218
226
  * @param {string} workLog.referenceId => Reference ID related to the work log
219
- * @param {string} workLog.name => Name related to the work log
220
- * @param {string} workLog.type => Type related to the work log
221
227
  * @throws {Error} error
222
228
  * @returns {Promise<string>} workLogId => ID related to the work log that has just been closed for the user
223
229
  */
224
230
 
225
231
  async finishWorkLog(workLog = {}) {
226
232
  try {
227
- Crashlytics.log('user close shift worklog', workLog);
233
+ Crashlytics.log('finishWorkLog:', workLog);
228
234
 
229
235
  this._requireStaffAuthorization();
230
236
 
231
237
  if (!isObject(workLog) || isEmptyObject(workLog)) return null;
232
238
 
233
- const {referenceId, name, type} = workLog;
234
- const shiftId = Storage.getString(SHIFT_ID);
239
+ const currentWorkLog = getObject(CURRENT_WORKLOG_DATA);
240
+ const {referenceId} = workLog;
241
+
242
+ if (!isValidObject(currentWorkLog)) {
243
+ throw new Error('There is no active worklog to close');
244
+ }
245
+
246
+ if (currentWorkLog?.referenceId !== referenceId) {
247
+ throw new Error(
248
+ 'The worklog you are trying to close is different from the one that is currently open.'
249
+ );
250
+ }
251
+
235
252
  const shiftStatus = Storage.getString(SHIFT_STATUS);
236
253
  const endTime = new Date().toISOString();
237
254
  const workLogId = Storage.getString(CURRENT_WORKLOG_ID);
238
-
239
255
  // TODO: uncomment this when resolve how to handle offline workLogs
240
256
  // await ShiftWorklogs.finish({
241
257
  // referenceId,
242
258
  // endDate: endTime,
243
259
  // });
244
260
 
245
- const dataForTimeTracker = {
246
- type,
247
- name,
248
- shiftId,
249
- referenceId,
250
- };
251
-
252
261
  OfflineData.save(workLogId, {
253
262
  referenceId,
254
263
  endDate: endTime,
255
264
  });
256
265
 
257
- await this._finishTracking({
258
- id: workLogId,
259
- date: endTime,
260
- payload: dataForTimeTracker,
261
- });
262
-
263
266
  if (shiftStatus === 'paused') {
264
267
  Storage.set(SHIFT_STATUS, 'opened');
265
268
  }
266
269
 
267
- this._deleteCurrentWorkLogData();
270
+ deleteStoredWorkLog();
268
271
 
269
272
  return workLogId;
270
273
  } catch (error) {
271
- Crashlytics.recordError(error, 'Error closing shift worklog');
272
- return Promise.reject(error);
274
+ const parsedError = errorParser(error);
275
+ Crashlytics.recordError(parsedError, 'An error occurred while trying to close user workLog');
276
+ return Promise.reject(parsedError);
273
277
  }
274
278
  }
275
279
 
276
280
  async sendPendingWorkLogs() {
277
281
  try {
278
- Crashlytics.log('user send pending work logs');
282
+ Crashlytics.log('sendPendingWorkLogs:');
279
283
 
280
284
  this._requireStaffAuthorization();
281
285
 
@@ -310,7 +314,7 @@ class Shift {
310
314
 
311
315
  async getUserOpenShift(params = {}) {
312
316
  try {
313
- Crashlytics.log('user get open shift', params);
317
+ Crashlytics.log('getUserOpenShift:', params);
314
318
 
315
319
  this._requireStaffAuthorization();
316
320
 
@@ -327,8 +331,9 @@ class Shift {
327
331
 
328
332
  return openShift || {};
329
333
  } catch (error) {
330
- Crashlytics.recordError(error, 'Error getting open shift in staff service');
331
- return Promise.reject(error);
334
+ const parsedError = errorParser(error);
335
+ Crashlytics.recordError(parsedError, 'Error getting open shift in staff service');
336
+ return Promise.reject(parsedError);
332
337
  }
333
338
  }
334
339
 
@@ -341,7 +346,7 @@ class Shift {
341
346
 
342
347
  async getWorkLogs(shiftId) {
343
348
  try {
344
- Crashlytics.log('user get work logs');
349
+ Crashlytics.log('getWorkLogs:');
345
350
 
346
351
  this._requireStaffAuthorization();
347
352
 
@@ -353,8 +358,12 @@ class Shift {
353
358
 
354
359
  return Formatter.formatWorkLogsFromJanis(workLogs);
355
360
  } catch (error) {
356
- Crashlytics.recordError(error, 'Error getting work logs in staff service');
357
- return Promise.reject(error);
361
+ const parsedError = errorParser(error);
362
+ Crashlytics.recordError(
363
+ parsedError,
364
+ 'An error occurred while trying to get user workLogs from staff service'
365
+ );
366
+ return Promise.reject(parsedError);
358
367
  }
359
368
  }
360
369
 
@@ -366,7 +375,7 @@ class Shift {
366
375
 
367
376
  async reOpen() {
368
377
  try {
369
- Crashlytics.log('user re open shift');
378
+ Crashlytics.log('reOpenShift:');
370
379
 
371
380
  this._requireStaffAuthorization();
372
381
 
@@ -380,8 +389,9 @@ class Shift {
380
389
 
381
390
  return null;
382
391
  } catch (error) {
383
- Crashlytics.recordError(error, 'Error re opening shift');
384
- return Promise.reject(error);
392
+ const parsedError = errorParser(error);
393
+ Crashlytics.recordError(parsedError, 'An error occurred while trying to re open user shift');
394
+ return Promise.reject(parsedError);
385
395
  }
386
396
  }
387
397
 
@@ -401,83 +411,6 @@ class Shift {
401
411
  return new Date(dateMaxToClose).getTime() < currentDate.getTime();
402
412
  }
403
413
 
404
- /**
405
- * Gets a shift report based on the shift ID and its registered events.
406
- *
407
- * The report includes information about performed activities, start and end times,
408
- * total elapsed time, work time and pause time.
409
- *
410
- * @async
411
- * @function getReport
412
- * @throws {Error} If the `shiftId` is not found in storage.
413
- * @returns {Promise<{
414
- * activities: Array<{
415
- * id: string,
416
- * name: string,
417
- * type: string,
418
- * startDate: string,
419
- * endDate: string,
420
- * duration: number
421
- * }>,
422
- * startDate: string,
423
- * endDate: string,
424
- * elapsedTime: number,
425
- * workTime: number,
426
- * pauseTime: number,
427
- * isComplete: boolean,
428
- * error: string | null
429
- * }>} Object with shift details.
430
- *
431
- * @property {Array} activities - List of activities registered during the shift.
432
- * @property {string} activities[].id - Unique identifier of the activity.
433
- * @property {string} activities[].name - Name or description of the activity.
434
- * @property {string} activities[].startDate - Start date and time of the activity.
435
- * @property {string} activities[].endDate - End date and time of the activity.
436
- * @property {number} activities[].duration - Duration of the activity in milliseconds.
437
- * @property {string} startDate - Start date and time of the shift.
438
- * @property {string} endDate - End date and time of the shift (can be empty if not finished).
439
- * @property {number} elapsedTime - Total elapsed time between shift start and end.
440
- * @property {number} workTime - Effective work time (total time minus pauses).
441
- * @property {number} pauseTime - Total time of registered pauses.
442
- * @property {boolean} isComplete - Indicates if the report was obtained completely without errors.
443
- * @property {string|null} error - Error message if the report is not complete, or null if there were no errors.
444
- */
445
-
446
- async getReport() {
447
- try {
448
- Crashlytics.log('user get shift report');
449
- const shiftId = Storage.getString(SHIFT_ID);
450
-
451
- if (!shiftId) throw new Error('Shift ID is required');
452
-
453
- const startDate = await TrackerRecords.getStartDateById(shiftId);
454
- const endDate = await TrackerRecords.getEndDateById(shiftId);
455
- const workLogs = await ShiftWorklogs.getShiftTrackedWorkLogs(shiftId);
456
-
457
- const activities = Formatter.formatShiftActivities(workLogs);
458
- const elapsedTime = TimeTracker.getElapsedTime({
459
- startTime: startDate,
460
- ...(!!endDate && {endTime: endDate}),
461
- format: false,
462
- });
463
-
464
- const pauseTime = activities.reduce((acc, activity) => acc + (activity?.duration || 0), 0);
465
- const workTime = elapsedTime - pauseTime;
466
-
467
- return {
468
- activities,
469
- startDate,
470
- endDate,
471
- elapsedTime,
472
- workTime,
473
- pauseTime,
474
- };
475
- } catch (reason) {
476
- Crashlytics.recordError(reason, 'Error getting shift report');
477
- return Promise.reject(reason);
478
- }
479
- }
480
-
481
414
  /**
482
415
  * Fetch the work log types from the staff MS and prepare them for register an activity.
483
416
  * @throws {Error} error
@@ -486,7 +419,7 @@ class Shift {
486
419
 
487
420
  async fetchWorklogTypes() {
488
421
  try {
489
- Crashlytics.log('user fetch worklog types');
422
+ Crashlytics.log('fetchWorklogTypes:');
490
423
 
491
424
  this._requireStaffAuthorization();
492
425
 
@@ -494,8 +427,9 @@ class Shift {
494
427
 
495
428
  return Formatter.formatWorkLogTypes(workLogTypes);
496
429
  } catch (error) {
497
- Crashlytics.recordError(error, 'Error fetching worklog types in staff service');
498
- return Promise.reject(error);
430
+ const parsedError = errorParser(error);
431
+ Crashlytics.recordError(parsedError, 'Error fetching worklog types from staff service');
432
+ return Promise.reject(parsedError);
499
433
  }
500
434
  }
501
435
 
@@ -507,14 +441,17 @@ class Shift {
507
441
 
508
442
  async deleteShiftRegisters() {
509
443
  try {
510
- Crashlytics.log('user delete shift registers');
444
+ Crashlytics.log('deleteShiftRegisters:');
511
445
  this._deleteShiftData();
512
- this._deleteCurrentWorkLogData();
513
- OfflineData.deleteAll();
514
- return await TimeTracker.deleteAllEvents();
446
+ deleteStoredWorkLog();
447
+ return OfflineData.deleteAll();
515
448
  } catch (error) {
516
- Crashlytics.recordError(error, 'Error deleting registers from shift tracking database');
517
- return Promise.reject(error);
449
+ const parsedError = errorParser(error);
450
+ Crashlytics.recordError(
451
+ parsedError,
452
+ 'An error occurred while trying to delete shift storage data'
453
+ );
454
+ return Promise.reject(parsedError);
518
455
  }
519
456
  }
520
457
 
@@ -524,49 +461,6 @@ class Shift {
524
461
  Storage.delete(SHIFT_DATA);
525
462
  }
526
463
 
527
- _deleteCurrentWorkLogData() {
528
- Storage.delete(CURRENT_WORKLOG_ID);
529
- Storage.delete(CURRENT_WORKLOG_DATA);
530
- }
531
-
532
- /**
533
- * @private
534
- * Start id tracking in the time tracking database.
535
- * @param {Object} params
536
- * @param {string} params.id => ID related to the shift
537
- * @param {string} params.date => Date related to the shift
538
- * @param {Object} params.payload => Payload related to the shift
539
- */
540
-
541
- async _startTracking(params) {
542
- const {id, date, payload} = params;
543
- await TimeTracker.addEvent({
544
- id,
545
- time: date || new Date().toISOString(),
546
- type: 'start',
547
- payload,
548
- }).catch(() => null);
549
- }
550
-
551
- /**
552
- * @private
553
- * Finish id tracking in the time tracking database.
554
- * @param {Object} params
555
- * @param {string} params.id => ID related to the shift
556
- * @param {string} params.date => Date related to the shift
557
- * @param {Object} params.payload => Payload related to the shift
558
- */
559
-
560
- async _finishTracking(params) {
561
- const {id, date, payload} = params;
562
- await TimeTracker.addEvent({
563
- id,
564
- time: date || new Date().toISOString(),
565
- type: 'finish',
566
- payload,
567
- }).catch(() => null);
568
- }
569
-
570
464
  /**
571
465
  * @private
572
466
  * Extend the shift closing date.
@@ -576,8 +470,14 @@ class Shift {
576
470
 
577
471
  _extendShiftClosingDate() {
578
472
  const shiftData = getObject(SHIFT_DATA);
579
- const {dateToClose} = shiftData;
580
- const updatedClosingDate = new Date(dateToClose).getTime() + ONE_HOUR_EXTENSION;
473
+ const {dateToClose, reopeningExtensionTime} = shiftData; // reopeningExtensionTime is in minutes
474
+ let extensionTime = DEFAULT_REOPENING_EXTENSION_TIME;
475
+
476
+ if (reopeningExtensionTime && isNumber(reopeningExtensionTime)) {
477
+ extensionTime = reopeningExtensionTime * 60 * 1000; // Convert minutes to milliseconds
478
+ }
479
+
480
+ const updatedClosingDate = new Date(dateToClose).getTime() + extensionTime; // Add the extension time to the closing date
581
481
 
582
482
  shiftData.dateToClose = new Date(updatedClosingDate).toISOString();
583
483
 
@@ -595,5 +495,23 @@ class Shift {
595
495
  if (this.hasStaffAuthorize) return;
596
496
  throw new Error('Staff MS authorization is required');
597
497
  }
498
+
499
+ /**
500
+ * @private
501
+ * Validate if is neccesary close the last work log
502
+ * @param {Object} newWorkLog
503
+ * @param {string} newWorkLog.referenceId => Reference ID related to the new work log
504
+ * @returns {boolean} true if is neccesary close the last work log, false otherwise
505
+ */
506
+
507
+ _isNeccesaryCloseLastWorkLog() {
508
+ const lastWorkLog = OfflineData.getLastRecord();
509
+ if (!isValidObject(lastWorkLog)) return false;
510
+
511
+ const isLastWorkLogClosed = !!lastWorkLog?.endDate;
512
+
513
+ return !isLastWorkLogClosed;
514
+ }
598
515
  }
516
+
599
517
  export default new Shift();
@@ -1,5 +1,4 @@
1
1
  import StaffApiServices from './StaffApiServices';
2
- import TrackerRecords from './TrackerRecords';
3
2
  import {isArray, isEmptyArray} from './utils/helpers';
4
3
 
5
4
  class ShiftWorklogs {
@@ -63,20 +62,6 @@ class ShiftWorklogs {
63
62
  }
64
63
  }
65
64
 
66
- async getShiftTrackedWorkLogs(shiftId) {
67
- try {
68
- if (!shiftId) throw new Error(`Shift ID is required, but got ${shiftId}`);
69
-
70
- const workLogEvents = await TrackerRecords.getClientShiftActivities(shiftId);
71
- const shiftWorkLogs = workLogEvents.filter((e) => e?.payload?.shiftId === shiftId);
72
- shiftWorkLogs.sort((a, b) => new Date(a?.time) - new Date(b?.time));
73
-
74
- return shiftWorkLogs;
75
- } catch (error) {
76
- return Promise.reject(error);
77
- }
78
- }
79
-
80
65
  async postPendingBatch(pendingWorkLogs = []) {
81
66
  try {
82
67
  if (!isArray(pendingWorkLogs) || isEmptyArray(pendingWorkLogs)) return null;
@@ -30,4 +30,4 @@ export const INTERNAL_WORKLOGS = {
30
30
  };
31
31
 
32
32
  // SHIFT CLOSE EXTENSION
33
- export const ONE_HOUR_EXTENSION = 60 * 60 * 1000; // 1 hour
33
+ export const DEFAULT_REOPENING_EXTENSION_TIME = 60 * 60 * 1000; // 1 hour
@@ -1,6 +1,7 @@
1
1
  import {useMMKVString} from 'react-native-mmkv';
2
2
  import {useMemo} from 'react';
3
3
  import Crashlytics from '../../utils/crashlytics';
4
+ import errorParser from '../../utils/errorParser';
4
5
 
5
6
  export const useMMKVObject = (key, defaultValue = null) => {
6
7
  const [raw] = useMMKVString(key);
@@ -10,7 +11,8 @@ export const useMMKVObject = (key, defaultValue = null) => {
10
11
  try {
11
12
  return JSON.parse(raw);
12
13
  } catch (e) {
13
- Crashlytics.recordError(e, `Invalid JSON in MMKV key: ${key}`);
14
+ const parsedError = errorParser(e);
15
+ Crashlytics.recordError(parsedError, `Invalid JSON in MMKV key: ${key}`);
14
16
  return defaultValue;
15
17
  }
16
18
  }, [raw]);
@@ -6,7 +6,6 @@ import {
6
6
  downloadWorkLogTypes,
7
7
  isAuthorizedToUseStaffMS,
8
8
  getShiftWorkLogsFromJanis,
9
- saveWorkLogTimesInDB,
10
9
  } from '../utils/provider';
11
10
  import {
12
11
  CURRENT_WORKLOG_DATA,
@@ -20,7 +19,6 @@ import {
20
19
  } from '../constant';
21
20
  import {useMMKVObject} from '../hooks/useMMKVObject';
22
21
  import {isValidObject, promiseWrapper} from '../utils/helpers';
23
- import Crashlytics from '../utils/crashlytics';
24
22
  import Storage from '../db/StorageService';
25
23
 
26
24
  const ShiftTrackingProvider = ({children, onError = null}) => {
@@ -129,7 +127,7 @@ const ShiftTrackingProvider = ({children, onError = null}) => {
129
127
  return;
130
128
  }
131
129
 
132
- const {openWorkLogs, closedWorkLogs} = workLogs;
130
+ const {openWorkLogs} = workLogs;
133
131
  const [currentWorkLog = {}] = openWorkLogs;
134
132
  const isExcludedWork = EXCLUDED_WORKLOG_TYPES.includes(currentWorkLog?.referenceId);
135
133
 
@@ -142,14 +140,6 @@ const ShiftTrackingProvider = ({children, onError = null}) => {
142
140
  Storage.set(SHIFT_STATUS, 'paused');
143
141
  }
144
142
 
145
- const promises = [currentWorkLog, ...closedWorkLogs].map((workLog) =>
146
- saveWorkLogTimesInDB(workLog).catch((workLogError) => {
147
- Crashlytics.recordError(workLogError, 'error trying to save work log times in db', workLog);
148
- })
149
- );
150
-
151
- await Promise.all(promises);
152
-
153
143
  setOpenShiftResult((prev) => ({
154
144
  ...prev,
155
145
  getWorkLogs: false,
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @description Parse the error to a readable format
3
+ * @param {Error} error - The error to parse
4
+ * @returns {Error} The parsed error
5
+ */
6
+
7
+ const errorParser = (error = {}) => {
8
+ if (error instanceof Error) return error;
9
+
10
+ const {result = {}, message} = error;
11
+ const reportedError = result?.message || message;
12
+
13
+ return new Error(reportedError);
14
+ };
15
+
16
+ export default errorParser;
@@ -4,6 +4,7 @@ import {WORKLOG_TYPES_DATA, WORKLOG_TYPES_EXPIRATION_TIME} from '../../../consta
4
4
  import Storage from '../../../db/StorageService';
5
5
  import Crashlytics from '../../crashlytics';
6
6
  import {isFunction} from '../../helpers';
7
+ import errorParser from '../../errorParser';
7
8
 
8
9
  const downloadWorkLogTypes = async (onDownloadError) => {
9
10
  try {
@@ -21,9 +22,10 @@ const downloadWorkLogTypes = async (onDownloadError) => {
21
22
  Storage.set(WORKLOG_TYPES_DATA, JSON.stringify(data));
22
23
  return null;
23
24
  } catch (error) {
24
- Crashlytics.recordError(error, 'Error downloading worklog types');
25
- if (isFunction(onDownloadError)) onDownloadError(error);
26
- return Promise.reject(error?.result || error);
25
+ const parsedError = errorParser(error);
26
+ Crashlytics.recordError(parsedError, 'Error downloading worklog types');
27
+ if (isFunction(onDownloadError)) onDownloadError(parsedError);
28
+ return Promise.reject(parsedError);
27
29
  }
28
30
  };
29
31
 
@@ -2,4 +2,3 @@ export {default as downloadWorkLogTypes} from './downloadWorkLogTypes';
2
2
  export {default as openShift} from './openShift';
3
3
  export {default as isAuthorizedToUseStaffMS} from './isAuthorizedToUseStaffMS';
4
4
  export {default as getShiftWorkLogsFromJanis} from './getShiftWorkLogsFromJanis';
5
- export {default as saveWorkLogTimesInDB} from './saveWorkLogTimesInDB';
@@ -3,6 +3,7 @@ import {STAFF_AUTH, STAFF_MS_AUTHORIZATION_EXPIRATION_TIME} from '../../../const
3
3
  import Storage from '../../../db/StorageService';
4
4
  import Shift from '../../../Shift';
5
5
  import {getStaffAuthorizationData} from '../../storage';
6
+ import errorParser from '../../errorParser';
6
7
 
7
8
  const isAuthorizedToUseStaffMS = async () => {
8
9
  try {
@@ -24,7 +25,8 @@ const isAuthorizedToUseStaffMS = async () => {
24
25
 
25
26
  return isAuthorized;
26
27
  } catch (error) {
27
- Crashlytics.recordError(error, 'Error checking staff MS authorization');
28
+ const parsedError = errorParser(error);
29
+ Crashlytics.recordError(parsedError, 'Error checking staff MS authorization');
28
30
  const data = {
29
31
  hasStaffAuthorization: false,
30
32
  expirationTime: 0,
@@ -32,7 +34,7 @@ const isAuthorizedToUseStaffMS = async () => {
32
34
  };
33
35
  Storage.set(STAFF_AUTH, JSON.stringify(data));
34
36
 
35
- return Promise.reject(error);
37
+ return Promise.reject(parsedError);
36
38
  }
37
39
  };
38
40
 
@@ -1,16 +1,11 @@
1
1
  import Storage from '../../../db/StorageService';
2
2
  import Crashlytics from '../../crashlytics';
3
- import {
4
- SHIFT_ID,
5
- SHIFT_STATUS,
6
- SHIFT_DATA,
7
- CURRENT_WORKLOG_ID,
8
- CURRENT_WORKLOG_DATA,
9
- } from '../../../constant';
3
+ import {SHIFT_ID, SHIFT_STATUS, SHIFT_DATA} from '../../../constant';
10
4
  import getUserId from '../../userInfo/getUserId';
11
5
  import Shift from '../../../Shift';
12
6
  import {isFunction} from '../../helpers';
13
- import TimeTracker from '../../../db/TimeTrackerService';
7
+ import {deleteStoredWorkLog, setObject} from '../../storage';
8
+ import errorParser from '../../errorParser';
14
9
 
15
10
  const openShift = async (onOpenShiftError) => {
16
11
  try {
@@ -43,26 +38,20 @@ const openShift = async (onOpenShiftError) => {
43
38
  getWorkLogs: false,
44
39
  };
45
40
 
46
- Storage.delete(CURRENT_WORKLOG_ID);
47
- Storage.delete(CURRENT_WORKLOG_DATA);
41
+ deleteStoredWorkLog();
48
42
  Storage.set(SHIFT_ID, currentShift.id);
49
43
  Storage.set(SHIFT_STATUS, currentShift.status);
50
- Storage.set(SHIFT_DATA, JSON.stringify(currentShift));
51
-
52
- await TimeTracker.addEvent({
53
- id: currentShift.id,
54
- time: currentShift.startDate,
55
- type: 'start',
56
- }).catch(() => null);
44
+ setObject(SHIFT_DATA, currentShift);
57
45
 
58
46
  return {
59
47
  openShiftId: currentShift.id,
60
48
  getWorkLogs: true,
61
49
  };
62
50
  } catch (error) {
63
- Crashlytics.recordError(error, 'Error opening shift in staff service');
64
- if (isFunction(onOpenShiftError)) onOpenShiftError(error);
65
- return Promise.reject(error?.result || error);
51
+ const parsedError = errorParser(error);
52
+ Crashlytics.recordError(parsedError, 'Error opening shift in staff service');
53
+ if (isFunction(onOpenShiftError)) onOpenShiftError(parsedError);
54
+ return Promise.reject(parsedError);
66
55
  }
67
56
  };
68
57
 
@@ -0,0 +1,9 @@
1
+ import {CURRENT_WORKLOG_DATA, CURRENT_WORKLOG_ID} from '../../../constant';
2
+ import Storage from '../../../db/StorageService';
3
+
4
+ const deleteStoredWorkLog = () => {
5
+ Storage.delete(CURRENT_WORKLOG_ID);
6
+ Storage.delete(CURRENT_WORKLOG_DATA);
7
+ };
8
+
9
+ export default deleteStoredWorkLog;
@@ -1,6 +1,7 @@
1
1
  import Storage from '../../../db/StorageService';
2
2
  import {STAFF_AUTH} from '../../../constant';
3
3
  import Crashlytics from '../../crashlytics';
4
+ import errorParser from '../../errorParser';
4
5
 
5
6
  const getStaffAuthorizationData = () => {
6
7
  try {
@@ -13,7 +14,8 @@ const getStaffAuthorizationData = () => {
13
14
  isExpired: expirationTime <= Date.now() || isExpired,
14
15
  };
15
16
  } catch (error) {
16
- Crashlytics.recordError(error, 'Error getting staff authorization data');
17
+ const parsedError = errorParser(error);
18
+ Crashlytics.recordError(parsedError, 'Error getting staff authorization data');
17
19
  return {
18
20
  hasStaffAuthorization: false,
19
21
  isExpired: true,
@@ -1,6 +1,7 @@
1
1
  import Storage from '../../../db/StorageService';
2
2
  import Crashlytics from '../../crashlytics';
3
3
  import {WORKLOG_TYPES_DATA} from '../../../constant';
4
+ import errorParser from '../../errorParser';
4
5
 
5
6
  /**
6
7
  * @description Get the worklog types data from the storage
@@ -25,7 +26,8 @@ const getWorkLogTypesData = () => {
25
26
  isExpired: expirationTime <= Date.now() || !workLogTypes.length,
26
27
  };
27
28
  } catch (error) {
28
- Crashlytics.recordError(error, 'Error getting worklogs data');
29
+ const parsedError = errorParser(error);
30
+ Crashlytics.recordError(parsedError, 'Error getting worklogs data');
29
31
  return {
30
32
  workLogTypes: [],
31
33
  expirationTime: 0,
@@ -3,6 +3,7 @@ import Storage from '../../db/StorageService';
3
3
  export {default as getWorkLogTypesData} from './getWorkLogTypesData';
4
4
  export {default as getShiftData} from './getShiftData';
5
5
  export {default as getStaffAuthorizationData} from './getStaffAuthorizationData';
6
+ export {default as deleteStoredWorkLog} from './deleteStoredWorkLog';
6
7
 
7
8
  export const setObject = (key, value) => {
8
9
  const jsonValue = JSON.stringify(value);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@janiscommerce/app-tracking-shift",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "main": "lib/index.js",
5
5
  "module": "lib/index.js",
6
6
  "exports": {
@@ -47,7 +47,6 @@
47
47
  "peerDependencies": {
48
48
  "@janiscommerce/app-crashlytics": ">=2.1.0",
49
49
  "@janiscommerce/app-request": ">=2.0.0",
50
- "@janiscommerce/app-tracking-time": ">=2.2.1",
51
50
  "react": ">=17.0.2 <19",
52
51
  "react-dom": ">=17.0.2 <19",
53
52
  "react-native": ">=0.67.5 <0.75"
@@ -55,8 +54,7 @@
55
54
  "dependencies": {
56
55
  "@janiscommerce/app-device-info": "^1.1.0",
57
56
  "@janiscommerce/oauth-native": "^1.10.2",
58
- "react-native-mmkv": "2.12.2",
59
- "realm": "11.3.0"
57
+ "react-native-mmkv": "2.12.2"
60
58
  },
61
59
  "devDependencies": {
62
60
  "@babel/core": "^7.0.0",
@@ -65,7 +63,6 @@
65
63
  "@babel/preset-react": "^7.0.0",
66
64
  "@janiscommerce/app-crashlytics": "^2.1.0",
67
65
  "@janiscommerce/app-request": "^2.6.0",
68
- "@janiscommerce/app-tracking-time": "^2.2.1",
69
66
  "@testing-library/react": "^16.3.0",
70
67
  "@testing-library/react-native": "^12.0.1",
71
68
  "babel-jest": "^29.0.0",
@@ -1,103 +0,0 @@
1
- import TimeTracker from './db/TimeTrackerService';
2
- import {reverseArray} from './utils/helpers';
3
-
4
- class TrackerRecords {
5
- async getWorkLogsFromTimeTracker(filteredId) {
6
- try {
7
- if (!filteredId) throw new Error(`Excluding ID is required, but got ${filteredId}`);
8
-
9
- const events = await this._filterEventsExcludingId(filteredId);
10
-
11
- return events;
12
- } catch (error) {
13
- return Promise.reject(error);
14
- }
15
- }
16
-
17
- async getClientShiftActivities(id) {
18
- try {
19
- const query = 'NOT (id CONTAINS[c] "picking" OR id CONTAINS[c] "delivery") AND id != $0';
20
- const clientActivities = await TimeTracker.searchEventByQuery(query, id);
21
-
22
- return clientActivities;
23
- } catch (error) {
24
- return Promise.reject(error);
25
- }
26
- }
27
-
28
- async getStartDateById(id) {
29
- try {
30
- if (!id) throw new Error(`ID is required, but got ${id}`);
31
-
32
- const startShiftEvent = await this._getStartEventById(id);
33
-
34
- return startShiftEvent?.time;
35
- } catch (error) {
36
- return Promise.reject(error);
37
- }
38
- }
39
-
40
- async getEndDateById(id) {
41
- try {
42
- if (!id) throw new Error(`ID is required, but got ${id}`);
43
-
44
- const endShiftEvent = await this._getFinishEventById(id);
45
-
46
- return endShiftEvent?.time;
47
- } catch (error) {
48
- return Promise.reject(error);
49
- }
50
- }
51
-
52
- async _getStartEventById(id) {
53
- try {
54
- const startEvent = await this._filterEventByType(id, 'start');
55
-
56
- const [firstEvent = {}] = startEvent;
57
-
58
- return firstEvent;
59
- } catch (error) {
60
- return Promise.reject(error);
61
- }
62
- }
63
-
64
- async _getFinishEventById(id) {
65
- try {
66
- const finishEvent = await this._filterEventByType(id, 'finish');
67
-
68
- const [lastEvent = {}] = reverseArray(finishEvent);
69
-
70
- return lastEvent;
71
- } catch (error) {
72
- return Promise.reject(error);
73
- }
74
- }
75
-
76
- async _filterEventByType(id, type) {
77
- try {
78
- if (!id) throw new Error('id is required');
79
- if (!type) throw new Error('type is required');
80
-
81
- const events = await TimeTracker.searchEventByQuery('id == $0 AND type == $1', id, type);
82
- console.log('events', events);
83
-
84
- return events;
85
- } catch (error) {
86
- return Promise.reject(error);
87
- }
88
- }
89
-
90
- async _filterEventsExcludingId(id) {
91
- try {
92
- if (!id) throw new Error('id is required');
93
-
94
- const events = await TimeTracker.searchEventByQuery('id!= $0', id);
95
-
96
- return events;
97
- } catch (error) {
98
- return Promise.reject(error);
99
- }
100
- }
101
- }
102
-
103
- export default new TrackerRecords();
@@ -1,5 +0,0 @@
1
- import EventTrackingService from '@janiscommerce/app-tracking-time';
2
-
3
- const TimeTrackerService = new EventTrackingService('shift-tracking');
4
-
5
- export default TimeTrackerService;
@@ -1,38 +0,0 @@
1
- import TimeTracker from '../../../db/TimeTrackerService';
2
- import {isEmptyObject, isObject} from '../../helpers';
3
-
4
- const saveWorkLogTimesInDB = async (workLog = {}) => {
5
- try {
6
- if (!isObject(workLog) || isEmptyObject(workLog)) return false;
7
-
8
- const {startDate, endDate, shiftId, id, referenceId, name} = workLog;
9
-
10
- const dataForDB = {
11
- shiftId,
12
- referenceId,
13
- name,
14
- };
15
-
16
- await TimeTracker.addEvent({
17
- id,
18
- type: 'start',
19
- time: startDate,
20
- payload: dataForDB,
21
- });
22
-
23
- if (endDate) {
24
- await TimeTracker.addEvent({
25
- id,
26
- type: 'finish',
27
- time: endDate,
28
- payload: dataForDB,
29
- });
30
- }
31
-
32
- return true;
33
- } catch (error) {
34
- return Promise.reject(error);
35
- }
36
- };
37
-
38
- export default saveWorkLogTimesInDB;