washday-sdk 1.6.61 → 1.6.63
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.
- package/dist/api/attendance/post.js +79 -2
- package/dist/api/attendance/put.js +2 -1
- package/dist/index.js +1 -0
- package/package.json +1 -1
- package/src/api/attendance/post.ts +118 -14
- package/src/api/attendance/put.ts +12 -5
- package/src/index.ts +1 -0
- package/src/interfaces/Attendance.ts +98 -1
- package/src/interfaces/Permission.ts +1 -0
- package/test/attendance.metadata.test.ts +113 -0
- package/test/attendance.update.test.ts +30 -0
|
@@ -8,13 +8,90 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
});
|
|
9
9
|
};
|
|
10
10
|
const ATTENDANCE_API = 'api/attendance';
|
|
11
|
+
const ATTENDANCE_SOURCES = ["web_pos", "pos_v2", "unknown"];
|
|
12
|
+
const LOCATION_PERMISSION_STATUSES = [
|
|
13
|
+
"granted",
|
|
14
|
+
"denied",
|
|
15
|
+
"unavailable",
|
|
16
|
+
"not_requested",
|
|
17
|
+
"error",
|
|
18
|
+
];
|
|
19
|
+
const stringValue = (value) => (typeof value === "string" && value.trim() ? value.trim() : undefined);
|
|
20
|
+
const numberValue = (value) => (typeof value === "number" && Number.isFinite(value) ? value : undefined);
|
|
21
|
+
const sourceValue = (value) => (ATTENDANCE_SOURCES.includes(value) ? value : undefined);
|
|
22
|
+
const permissionStatusValue = (value) => (LOCATION_PERMISSION_STATUSES.includes(value)
|
|
23
|
+
? value
|
|
24
|
+
: undefined);
|
|
25
|
+
const dateValue = (value) => {
|
|
26
|
+
if (value instanceof Date && !Number.isNaN(value.getTime()))
|
|
27
|
+
return value;
|
|
28
|
+
if (typeof value !== "string")
|
|
29
|
+
return undefined;
|
|
30
|
+
return Number.isNaN(new Date(value).getTime()) ? undefined : value;
|
|
31
|
+
};
|
|
32
|
+
const buildSafeAttendanceLocation = (location) => {
|
|
33
|
+
if (!location)
|
|
34
|
+
return undefined;
|
|
35
|
+
const permissionStatus = permissionStatusValue(location.permissionStatus);
|
|
36
|
+
if (!permissionStatus)
|
|
37
|
+
return undefined;
|
|
38
|
+
const safeLocation = {
|
|
39
|
+
permissionStatus,
|
|
40
|
+
capturedAt: dateValue(location.capturedAt),
|
|
41
|
+
errorCode: stringValue(location.errorCode),
|
|
42
|
+
errorMessage: stringValue(location.errorMessage),
|
|
43
|
+
};
|
|
44
|
+
if (permissionStatus === "granted") {
|
|
45
|
+
safeLocation.latitude = numberValue(location.latitude);
|
|
46
|
+
safeLocation.longitude = numberValue(location.longitude);
|
|
47
|
+
safeLocation.accuracy = numberValue(location.accuracy);
|
|
48
|
+
safeLocation.altitude = numberValue(location.altitude);
|
|
49
|
+
safeLocation.altitudeAccuracy = numberValue(location.altitudeAccuracy);
|
|
50
|
+
safeLocation.heading = numberValue(location.heading);
|
|
51
|
+
safeLocation.speed = numberValue(location.speed);
|
|
52
|
+
}
|
|
53
|
+
const filteredEntries = Object.entries(safeLocation).filter(([, value]) => value !== undefined);
|
|
54
|
+
return filteredEntries.length
|
|
55
|
+
? Object.fromEntries(filteredEntries)
|
|
56
|
+
: undefined;
|
|
57
|
+
};
|
|
58
|
+
const buildSafeAttendanceMetadata = (metadata) => {
|
|
59
|
+
if (!metadata)
|
|
60
|
+
return undefined;
|
|
61
|
+
const safeMetadata = {
|
|
62
|
+
source: sourceValue(metadata.source),
|
|
63
|
+
platform: stringValue(metadata.platform),
|
|
64
|
+
appVersion: stringValue(metadata.appVersion),
|
|
65
|
+
deviceId: stringValue(metadata.deviceId),
|
|
66
|
+
language: stringValue(metadata.language),
|
|
67
|
+
timezone: stringValue(metadata.timezone),
|
|
68
|
+
screenWidth: numberValue(metadata.screenWidth),
|
|
69
|
+
screenHeight: numberValue(metadata.screenHeight),
|
|
70
|
+
location: buildSafeAttendanceLocation(metadata.location),
|
|
71
|
+
};
|
|
72
|
+
const filteredEntries = Object.entries(safeMetadata).filter(([, value]) => value !== undefined);
|
|
73
|
+
return filteredEntries.length
|
|
74
|
+
? Object.fromEntries(filteredEntries)
|
|
75
|
+
: undefined;
|
|
76
|
+
};
|
|
77
|
+
const buildAttendancePayload = (data) => {
|
|
78
|
+
const metadata = buildSafeAttendanceMetadata(data.metadata);
|
|
79
|
+
const payload = Object.assign({}, data);
|
|
80
|
+
if (metadata) {
|
|
81
|
+
payload.metadata = metadata;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
delete payload.metadata;
|
|
85
|
+
}
|
|
86
|
+
return payload;
|
|
87
|
+
};
|
|
11
88
|
export const clockIn = function (data) {
|
|
12
89
|
return __awaiter(this, void 0, void 0, function* () {
|
|
13
90
|
try {
|
|
14
91
|
const config = {
|
|
15
92
|
headers: { Authorization: `Bearer ${this.apiToken}` }
|
|
16
93
|
};
|
|
17
|
-
return yield this.axiosInstance.post(`${ATTENDANCE_API}/clock-in`, data, config);
|
|
94
|
+
return yield this.axiosInstance.post(`${ATTENDANCE_API}/clock-in`, buildAttendancePayload(data), config);
|
|
18
95
|
}
|
|
19
96
|
catch (error) {
|
|
20
97
|
console.error('Error clock-in:', error);
|
|
@@ -28,7 +105,7 @@ export const clockOut = function (data) {
|
|
|
28
105
|
const config = {
|
|
29
106
|
headers: { Authorization: `Bearer ${this.apiToken}` }
|
|
30
107
|
};
|
|
31
|
-
return yield this.axiosInstance.post(`${ATTENDANCE_API}/clock-out`, data, config);
|
|
108
|
+
return yield this.axiosInstance.post(`${ATTENDANCE_API}/clock-out`, buildAttendancePayload(data), config);
|
|
32
109
|
}
|
|
33
110
|
catch (error) {
|
|
34
111
|
console.error('Error clock-out:', error);
|
|
@@ -14,7 +14,8 @@ export const updateById = function (id, data) {
|
|
|
14
14
|
const config = {
|
|
15
15
|
headers: { Authorization: `Bearer ${this.apiToken}` }
|
|
16
16
|
};
|
|
17
|
-
|
|
17
|
+
const { timestamp, notes, reason } = data;
|
|
18
|
+
return yield this.axiosInstance.put(`${ATTENDANCE_API}/${id}`, Object.assign(Object.assign(Object.assign({}, (timestamp !== undefined ? { timestamp } : {})), (notes !== undefined ? { notes } : {})), { reason }), config);
|
|
18
19
|
}
|
|
19
20
|
catch (error) {
|
|
20
21
|
console.error('Error updating attendance entry:', error);
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,36 +1,140 @@
|
|
|
1
1
|
import { WashdayClientInstance } from "../../interfaces/Api";
|
|
2
|
-
import
|
|
2
|
+
import type {
|
|
3
|
+
AttendanceClientMetadata,
|
|
4
|
+
AttendanceLocationMetadata,
|
|
5
|
+
AttendanceLocationPermissionStatus,
|
|
6
|
+
AttendanceSource,
|
|
7
|
+
ClockInRequest,
|
|
8
|
+
ClockOutRequest,
|
|
9
|
+
} from "../../interfaces/Attendance";
|
|
3
10
|
|
|
4
11
|
const ATTENDANCE_API = 'api/attendance';
|
|
12
|
+
const ATTENDANCE_SOURCES: AttendanceSource[] = ["web_pos", "pos_v2", "unknown"];
|
|
13
|
+
const LOCATION_PERMISSION_STATUSES: AttendanceLocationPermissionStatus[] = [
|
|
14
|
+
"granted",
|
|
15
|
+
"denied",
|
|
16
|
+
"unavailable",
|
|
17
|
+
"not_requested",
|
|
18
|
+
"error",
|
|
19
|
+
];
|
|
5
20
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
21
|
+
const stringValue = (value: unknown): string | undefined => (
|
|
22
|
+
typeof value === "string" && value.trim() ? value.trim() : undefined
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const numberValue = (value: unknown): number | undefined => (
|
|
26
|
+
typeof value === "number" && Number.isFinite(value) ? value : undefined
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const sourceValue = (value: unknown): AttendanceSource | undefined => (
|
|
30
|
+
ATTENDANCE_SOURCES.includes(value as AttendanceSource) ? value as AttendanceSource : undefined
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const permissionStatusValue = (value: unknown): AttendanceLocationPermissionStatus | undefined => (
|
|
34
|
+
LOCATION_PERMISSION_STATUSES.includes(value as AttendanceLocationPermissionStatus)
|
|
35
|
+
? value as AttendanceLocationPermissionStatus
|
|
36
|
+
: undefined
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const dateValue = (value: unknown): string | Date | undefined => {
|
|
40
|
+
if (value instanceof Date && !Number.isNaN(value.getTime())) return value;
|
|
41
|
+
if (typeof value !== "string") return undefined;
|
|
42
|
+
return Number.isNaN(new Date(value).getTime()) ? undefined : value;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const buildSafeAttendanceLocation = (
|
|
46
|
+
location?: AttendanceLocationMetadata & Record<string, unknown>
|
|
47
|
+
): AttendanceLocationMetadata | undefined => {
|
|
48
|
+
if (!location) return undefined;
|
|
49
|
+
|
|
50
|
+
const permissionStatus = permissionStatusValue(location.permissionStatus);
|
|
51
|
+
if (!permissionStatus) return undefined;
|
|
52
|
+
|
|
53
|
+
const safeLocation: AttendanceLocationMetadata = {
|
|
54
|
+
permissionStatus,
|
|
55
|
+
capturedAt: dateValue(location.capturedAt),
|
|
56
|
+
errorCode: stringValue(location.errorCode),
|
|
57
|
+
errorMessage: stringValue(location.errorMessage),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
if (permissionStatus === "granted") {
|
|
61
|
+
safeLocation.latitude = numberValue(location.latitude);
|
|
62
|
+
safeLocation.longitude = numberValue(location.longitude);
|
|
63
|
+
safeLocation.accuracy = numberValue(location.accuracy);
|
|
64
|
+
safeLocation.altitude = numberValue(location.altitude);
|
|
65
|
+
safeLocation.altitudeAccuracy = numberValue(location.altitudeAccuracy);
|
|
66
|
+
safeLocation.heading = numberValue(location.heading);
|
|
67
|
+
safeLocation.speed = numberValue(location.speed);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const filteredEntries = Object.entries(safeLocation).filter(
|
|
71
|
+
([, value]) => value !== undefined
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return filteredEntries.length
|
|
75
|
+
? Object.fromEntries(filteredEntries) as AttendanceLocationMetadata
|
|
76
|
+
: undefined;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const buildSafeAttendanceMetadata = (
|
|
80
|
+
metadata?: AttendanceClientMetadata & Record<string, unknown>
|
|
81
|
+
): AttendanceClientMetadata | undefined => {
|
|
82
|
+
if (!metadata) return undefined;
|
|
83
|
+
|
|
84
|
+
const safeMetadata: AttendanceClientMetadata = {
|
|
85
|
+
source: sourceValue(metadata.source),
|
|
86
|
+
platform: stringValue(metadata.platform),
|
|
87
|
+
appVersion: stringValue(metadata.appVersion),
|
|
88
|
+
deviceId: stringValue(metadata.deviceId),
|
|
89
|
+
language: stringValue(metadata.language),
|
|
90
|
+
timezone: stringValue(metadata.timezone),
|
|
91
|
+
screenWidth: numberValue(metadata.screenWidth),
|
|
92
|
+
screenHeight: numberValue(metadata.screenHeight),
|
|
93
|
+
location: buildSafeAttendanceLocation(metadata.location as any),
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const filteredEntries = Object.entries(safeMetadata).filter(
|
|
97
|
+
([, value]) => value !== undefined
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return filteredEntries.length
|
|
101
|
+
? Object.fromEntries(filteredEntries) as AttendanceClientMetadata
|
|
102
|
+
: undefined;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const buildAttendancePayload = <T extends ClockInRequest | ClockOutRequest>(data: T): T => {
|
|
106
|
+
const metadata = buildSafeAttendanceMetadata(data.metadata as any);
|
|
107
|
+
const payload = { ...data } as T;
|
|
108
|
+
|
|
109
|
+
if (metadata) {
|
|
110
|
+
payload.metadata = metadata;
|
|
111
|
+
} else {
|
|
112
|
+
delete payload.metadata;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return payload;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export const clockIn = async function (this: WashdayClientInstance, data: ClockInRequest): Promise<any> {
|
|
11
119
|
try {
|
|
12
120
|
const config = {
|
|
13
121
|
headers: { Authorization: `Bearer ${this.apiToken}` }
|
|
14
122
|
};
|
|
15
|
-
return await this.axiosInstance.post(`${ATTENDANCE_API}/clock-in`, data, config);
|
|
123
|
+
return await this.axiosInstance.post(`${ATTENDANCE_API}/clock-in`, buildAttendancePayload(data), config);
|
|
16
124
|
} catch (error) {
|
|
17
125
|
console.error('Error clock-in:', error);
|
|
18
126
|
throw error;
|
|
19
127
|
}
|
|
20
128
|
};
|
|
21
129
|
|
|
22
|
-
export const clockOut = async function (this: WashdayClientInstance, data: {
|
|
23
|
-
userId?: string;
|
|
24
|
-
storeId?: string; // ✅ AÑADIDO - requerido por backend
|
|
25
|
-
notes?: string;
|
|
26
|
-
}): Promise<any> {
|
|
130
|
+
export const clockOut = async function (this: WashdayClientInstance, data: ClockOutRequest): Promise<any> {
|
|
27
131
|
try {
|
|
28
132
|
const config = {
|
|
29
133
|
headers: { Authorization: `Bearer ${this.apiToken}` }
|
|
30
134
|
};
|
|
31
|
-
return await this.axiosInstance.post(`${ATTENDANCE_API}/clock-out`, data, config);
|
|
135
|
+
return await this.axiosInstance.post(`${ATTENDANCE_API}/clock-out`, buildAttendancePayload(data), config);
|
|
32
136
|
} catch (error) {
|
|
33
137
|
console.error('Error clock-out:', error);
|
|
34
138
|
throw error;
|
|
35
139
|
}
|
|
36
|
-
};
|
|
140
|
+
};
|
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
import { WashdayClientInstance } from "../../interfaces/Api";
|
|
2
|
-
import axiosInstance from "../axiosInstance";
|
|
3
2
|
|
|
4
3
|
const ATTENDANCE_API = 'api/attendance';
|
|
5
4
|
|
|
6
|
-
export
|
|
5
|
+
export interface UpdateAttendanceEntryRequest {
|
|
7
6
|
timestamp?: string;
|
|
8
7
|
notes?: string;
|
|
8
|
+
reason: string;
|
|
9
9
|
editorId?: string;
|
|
10
|
-
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const updateById = async function (this: WashdayClientInstance, id: string, data: UpdateAttendanceEntryRequest): Promise<any> {
|
|
11
13
|
try {
|
|
12
14
|
const config = {
|
|
13
15
|
headers: { Authorization: `Bearer ${this.apiToken}` }
|
|
14
16
|
};
|
|
15
|
-
|
|
17
|
+
const { timestamp, notes, reason } = data;
|
|
18
|
+
return await this.axiosInstance.put(`${ATTENDANCE_API}/${id}`, {
|
|
19
|
+
...(timestamp !== undefined ? { timestamp } : {}),
|
|
20
|
+
...(notes !== undefined ? { notes } : {}),
|
|
21
|
+
reason,
|
|
22
|
+
}, config);
|
|
16
23
|
} catch (error) {
|
|
17
24
|
console.error('Error updating attendance entry:', error);
|
|
18
25
|
throw error;
|
|
19
26
|
}
|
|
20
|
-
};
|
|
27
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export * from './interfaces/Auth';
|
|
|
7
7
|
export * from './interfaces/Customer';
|
|
8
8
|
export * from './interfaces/DomainUrls';
|
|
9
9
|
export * from './interfaces/GoogleMaps';
|
|
10
|
+
export * from './interfaces/Attendance';
|
|
10
11
|
export type { StartMPOAuthFlowRequest } from './api/integrations/post';
|
|
11
12
|
export type {
|
|
12
13
|
CreateCFDISubscriptionCheckoutSessionRequest,
|
|
@@ -1,6 +1,62 @@
|
|
|
1
1
|
import { IUser } from "./User";
|
|
2
2
|
import { IStore } from "./Store";
|
|
3
3
|
|
|
4
|
+
export type AttendanceSource = "web_pos" | "pos_v2" | "unknown";
|
|
5
|
+
|
|
6
|
+
export type AttendanceLocationPermissionStatus =
|
|
7
|
+
| "granted"
|
|
8
|
+
| "denied"
|
|
9
|
+
| "unavailable"
|
|
10
|
+
| "not_requested"
|
|
11
|
+
| "error";
|
|
12
|
+
|
|
13
|
+
export interface AttendanceLocationMetadata {
|
|
14
|
+
latitude?: number;
|
|
15
|
+
longitude?: number;
|
|
16
|
+
accuracy?: number;
|
|
17
|
+
altitude?: number;
|
|
18
|
+
altitudeAccuracy?: number;
|
|
19
|
+
heading?: number;
|
|
20
|
+
speed?: number;
|
|
21
|
+
permissionStatus?: AttendanceLocationPermissionStatus;
|
|
22
|
+
capturedAt?: string | Date;
|
|
23
|
+
errorCode?: string;
|
|
24
|
+
errorMessage?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface AttendanceClientMetadata {
|
|
28
|
+
platform?: string;
|
|
29
|
+
appVersion?: string;
|
|
30
|
+
deviceId?: string;
|
|
31
|
+
source?: AttendanceSource;
|
|
32
|
+
language?: string;
|
|
33
|
+
timezone?: string;
|
|
34
|
+
screenWidth?: number;
|
|
35
|
+
screenHeight?: number;
|
|
36
|
+
location?: AttendanceLocationMetadata;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface AttendanceServerMetadata extends AttendanceClientMetadata {
|
|
40
|
+
ipAddress?: string;
|
|
41
|
+
cfConnectingIp?: string;
|
|
42
|
+
xForwardedFor?: string;
|
|
43
|
+
userAgent?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ClockInRequest {
|
|
47
|
+
storeId: string;
|
|
48
|
+
userId?: string;
|
|
49
|
+
notes?: string;
|
|
50
|
+
metadata?: AttendanceClientMetadata;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface ClockOutRequest {
|
|
54
|
+
userId?: string;
|
|
55
|
+
storeId?: string;
|
|
56
|
+
notes?: string;
|
|
57
|
+
metadata?: AttendanceClientMetadata;
|
|
58
|
+
}
|
|
59
|
+
|
|
4
60
|
export interface IAttendanceRecord {
|
|
5
61
|
_id?: string;
|
|
6
62
|
employee: IUser | string;
|
|
@@ -8,9 +64,12 @@ export interface IAttendanceRecord {
|
|
|
8
64
|
type: 'IN' | 'OUT';
|
|
9
65
|
timestamp: Date;
|
|
10
66
|
ipAddress?: string;
|
|
67
|
+
metadata?: AttendanceServerMetadata;
|
|
11
68
|
notes?: string;
|
|
12
69
|
editedBy?: IUser | string;
|
|
13
70
|
editedAt?: Date;
|
|
71
|
+
lastEditReason?: string;
|
|
72
|
+
revision?: number;
|
|
14
73
|
autoClosed?: boolean;
|
|
15
74
|
createdAt?: Date;
|
|
16
75
|
updatedAt?: Date;
|
|
@@ -29,6 +88,36 @@ export interface IAttendanceStatus {
|
|
|
29
88
|
clockInStore?: IStore | string;
|
|
30
89
|
}
|
|
31
90
|
|
|
91
|
+
export interface AttendanceShiftDuration {
|
|
92
|
+
hours: number;
|
|
93
|
+
minutes: number;
|
|
94
|
+
totalMinutes: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface AttendanceReportTableInfoRow {
|
|
98
|
+
_id?: string;
|
|
99
|
+
employee: IUser | string | {
|
|
100
|
+
_id?: string;
|
|
101
|
+
name?: string;
|
|
102
|
+
email?: string;
|
|
103
|
+
};
|
|
104
|
+
clockInTime?: Date | string;
|
|
105
|
+
clockOutTime?: Date | string | null;
|
|
106
|
+
shiftDuration?: AttendanceShiftDuration | null;
|
|
107
|
+
clockInStore?: string;
|
|
108
|
+
clockOutStore?: string | null;
|
|
109
|
+
notes?: string;
|
|
110
|
+
clockInIpAddress?: string;
|
|
111
|
+
clockOutIpAddress?: string;
|
|
112
|
+
clockInMetadata?: AttendanceServerMetadata | null;
|
|
113
|
+
clockOutMetadata?: AttendanceServerMetadata | null;
|
|
114
|
+
clockInLocation?: AttendanceLocationMetadata | null;
|
|
115
|
+
clockOutLocation?: AttendanceLocationMetadata | null;
|
|
116
|
+
clockInLocationSummary?: string;
|
|
117
|
+
clockOutLocationSummary?: string;
|
|
118
|
+
autoClosed?: boolean;
|
|
119
|
+
}
|
|
120
|
+
|
|
32
121
|
export interface IAttendanceReport {
|
|
33
122
|
storeId: string;
|
|
34
123
|
storeName?: string;
|
|
@@ -42,4 +131,12 @@ export interface IAttendanceReport {
|
|
|
42
131
|
startDate: Date;
|
|
43
132
|
endDate: Date;
|
|
44
133
|
};
|
|
45
|
-
|
|
134
|
+
tableInfo?: AttendanceReportTableInfoRow[];
|
|
135
|
+
totalRows?: number;
|
|
136
|
+
pagination?: {
|
|
137
|
+
currentPage: number;
|
|
138
|
+
totalPages: number;
|
|
139
|
+
totalRecords: number;
|
|
140
|
+
limit?: number;
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -13,6 +13,7 @@ export interface IPermissionMutationType {
|
|
|
13
13
|
|
|
14
14
|
export interface IPermission {
|
|
15
15
|
canEditOrders: IPermissionMutationType,
|
|
16
|
+
canManageAttendance: IPermissionMutationType,
|
|
16
17
|
mutateOrderPayments: IPermissionMutationType,
|
|
17
18
|
markOrderAsCollectedWithPendingAmount: IPermissionMutationType,
|
|
18
19
|
markOrderAsUncollected: IPermissionMutationType,
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { clockIn, clockOut } from "../src/api/attendance/post";
|
|
2
|
+
|
|
3
|
+
describe("attendance metadata requests", () => {
|
|
4
|
+
it("forwards client-safe clock-in metadata and strips IP fields", async () => {
|
|
5
|
+
const post = jest.fn().mockResolvedValue({ data: { data: {} } });
|
|
6
|
+
const client = {
|
|
7
|
+
apiToken: "token-123",
|
|
8
|
+
axiosInstance: { post },
|
|
9
|
+
} as any;
|
|
10
|
+
|
|
11
|
+
await clockIn.call(client, {
|
|
12
|
+
storeId: "store-1",
|
|
13
|
+
userId: "user-1",
|
|
14
|
+
metadata: {
|
|
15
|
+
source: "web_pos",
|
|
16
|
+
platform: "MacIntel",
|
|
17
|
+
language: "es-MX",
|
|
18
|
+
timezone: "America/Mexico_City",
|
|
19
|
+
screenWidth: 1440,
|
|
20
|
+
screenHeight: 900,
|
|
21
|
+
location: {
|
|
22
|
+
permissionStatus: "granted",
|
|
23
|
+
latitude: 19.4326,
|
|
24
|
+
longitude: -99.1332,
|
|
25
|
+
accuracy: 24,
|
|
26
|
+
capturedAt: "2026-05-26T14:00:00.000Z",
|
|
27
|
+
unknown: "drop-me",
|
|
28
|
+
},
|
|
29
|
+
ipAddress: "1.1.1.1",
|
|
30
|
+
cfConnectingIp: "1.1.1.2",
|
|
31
|
+
xForwardedFor: "1.1.1.3",
|
|
32
|
+
},
|
|
33
|
+
} as any);
|
|
34
|
+
|
|
35
|
+
expect(post).toHaveBeenCalledWith(
|
|
36
|
+
"api/attendance/clock-in",
|
|
37
|
+
{
|
|
38
|
+
storeId: "store-1",
|
|
39
|
+
userId: "user-1",
|
|
40
|
+
metadata: {
|
|
41
|
+
source: "web_pos",
|
|
42
|
+
platform: "MacIntel",
|
|
43
|
+
language: "es-MX",
|
|
44
|
+
timezone: "America/Mexico_City",
|
|
45
|
+
screenWidth: 1440,
|
|
46
|
+
screenHeight: 900,
|
|
47
|
+
location: {
|
|
48
|
+
permissionStatus: "granted",
|
|
49
|
+
latitude: 19.4326,
|
|
50
|
+
longitude: -99.1332,
|
|
51
|
+
accuracy: 24,
|
|
52
|
+
capturedAt: "2026-05-26T14:00:00.000Z",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
headers: { Authorization: "Bearer token-123" },
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("forwards client-safe clock-out metadata and strips IP fields", async () => {
|
|
63
|
+
const post = jest.fn().mockResolvedValue({ data: { data: {} } });
|
|
64
|
+
const client = {
|
|
65
|
+
apiToken: "token-123",
|
|
66
|
+
axiosInstance: { post },
|
|
67
|
+
} as any;
|
|
68
|
+
|
|
69
|
+
await clockOut.call(client, {
|
|
70
|
+
storeId: "store-1",
|
|
71
|
+
userId: "user-1",
|
|
72
|
+
metadata: {
|
|
73
|
+
source: "pos_v2",
|
|
74
|
+
platform: "ios",
|
|
75
|
+
appVersion: "2.5.0",
|
|
76
|
+
deviceId: "device-1",
|
|
77
|
+
location: {
|
|
78
|
+
permissionStatus: "denied",
|
|
79
|
+
latitude: 19.4326,
|
|
80
|
+
longitude: -99.1332,
|
|
81
|
+
accuracy: 24,
|
|
82
|
+
capturedAt: "2026-05-26T14:00:00.000Z",
|
|
83
|
+
errorCode: "1",
|
|
84
|
+
errorMessage: "Permission denied",
|
|
85
|
+
},
|
|
86
|
+
ipAddress: "1.1.1.1",
|
|
87
|
+
},
|
|
88
|
+
} as any);
|
|
89
|
+
|
|
90
|
+
expect(post).toHaveBeenCalledWith(
|
|
91
|
+
"api/attendance/clock-out",
|
|
92
|
+
{
|
|
93
|
+
storeId: "store-1",
|
|
94
|
+
userId: "user-1",
|
|
95
|
+
metadata: {
|
|
96
|
+
source: "pos_v2",
|
|
97
|
+
platform: "ios",
|
|
98
|
+
appVersion: "2.5.0",
|
|
99
|
+
deviceId: "device-1",
|
|
100
|
+
location: {
|
|
101
|
+
permissionStatus: "denied",
|
|
102
|
+
capturedAt: "2026-05-26T14:00:00.000Z",
|
|
103
|
+
errorCode: "1",
|
|
104
|
+
errorMessage: "Permission denied",
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
headers: { Authorization: "Bearer token-123" },
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { updateById } from "../src/api/attendance/put";
|
|
2
|
+
|
|
3
|
+
describe("attendance update requests", () => {
|
|
4
|
+
it("sends reason with editable attendance fields", async () => {
|
|
5
|
+
const put = jest.fn().mockResolvedValue({ data: { data: {} } });
|
|
6
|
+
const client = {
|
|
7
|
+
apiToken: "token-123",
|
|
8
|
+
axiosInstance: { put },
|
|
9
|
+
} as any;
|
|
10
|
+
|
|
11
|
+
await updateById.call(client, "entry-1", {
|
|
12
|
+
timestamp: "2026-05-26T14:00:00.000Z",
|
|
13
|
+
notes: "Corrected note",
|
|
14
|
+
reason: "El empleado olvidó marcar a tiempo",
|
|
15
|
+
editorId: "ignored-by-backend",
|
|
16
|
+
} as any);
|
|
17
|
+
|
|
18
|
+
expect(put).toHaveBeenCalledWith(
|
|
19
|
+
"api/attendance/entry-1",
|
|
20
|
+
{
|
|
21
|
+
timestamp: "2026-05-26T14:00:00.000Z",
|
|
22
|
+
notes: "Corrected note",
|
|
23
|
+
reason: "El empleado olvidó marcar a tiempo",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
headers: { Authorization: "Bearer token-123" },
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
});
|