washday-sdk 1.6.60 → 1.6.62

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.
@@ -8,13 +8,46 @@ 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 stringValue = (value) => (typeof value === "string" && value.trim() ? value.trim() : undefined);
13
+ const numberValue = (value) => (typeof value === "number" && Number.isFinite(value) ? value : undefined);
14
+ const sourceValue = (value) => (ATTENDANCE_SOURCES.includes(value) ? value : undefined);
15
+ const buildSafeAttendanceMetadata = (metadata) => {
16
+ if (!metadata)
17
+ return undefined;
18
+ const safeMetadata = {
19
+ source: sourceValue(metadata.source),
20
+ platform: stringValue(metadata.platform),
21
+ appVersion: stringValue(metadata.appVersion),
22
+ deviceId: stringValue(metadata.deviceId),
23
+ language: stringValue(metadata.language),
24
+ timezone: stringValue(metadata.timezone),
25
+ screenWidth: numberValue(metadata.screenWidth),
26
+ screenHeight: numberValue(metadata.screenHeight),
27
+ };
28
+ const filteredEntries = Object.entries(safeMetadata).filter(([, value]) => value !== undefined);
29
+ return filteredEntries.length
30
+ ? Object.fromEntries(filteredEntries)
31
+ : undefined;
32
+ };
33
+ const buildAttendancePayload = (data) => {
34
+ const metadata = buildSafeAttendanceMetadata(data.metadata);
35
+ const payload = Object.assign({}, data);
36
+ if (metadata) {
37
+ payload.metadata = metadata;
38
+ }
39
+ else {
40
+ delete payload.metadata;
41
+ }
42
+ return payload;
43
+ };
11
44
  export const clockIn = function (data) {
12
45
  return __awaiter(this, void 0, void 0, function* () {
13
46
  try {
14
47
  const config = {
15
48
  headers: { Authorization: `Bearer ${this.apiToken}` }
16
49
  };
17
- return yield this.axiosInstance.post(`${ATTENDANCE_API}/clock-in`, data, config);
50
+ return yield this.axiosInstance.post(`${ATTENDANCE_API}/clock-in`, buildAttendancePayload(data), config);
18
51
  }
19
52
  catch (error) {
20
53
  console.error('Error clock-in:', error);
@@ -28,7 +61,7 @@ export const clockOut = function (data) {
28
61
  const config = {
29
62
  headers: { Authorization: `Bearer ${this.apiToken}` }
30
63
  };
31
- return yield this.axiosInstance.post(`${ATTENDANCE_API}/clock-out`, data, config);
64
+ return yield this.axiosInstance.post(`${ATTENDANCE_API}/clock-out`, buildAttendancePayload(data), config);
32
65
  }
33
66
  catch (error) {
34
67
  console.error('Error clock-out:', error);
package/dist/index.js CHANGED
@@ -7,4 +7,5 @@ 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 { WashdayClient, utils };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "washday-sdk",
3
- "version": "1.6.60",
3
+ "version": "1.6.62",
4
4
  "description": "Washday utilities functions and API",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -1,36 +1,84 @@
1
1
  import { WashdayClientInstance } from "../../interfaces/Api";
2
- import axiosInstance from "../axiosInstance";
2
+ import type {
3
+ AttendanceClientMetadata,
4
+ AttendanceSource,
5
+ ClockInRequest,
6
+ ClockOutRequest,
7
+ } from "../../interfaces/Attendance";
3
8
 
4
9
  const ATTENDANCE_API = 'api/attendance';
10
+ const ATTENDANCE_SOURCES: AttendanceSource[] = ["web_pos", "pos_v2", "unknown"];
5
11
 
6
- export const clockIn = async function (this: WashdayClientInstance, data: {
7
- storeId: string;
8
- userId?: string;
9
- notes?: string;
10
- }): Promise<any> {
12
+ const stringValue = (value: unknown): string | undefined => (
13
+ typeof value === "string" && value.trim() ? value.trim() : undefined
14
+ );
15
+
16
+ const numberValue = (value: unknown): number | undefined => (
17
+ typeof value === "number" && Number.isFinite(value) ? value : undefined
18
+ );
19
+
20
+ const sourceValue = (value: unknown): AttendanceSource | undefined => (
21
+ ATTENDANCE_SOURCES.includes(value as AttendanceSource) ? value as AttendanceSource : undefined
22
+ );
23
+
24
+ const buildSafeAttendanceMetadata = (
25
+ metadata?: AttendanceClientMetadata & Record<string, unknown>
26
+ ): AttendanceClientMetadata | undefined => {
27
+ if (!metadata) return undefined;
28
+
29
+ const safeMetadata: AttendanceClientMetadata = {
30
+ source: sourceValue(metadata.source),
31
+ platform: stringValue(metadata.platform),
32
+ appVersion: stringValue(metadata.appVersion),
33
+ deviceId: stringValue(metadata.deviceId),
34
+ language: stringValue(metadata.language),
35
+ timezone: stringValue(metadata.timezone),
36
+ screenWidth: numberValue(metadata.screenWidth),
37
+ screenHeight: numberValue(metadata.screenHeight),
38
+ };
39
+
40
+ const filteredEntries = Object.entries(safeMetadata).filter(
41
+ ([, value]) => value !== undefined
42
+ );
43
+
44
+ return filteredEntries.length
45
+ ? Object.fromEntries(filteredEntries) as AttendanceClientMetadata
46
+ : undefined;
47
+ };
48
+
49
+ const buildAttendancePayload = <T extends ClockInRequest | ClockOutRequest>(data: T): T => {
50
+ const metadata = buildSafeAttendanceMetadata(data.metadata as any);
51
+ const payload = { ...data } as T;
52
+
53
+ if (metadata) {
54
+ payload.metadata = metadata;
55
+ } else {
56
+ delete payload.metadata;
57
+ }
58
+
59
+ return payload;
60
+ };
61
+
62
+ export const clockIn = async function (this: WashdayClientInstance, data: ClockInRequest): Promise<any> {
11
63
  try {
12
64
  const config = {
13
65
  headers: { Authorization: `Bearer ${this.apiToken}` }
14
66
  };
15
- return await this.axiosInstance.post(`${ATTENDANCE_API}/clock-in`, data, config);
67
+ return await this.axiosInstance.post(`${ATTENDANCE_API}/clock-in`, buildAttendancePayload(data), config);
16
68
  } catch (error) {
17
69
  console.error('Error clock-in:', error);
18
70
  throw error;
19
71
  }
20
72
  };
21
73
 
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> {
74
+ export const clockOut = async function (this: WashdayClientInstance, data: ClockOutRequest): Promise<any> {
27
75
  try {
28
76
  const config = {
29
77
  headers: { Authorization: `Bearer ${this.apiToken}` }
30
78
  };
31
- return await this.axiosInstance.post(`${ATTENDANCE_API}/clock-out`, data, config);
79
+ return await this.axiosInstance.post(`${ATTENDANCE_API}/clock-out`, buildAttendancePayload(data), config);
32
80
  } catch (error) {
33
81
  console.error('Error clock-out:', error);
34
82
  throw error;
35
83
  }
36
- };
84
+ };
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,40 @@
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 interface AttendanceClientMetadata {
7
+ platform?: string;
8
+ appVersion?: string;
9
+ deviceId?: string;
10
+ source?: AttendanceSource;
11
+ language?: string;
12
+ timezone?: string;
13
+ screenWidth?: number;
14
+ screenHeight?: number;
15
+ }
16
+
17
+ export interface AttendanceServerMetadata extends AttendanceClientMetadata {
18
+ ipAddress?: string;
19
+ cfConnectingIp?: string;
20
+ xForwardedFor?: string;
21
+ userAgent?: string;
22
+ }
23
+
24
+ export interface ClockInRequest {
25
+ storeId: string;
26
+ userId?: string;
27
+ notes?: string;
28
+ metadata?: AttendanceClientMetadata;
29
+ }
30
+
31
+ export interface ClockOutRequest {
32
+ userId?: string;
33
+ storeId?: string;
34
+ notes?: string;
35
+ metadata?: AttendanceClientMetadata;
36
+ }
37
+
4
38
  export interface IAttendanceRecord {
5
39
  _id?: string;
6
40
  employee: IUser | string;
@@ -8,6 +42,7 @@ export interface IAttendanceRecord {
8
42
  type: 'IN' | 'OUT';
9
43
  timestamp: Date;
10
44
  ipAddress?: string;
45
+ metadata?: AttendanceServerMetadata;
11
46
  notes?: string;
12
47
  editedBy?: IUser | string;
13
48
  editedAt?: Date;
@@ -42,4 +77,4 @@ export interface IAttendanceReport {
42
77
  startDate: Date;
43
78
  endDate: Date;
44
79
  };
45
- }
80
+ }
@@ -33,13 +33,17 @@ export interface UpdateCompanySubscriptionBillingInfoPayload {
33
33
 
34
34
  export type CFDIBillingMode = "none" | "separate_subscription" | "main_subscription_item";
35
35
  export type CFDIMigrationStatus = "none" | "pending_cycle_end" | "migrated";
36
+ export type CFDIBillingStatus = "inactive" | "active" | "cancel_scheduled";
36
37
 
37
38
  export interface CFDIAddonStatus {
38
39
  enabled: boolean;
39
40
  billingMode: CFDIBillingMode;
40
41
  migrationStatus: CFDIMigrationStatus;
42
+ billingStatus: CFDIBillingStatus;
43
+ scheduledDisableAt: string | null;
41
44
  isLegacy: boolean;
42
45
  canToggleFromUI: boolean;
46
+ canCancelScheduledDisable: boolean;
43
47
  }
44
48
 
45
49
  export interface CFDIAddonToggleRequest {
@@ -55,6 +59,9 @@ export interface IStripeSettings {
55
59
  stripeCFDISubItemID: string | null;
56
60
  cfdiBillingMode?: CFDIBillingMode;
57
61
  cfdiMigrationStatus?: CFDIMigrationStatus;
62
+ cfdiBillingStatus?: CFDIBillingStatus;
63
+ cfdiScheduledDisableAt?: Date | string | null;
64
+ stripeCFDIDisableScheduleID?: string | null;
58
65
  legacyStripeCFDISubID?: string | null;
59
66
  legacyStripeCFDISubItemID?: string | null;
60
67
  }
@@ -0,0 +1,83 @@
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
+ ipAddress: "1.1.1.1",
22
+ cfConnectingIp: "1.1.1.2",
23
+ xForwardedFor: "1.1.1.3",
24
+ },
25
+ } as any);
26
+
27
+ expect(post).toHaveBeenCalledWith(
28
+ "api/attendance/clock-in",
29
+ {
30
+ storeId: "store-1",
31
+ userId: "user-1",
32
+ metadata: {
33
+ source: "web_pos",
34
+ platform: "MacIntel",
35
+ language: "es-MX",
36
+ timezone: "America/Mexico_City",
37
+ screenWidth: 1440,
38
+ screenHeight: 900,
39
+ },
40
+ },
41
+ {
42
+ headers: { Authorization: "Bearer token-123" },
43
+ },
44
+ );
45
+ });
46
+
47
+ it("forwards client-safe clock-out metadata and strips IP fields", async () => {
48
+ const post = jest.fn().mockResolvedValue({ data: { data: {} } });
49
+ const client = {
50
+ apiToken: "token-123",
51
+ axiosInstance: { post },
52
+ } as any;
53
+
54
+ await clockOut.call(client, {
55
+ storeId: "store-1",
56
+ userId: "user-1",
57
+ metadata: {
58
+ source: "pos_v2",
59
+ platform: "ios",
60
+ appVersion: "2.5.0",
61
+ deviceId: "device-1",
62
+ ipAddress: "1.1.1.1",
63
+ },
64
+ } as any);
65
+
66
+ expect(post).toHaveBeenCalledWith(
67
+ "api/attendance/clock-out",
68
+ {
69
+ storeId: "store-1",
70
+ userId: "user-1",
71
+ metadata: {
72
+ source: "pos_v2",
73
+ platform: "ios",
74
+ appVersion: "2.5.0",
75
+ deviceId: "device-1",
76
+ },
77
+ },
78
+ {
79
+ headers: { Authorization: "Bearer token-123" },
80
+ },
81
+ );
82
+ });
83
+ });
@@ -3,6 +3,7 @@ import {
3
3
  enableCFDIAddon,
4
4
  } from "../src/api/companies/post";
5
5
  import { getCFDIAddonStatus } from "../src/api/companies/get";
6
+ import type { CFDIAddonStatus } from "../src/interfaces/Company";
6
7
 
7
8
  describe("CFDI billing add-on company endpoints", () => {
8
9
  it("enables the CFDI add-on through the billing endpoint", async () => {
@@ -51,4 +52,19 @@ describe("CFDI billing add-on company endpoints", () => {
51
52
  { headers: { Authorization: "Bearer token-1" } },
52
53
  );
53
54
  });
55
+
56
+ it("types scheduled disable status fields", () => {
57
+ const status: CFDIAddonStatus = {
58
+ enabled: false,
59
+ billingMode: "main_subscription_item",
60
+ migrationStatus: "migrated",
61
+ billingStatus: "cancel_scheduled",
62
+ scheduledDisableAt: "2026-06-01T00:00:00.000Z",
63
+ canCancelScheduledDisable: true,
64
+ isLegacy: false,
65
+ canToggleFromUI: true,
66
+ };
67
+
68
+ expect(status.billingStatus).toBe("cancel_scheduled");
69
+ });
54
70
  });