@markwharton/eh-payroll 2.1.1 → 2.1.2
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/client.d.ts +12 -0
- package/dist/client.js +68 -139
- package/dist/errors.d.ts +6 -6
- package/dist/errors.js +9 -9
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/client.d.ts
CHANGED
|
@@ -52,6 +52,18 @@ export declare class EHClient {
|
|
|
52
52
|
* Fetch a URL and parse the response, with standardized error handling.
|
|
53
53
|
*/
|
|
54
54
|
private fetchAndParse;
|
|
55
|
+
/**
|
|
56
|
+
* Fetch all pages of a paginated endpoint.
|
|
57
|
+
*
|
|
58
|
+
* Payroll API pagination uses either OData ($skip/$top) or PascalCase
|
|
59
|
+
* (CurrentPage/PageSize). The caller provides a buildUrl callback that
|
|
60
|
+
* receives the current skip offset and returns the full URL with
|
|
61
|
+
* pagination parameters set.
|
|
62
|
+
*
|
|
63
|
+
* Consumer always receives the complete array — pageSize controls
|
|
64
|
+
* the internal batch size (items per API call).
|
|
65
|
+
*/
|
|
66
|
+
private fetchPaginated;
|
|
55
67
|
/**
|
|
56
68
|
* Validate the API key by calling GET /user
|
|
57
69
|
*/
|
package/dist/client.js
CHANGED
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { AU_EMPLOYEE_OPERATIONAL_FIELDS, AU_EMPLOYEE_FIELDS, LOCATION_FIELDS, EMPLOYEE_GROUP_FIELDS, ROSTER_SHIFT_FIELDS, KIOSK_FIELDS, KIOSK_EMPLOYEE_FIELDS, } from './types.js';
|
|
10
10
|
import { buildBasicAuthHeader } from './utils.js';
|
|
11
|
-
import {
|
|
11
|
+
import { parseEHPayrollErrorResponse } from './errors.js';
|
|
12
12
|
import { EH_API_BASE, EH_REGION_URLS } from './constants.js';
|
|
13
|
-
import { TTLCache, pickFields, RateLimiter, getErrorMessage, fetchWithRetry, resolveRetryConfig, ok, err } from '@markwharton/api-core';
|
|
13
|
+
import { TTLCache, pickFields, RateLimiter, getErrorMessage, fetchWithRetry, resolveRetryConfig, ok, okVoid, err } from '@markwharton/api-core';
|
|
14
14
|
/** Default page size for paginated endpoints */
|
|
15
15
|
const DEFAULT_PAGE_SIZE = 100;
|
|
16
16
|
// ============================================================================
|
|
@@ -114,7 +114,7 @@ export class EHClient {
|
|
|
114
114
|
const response = await this.fetch(url, fetchOptions);
|
|
115
115
|
if (!response.ok) {
|
|
116
116
|
const errorText = await response.text();
|
|
117
|
-
const { message } =
|
|
117
|
+
const { message } = parseEHPayrollErrorResponse(errorText, response.status);
|
|
118
118
|
return err(message, response.status);
|
|
119
119
|
}
|
|
120
120
|
return ok(await parse(response));
|
|
@@ -123,6 +123,42 @@ export class EHClient {
|
|
|
123
123
|
return err(getErrorMessage(error), 0);
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Fetch all pages of a paginated endpoint.
|
|
128
|
+
*
|
|
129
|
+
* Payroll API pagination uses either OData ($skip/$top) or PascalCase
|
|
130
|
+
* (CurrentPage/PageSize). The caller provides a buildUrl callback that
|
|
131
|
+
* receives the current skip offset and returns the full URL with
|
|
132
|
+
* pagination parameters set.
|
|
133
|
+
*
|
|
134
|
+
* Consumer always receives the complete array — pageSize controls
|
|
135
|
+
* the internal batch size (items per API call).
|
|
136
|
+
*/
|
|
137
|
+
async fetchPaginated(buildUrl, fields, pageSize = DEFAULT_PAGE_SIZE) {
|
|
138
|
+
try {
|
|
139
|
+
const allItems = [];
|
|
140
|
+
let skip = 0;
|
|
141
|
+
while (true) {
|
|
142
|
+
const url = buildUrl(skip);
|
|
143
|
+
const response = await this.fetch(url);
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
const errorText = await response.text();
|
|
146
|
+
const { message } = parseEHPayrollErrorResponse(errorText, response.status);
|
|
147
|
+
return err(message, response.status);
|
|
148
|
+
}
|
|
149
|
+
const page = (await response.json())
|
|
150
|
+
.map(item => pickFields(item, fields));
|
|
151
|
+
allItems.push(...page);
|
|
152
|
+
if (page.length < pageSize)
|
|
153
|
+
break;
|
|
154
|
+
skip += pageSize;
|
|
155
|
+
}
|
|
156
|
+
return ok(allItems);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
return err(getErrorMessage(error), 0);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
126
162
|
// ============================================================================
|
|
127
163
|
// Validation
|
|
128
164
|
// ============================================================================
|
|
@@ -134,7 +170,7 @@ export class EHClient {
|
|
|
134
170
|
try {
|
|
135
171
|
const response = await this.fetch(url);
|
|
136
172
|
if (response.ok) {
|
|
137
|
-
return
|
|
173
|
+
return okVoid();
|
|
138
174
|
}
|
|
139
175
|
if (response.status === 401 || response.status === 403) {
|
|
140
176
|
return err('Invalid or expired API key', response.status);
|
|
@@ -161,37 +197,17 @@ export class EHClient {
|
|
|
161
197
|
if (options?.includePii)
|
|
162
198
|
parts.push('pii');
|
|
163
199
|
const cacheKey = parts.join(':');
|
|
164
|
-
return this.cached(cacheKey, this.cacheTtl.employeesTtl,
|
|
200
|
+
return this.cached(cacheKey, this.cacheTtl.employeesTtl, () => {
|
|
165
201
|
const params = new URLSearchParams();
|
|
166
202
|
if (options?.payScheduleId != null)
|
|
167
203
|
params.set('filter.payScheduleId', String(options.payScheduleId));
|
|
168
204
|
if (options?.locationId != null)
|
|
169
205
|
params.set('filter.locationId', String(options.locationId));
|
|
170
206
|
params.set('$top', String(DEFAULT_PAGE_SIZE));
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
params.set('$skip', String(skip));
|
|
176
|
-
const url = `${this.baseUrl}/business/${this.businessId}/employee/unstructured?${params}`;
|
|
177
|
-
const response = await this.fetch(url);
|
|
178
|
-
if (!response.ok) {
|
|
179
|
-
const errorText = await response.text();
|
|
180
|
-
const { message } = parseEHErrorResponse(errorText, response.status);
|
|
181
|
-
return err(message, response.status);
|
|
182
|
-
}
|
|
183
|
-
const page = (await response.json())
|
|
184
|
-
.map(item => pickFields(item, fields));
|
|
185
|
-
allEmployees.push(...page);
|
|
186
|
-
if (page.length < DEFAULT_PAGE_SIZE)
|
|
187
|
-
break;
|
|
188
|
-
skip += DEFAULT_PAGE_SIZE;
|
|
189
|
-
}
|
|
190
|
-
return ok(allEmployees);
|
|
191
|
-
}
|
|
192
|
-
catch (error) {
|
|
193
|
-
return err(getErrorMessage(error), 0);
|
|
194
|
-
}
|
|
207
|
+
return this.fetchPaginated((skip) => {
|
|
208
|
+
params.set('$skip', String(skip));
|
|
209
|
+
return `${this.baseUrl}/business/${this.businessId}/employee/unstructured?${params}`;
|
|
210
|
+
}, fields);
|
|
195
211
|
});
|
|
196
212
|
}
|
|
197
213
|
/**
|
|
@@ -228,34 +244,12 @@ export class EHClient {
|
|
|
228
244
|
* Get all business locations
|
|
229
245
|
*/
|
|
230
246
|
async getLocations() {
|
|
231
|
-
return this.cached('locations', this.cacheTtl.locationsTtl,
|
|
232
|
-
const params = new URLSearchParams({
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
let skip = 0;
|
|
238
|
-
while (true) {
|
|
239
|
-
params.set('$skip', String(skip));
|
|
240
|
-
const url = `${this.baseUrl}/business/${this.businessId}/location?${params}`;
|
|
241
|
-
const response = await this.fetch(url);
|
|
242
|
-
if (!response.ok) {
|
|
243
|
-
const errorText = await response.text();
|
|
244
|
-
const { message } = parseEHErrorResponse(errorText, response.status);
|
|
245
|
-
return err(message, response.status);
|
|
246
|
-
}
|
|
247
|
-
const page = (await response.json())
|
|
248
|
-
.map(item => pickFields(item, LOCATION_FIELDS));
|
|
249
|
-
allLocations.push(...page);
|
|
250
|
-
if (page.length < DEFAULT_PAGE_SIZE)
|
|
251
|
-
break;
|
|
252
|
-
skip += DEFAULT_PAGE_SIZE;
|
|
253
|
-
}
|
|
254
|
-
return ok(allLocations);
|
|
255
|
-
}
|
|
256
|
-
catch (error) {
|
|
257
|
-
return err(getErrorMessage(error), 0);
|
|
258
|
-
}
|
|
247
|
+
return this.cached('locations', this.cacheTtl.locationsTtl, () => {
|
|
248
|
+
const params = new URLSearchParams({ '$top': String(DEFAULT_PAGE_SIZE) });
|
|
249
|
+
return this.fetchPaginated((skip) => {
|
|
250
|
+
params.set('$skip', String(skip));
|
|
251
|
+
return `${this.baseUrl}/business/${this.businessId}/location?${params}`;
|
|
252
|
+
}, LOCATION_FIELDS);
|
|
259
253
|
});
|
|
260
254
|
}
|
|
261
255
|
// ============================================================================
|
|
@@ -265,34 +259,12 @@ export class EHClient {
|
|
|
265
259
|
* Get all employee groups
|
|
266
260
|
*/
|
|
267
261
|
async getEmployeeGroups() {
|
|
268
|
-
return this.cached('
|
|
269
|
-
const params = new URLSearchParams({
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
let skip = 0;
|
|
275
|
-
while (true) {
|
|
276
|
-
params.set('$skip', String(skip));
|
|
277
|
-
const url = `${this.baseUrl}/business/${this.businessId}/employeegroup?${params}`;
|
|
278
|
-
const response = await this.fetch(url);
|
|
279
|
-
if (!response.ok) {
|
|
280
|
-
const errorText = await response.text();
|
|
281
|
-
const { message } = parseEHErrorResponse(errorText, response.status);
|
|
282
|
-
return err(message, response.status);
|
|
283
|
-
}
|
|
284
|
-
const page = (await response.json())
|
|
285
|
-
.map(item => pickFields(item, EMPLOYEE_GROUP_FIELDS));
|
|
286
|
-
allGroups.push(...page);
|
|
287
|
-
if (page.length < DEFAULT_PAGE_SIZE)
|
|
288
|
-
break;
|
|
289
|
-
skip += DEFAULT_PAGE_SIZE;
|
|
290
|
-
}
|
|
291
|
-
return ok(allGroups);
|
|
292
|
-
}
|
|
293
|
-
catch (error) {
|
|
294
|
-
return err(getErrorMessage(error), 0);
|
|
295
|
-
}
|
|
262
|
+
return this.cached('employeegroups', this.cacheTtl.groupsTtl, () => {
|
|
263
|
+
const params = new URLSearchParams({ '$top': String(DEFAULT_PAGE_SIZE) });
|
|
264
|
+
return this.fetchPaginated((skip) => {
|
|
265
|
+
params.set('$skip', String(skip));
|
|
266
|
+
return `${this.baseUrl}/business/${this.businessId}/employeegroup?${params}`;
|
|
267
|
+
}, EMPLOYEE_GROUP_FIELDS);
|
|
296
268
|
});
|
|
297
269
|
}
|
|
298
270
|
// ============================================================================
|
|
@@ -332,7 +304,7 @@ export class EHClient {
|
|
|
332
304
|
if (options?.includeWarnings)
|
|
333
305
|
parts.push('iw');
|
|
334
306
|
const cacheKey = parts.join(':');
|
|
335
|
-
return this.cached(cacheKey, this.cacheTtl.rosterShiftsTtl,
|
|
307
|
+
return this.cached(cacheKey, this.cacheTtl.rosterShiftsTtl, () => {
|
|
336
308
|
// Roster shift query params are PascalCase per the Swagger spec, unlike
|
|
337
309
|
// all other endpoints which use camelCase. See: https://api.keypay.com.au/swagger-au.json
|
|
338
310
|
const params = new URLSearchParams({
|
|
@@ -372,31 +344,10 @@ export class EHClient {
|
|
|
372
344
|
params.set('ExcludeShiftsOverlappingFromDate', 'true');
|
|
373
345
|
if (options?.includeWarnings)
|
|
374
346
|
params.set('IncludeWarnings', 'true');
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
while (true) {
|
|
380
|
-
params.set('CurrentPage', String(currentPage));
|
|
381
|
-
const url = `${this.baseUrl}/business/${this.businessId}/rostershift?${params}`;
|
|
382
|
-
const response = await this.fetch(url);
|
|
383
|
-
if (!response.ok) {
|
|
384
|
-
const errorText = await response.text();
|
|
385
|
-
const { message } = parseEHErrorResponse(errorText, response.status);
|
|
386
|
-
return err(message, response.status);
|
|
387
|
-
}
|
|
388
|
-
const page = (await response.json())
|
|
389
|
-
.map(item => pickFields(item, ROSTER_SHIFT_FIELDS));
|
|
390
|
-
allShifts.push(...page);
|
|
391
|
-
if (page.length < DEFAULT_PAGE_SIZE)
|
|
392
|
-
break;
|
|
393
|
-
currentPage++;
|
|
394
|
-
}
|
|
395
|
-
return ok(allShifts);
|
|
396
|
-
}
|
|
397
|
-
catch (error) {
|
|
398
|
-
return err(getErrorMessage(error), 0);
|
|
399
|
-
}
|
|
347
|
+
return this.fetchPaginated((skip) => {
|
|
348
|
+
params.set('CurrentPage', String(skip / DEFAULT_PAGE_SIZE + 1));
|
|
349
|
+
return `${this.baseUrl}/business/${this.businessId}/rostershift?${params}`;
|
|
350
|
+
}, ROSTER_SHIFT_FIELDS);
|
|
400
351
|
});
|
|
401
352
|
}
|
|
402
353
|
// ============================================================================
|
|
@@ -406,34 +357,12 @@ export class EHClient {
|
|
|
406
357
|
* Get all kiosks for the business
|
|
407
358
|
*/
|
|
408
359
|
async getKiosks() {
|
|
409
|
-
return this.cached('kiosks', this.cacheTtl.kiosksTtl,
|
|
410
|
-
const params = new URLSearchParams({
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
let skip = 0;
|
|
416
|
-
while (true) {
|
|
417
|
-
params.set('$skip', String(skip));
|
|
418
|
-
const url = `${this.baseUrl}/business/${this.businessId}/kiosk?${params}`;
|
|
419
|
-
const response = await this.fetch(url);
|
|
420
|
-
if (!response.ok) {
|
|
421
|
-
const errorText = await response.text();
|
|
422
|
-
const { message } = parseEHErrorResponse(errorText, response.status);
|
|
423
|
-
return err(message, response.status);
|
|
424
|
-
}
|
|
425
|
-
const page = (await response.json())
|
|
426
|
-
.map(item => pickFields(item, KIOSK_FIELDS));
|
|
427
|
-
allKiosks.push(...page);
|
|
428
|
-
if (page.length < DEFAULT_PAGE_SIZE)
|
|
429
|
-
break;
|
|
430
|
-
skip += DEFAULT_PAGE_SIZE;
|
|
431
|
-
}
|
|
432
|
-
return ok(allKiosks);
|
|
433
|
-
}
|
|
434
|
-
catch (error) {
|
|
435
|
-
return err(getErrorMessage(error), 0);
|
|
436
|
-
}
|
|
360
|
+
return this.cached('kiosks', this.cacheTtl.kiosksTtl, () => {
|
|
361
|
+
const params = new URLSearchParams({ '$top': String(DEFAULT_PAGE_SIZE) });
|
|
362
|
+
return this.fetchPaginated((skip) => {
|
|
363
|
+
params.set('$skip', String(skip));
|
|
364
|
+
return `${this.baseUrl}/business/${this.businessId}/kiosk?${params}`;
|
|
365
|
+
}, KIOSK_FIELDS);
|
|
437
366
|
});
|
|
438
367
|
}
|
|
439
368
|
/**
|
package/dist/errors.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import type { ParsedError } from '@markwharton/api-core';
|
|
|
11
11
|
/**
|
|
12
12
|
* Parsed EH error response
|
|
13
13
|
*/
|
|
14
|
-
export type
|
|
14
|
+
export type EHPayrollParsedError = ParsedError;
|
|
15
15
|
/**
|
|
16
16
|
* Parse EH API error response text into a human-readable message.
|
|
17
17
|
*
|
|
@@ -19,19 +19,19 @@ export type EHParsedError = ParsedError;
|
|
|
19
19
|
* JSON error formats with no API-specific extensions.
|
|
20
20
|
*
|
|
21
21
|
* @param errorText - Raw error response text
|
|
22
|
-
* @param
|
|
22
|
+
* @param status - HTTP status code
|
|
23
23
|
* @returns Parsed error with message
|
|
24
24
|
*/
|
|
25
|
-
export declare function
|
|
25
|
+
export declare function parseEHPayrollErrorResponse(errorText: string, status: number): EHPayrollParsedError;
|
|
26
26
|
/**
|
|
27
27
|
* Custom error class for EH API errors
|
|
28
28
|
*/
|
|
29
|
-
export declare class
|
|
29
|
+
export declare class EHPayrollError extends ApiError {
|
|
30
30
|
constructor(message: string, status: number, options?: {
|
|
31
31
|
rawResponse?: string;
|
|
32
32
|
});
|
|
33
33
|
/**
|
|
34
|
-
* Create an
|
|
34
|
+
* Create an EHPayrollError from an API response
|
|
35
35
|
*/
|
|
36
|
-
static fromResponse(
|
|
36
|
+
static fromResponse(status: number, responseText: string): EHPayrollError;
|
|
37
37
|
}
|
package/dist/errors.js
CHANGED
|
@@ -14,26 +14,26 @@ import { ApiError, parseJsonErrorResponse } from '@markwharton/api-core';
|
|
|
14
14
|
* JSON error formats with no API-specific extensions.
|
|
15
15
|
*
|
|
16
16
|
* @param errorText - Raw error response text
|
|
17
|
-
* @param
|
|
17
|
+
* @param status - HTTP status code
|
|
18
18
|
* @returns Parsed error with message
|
|
19
19
|
*/
|
|
20
|
-
export function
|
|
21
|
-
return parseJsonErrorResponse(errorText,
|
|
20
|
+
export function parseEHPayrollErrorResponse(errorText, status) {
|
|
21
|
+
return parseJsonErrorResponse(errorText, status);
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* Custom error class for EH API errors
|
|
25
25
|
*/
|
|
26
|
-
export class
|
|
26
|
+
export class EHPayrollError extends ApiError {
|
|
27
27
|
constructor(message, status, options) {
|
|
28
28
|
super(message, status, options);
|
|
29
|
-
this.name = '
|
|
29
|
+
this.name = 'EHPayrollError';
|
|
30
30
|
}
|
|
31
31
|
/**
|
|
32
|
-
* Create an
|
|
32
|
+
* Create an EHPayrollError from an API response
|
|
33
33
|
*/
|
|
34
|
-
static fromResponse(
|
|
35
|
-
const parsed =
|
|
36
|
-
return new
|
|
34
|
+
static fromResponse(status, responseText) {
|
|
35
|
+
const parsed = parseEHPayrollErrorResponse(responseText, status);
|
|
36
|
+
return new EHPayrollError(parsed.message, status, {
|
|
37
37
|
rawResponse: responseText,
|
|
38
38
|
});
|
|
39
39
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -28,5 +28,5 @@ export { ok, err, getErrorMessage, pickFields, RateLimiter } from '@markwharton/
|
|
|
28
28
|
export type { Result, RetryConfig, OnRequestCallback, ClientConfig } from '@markwharton/api-core';
|
|
29
29
|
export { EH_API_BASE, EH_REGION_URLS } from './constants.js';
|
|
30
30
|
export type { EHRegion } from './constants.js';
|
|
31
|
-
export {
|
|
32
|
-
export type {
|
|
31
|
+
export { EHPayrollError, parseEHPayrollErrorResponse } from './errors.js';
|
|
32
|
+
export type { EHPayrollParsedError } from './errors.js';
|
package/dist/index.js
CHANGED
|
@@ -30,4 +30,4 @@ export { ok, err, getErrorMessage, pickFields, RateLimiter } from '@markwharton/
|
|
|
30
30
|
// Constants
|
|
31
31
|
export { EH_API_BASE, EH_REGION_URLS } from './constants.js';
|
|
32
32
|
// Errors
|
|
33
|
-
export {
|
|
33
|
+
export { EHPayrollError, parseEHPayrollErrorResponse } from './errors.js';
|