@planescrm/sdk 0.55.0 → 0.72.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.
- package/README.md +145 -3
- package/dist/client.d.mts +6016 -0
- package/dist/client.d.ts +6016 -0
- package/dist/client.js +3255 -0
- package/dist/client.js.map +1 -0
- package/dist/client.mjs +3232 -0
- package/dist/client.mjs.map +1 -0
- package/dist/index.d.mts +1787 -35
- package/dist/index.d.ts +1787 -35
- package/dist/index.js +1146 -92
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1136 -92
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -73,6 +73,7 @@ __export(index_exports, {
|
|
|
73
73
|
ESTIMATE_STATUSES: () => ESTIMATE_STATUSES,
|
|
74
74
|
EnrollmentsResource: () => EnrollmentsResource,
|
|
75
75
|
EstimateResource: () => EstimateResource,
|
|
76
|
+
EventAutomationsResource: () => EventAutomationsResource,
|
|
76
77
|
FEATURE_KEYS: () => FEATURE_KEYS,
|
|
77
78
|
GmailResource: () => GmailResource,
|
|
78
79
|
InventoryResource: () => InventoryResource,
|
|
@@ -81,6 +82,8 @@ __export(index_exports, {
|
|
|
81
82
|
InvoiceSettingsResource: () => InvoiceSettingsResource,
|
|
82
83
|
JOB_SORT_KEYS: () => JOB_SORT_KEYS,
|
|
83
84
|
JobResource: () => JobResource,
|
|
85
|
+
LeadSourcesResource: () => LeadSourcesResource,
|
|
86
|
+
ListsResource: () => ListsResource,
|
|
84
87
|
MODULE_KEYS: () => MODULE_KEYS,
|
|
85
88
|
MeResource: () => MeResource,
|
|
86
89
|
MemoryTokenStorage: () => MemoryTokenStorage,
|
|
@@ -104,13 +107,20 @@ __export(index_exports, {
|
|
|
104
107
|
RolesResource: () => RolesResource,
|
|
105
108
|
ServerError: () => ServerError,
|
|
106
109
|
ServiceSchedulesResource: () => ServiceSchedulesResource,
|
|
110
|
+
TASK_SORT_KEYS: () => TASK_SORT_KEYS,
|
|
107
111
|
TagResource: () => TagResource,
|
|
108
112
|
TaskResource: () => TaskResource,
|
|
113
|
+
TeamsResource: () => TeamsResource,
|
|
114
|
+
UsersResource: () => UsersResource,
|
|
109
115
|
ValidationError: () => ValidationError,
|
|
110
116
|
WEBHOOK_EVENT_TYPES: () => WEBHOOK_EVENT_TYPES,
|
|
117
|
+
WORKFLOW_EVENT_RULE_CATCH_UP_EVENT_TYPES: () => WORKFLOW_EVENT_RULE_CATCH_UP_EVENT_TYPES,
|
|
111
118
|
WORKFLOW_EVENT_RULE_EVENT_TYPES: () => WORKFLOW_EVENT_RULE_EVENT_TYPES,
|
|
112
119
|
WORKFLOW_EVENT_RULE_MATCH_ALL_EVENT_TYPES: () => WORKFLOW_EVENT_RULE_MATCH_ALL_EVENT_TYPES,
|
|
120
|
+
WebFormSubmissionsResource: () => WebFormSubmissionsResource,
|
|
121
|
+
WebFormsResource: () => WebFormsResource,
|
|
113
122
|
WebhookRouter: () => WebhookRouter,
|
|
123
|
+
WebhookSubscriptionsResource: () => WebhookSubscriptionsResource,
|
|
114
124
|
WebhookVerificationError: () => WebhookVerificationError,
|
|
115
125
|
WorkflowEventRulesResource: () => WorkflowEventRulesResource,
|
|
116
126
|
WorkflowsResource: () => WorkflowsResource,
|
|
@@ -292,6 +302,9 @@ var HttpClient = class {
|
|
|
292
302
|
this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
293
303
|
this.retryOnRateLimit = config.retryOnRateLimit !== false;
|
|
294
304
|
this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
305
|
+
this.getAccessToken = config.getAccessToken;
|
|
306
|
+
this.onUnauthorized = config.onUnauthorized;
|
|
307
|
+
this.fetchImpl = config.fetch;
|
|
295
308
|
if (config.clientId && config.clientSecret) {
|
|
296
309
|
this.oauth = {
|
|
297
310
|
clientId: config.clientId,
|
|
@@ -308,53 +321,68 @@ var HttpClient = class {
|
|
|
308
321
|
let lastError;
|
|
309
322
|
let didRefreshOn401 = false;
|
|
310
323
|
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
|
|
324
|
+
const authHeaders = await this.resolveAuthHeaders();
|
|
311
325
|
const headers = {
|
|
312
326
|
"Content-Type": "application/json",
|
|
313
|
-
...
|
|
327
|
+
...authHeaders,
|
|
314
328
|
...options.headers
|
|
315
329
|
};
|
|
330
|
+
if (options.idempotencyKey) headers["Idempotency-Key"] = options.idempotencyKey;
|
|
331
|
+
const timer = timeoutSignal(this.timeout);
|
|
316
332
|
const fetchOptions = {
|
|
317
333
|
method: options.method,
|
|
318
334
|
headers,
|
|
319
|
-
signal:
|
|
335
|
+
signal: timer.signal
|
|
320
336
|
};
|
|
321
337
|
if (bodyStr !== void 0) {
|
|
322
338
|
fetchOptions.body = bodyStr;
|
|
323
339
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if (response.
|
|
327
|
-
|
|
340
|
+
try {
|
|
341
|
+
const response = await (this.fetchImpl ?? fetch)(url, fetchOptions);
|
|
342
|
+
if (response.ok) {
|
|
343
|
+
if (response.status === 204) {
|
|
344
|
+
return void 0;
|
|
345
|
+
}
|
|
346
|
+
if (options.responseType === "arrayBuffer") {
|
|
347
|
+
return await response.arrayBuffer();
|
|
348
|
+
}
|
|
349
|
+
return await response.json();
|
|
328
350
|
}
|
|
329
|
-
if (
|
|
330
|
-
|
|
351
|
+
if (response.status === 429 && this.retryOnRateLimit && attempt < this.maxRetries) {
|
|
352
|
+
const retryAfter = this.parseRetryAfter(response);
|
|
353
|
+
await this.sleep(retryAfter * 1e3);
|
|
354
|
+
continue;
|
|
331
355
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
356
|
+
if (response.status === 401 && this.oauth && !didRefreshOn401) {
|
|
357
|
+
didRefreshOn401 = true;
|
|
358
|
+
await this.refreshTokens();
|
|
359
|
+
attempt--;
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
if (response.status === 401 && this.onUnauthorized && !didRefreshOn401) {
|
|
363
|
+
didRefreshOn401 = true;
|
|
364
|
+
if (await this.recoverSession(authHeaders["Authorization"])) {
|
|
365
|
+
attempt--;
|
|
366
|
+
continue;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
lastError = await this.mapError(response);
|
|
370
|
+
if (response.status !== 429 || !this.retryOnRateLimit) {
|
|
371
|
+
throw lastError;
|
|
372
|
+
}
|
|
373
|
+
} finally {
|
|
374
|
+
timer.clear();
|
|
348
375
|
}
|
|
349
376
|
}
|
|
350
377
|
throw lastError ?? new RateLimitError();
|
|
351
378
|
}
|
|
352
379
|
getAuthHeaders() {
|
|
380
|
+
if (this.getAccessToken) return {};
|
|
353
381
|
if (this.accessToken) {
|
|
354
382
|
return { Authorization: `Bearer ${this.accessToken}` };
|
|
355
383
|
}
|
|
356
384
|
if (this.apiKey) {
|
|
357
|
-
return {
|
|
385
|
+
return { Authorization: `Bearer ${this.apiKey}` };
|
|
358
386
|
}
|
|
359
387
|
return {};
|
|
360
388
|
}
|
|
@@ -365,6 +393,10 @@ var HttpClient = class {
|
|
|
365
393
|
* the token is within the skew window of expiry.
|
|
366
394
|
*/
|
|
367
395
|
async resolveAuthHeaders() {
|
|
396
|
+
if (this.getAccessToken) {
|
|
397
|
+
const token = await withTimeout(this.getAccessToken(), this.timeout, "getAccessToken");
|
|
398
|
+
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
399
|
+
}
|
|
368
400
|
if (!this.oauth) {
|
|
369
401
|
return this.getAuthHeaders();
|
|
370
402
|
}
|
|
@@ -428,6 +460,41 @@ var HttpClient = class {
|
|
|
428
460
|
this.refreshInFlight = void 0;
|
|
429
461
|
}
|
|
430
462
|
}
|
|
463
|
+
/**
|
|
464
|
+
* Ask the app to refresh its session after a 401. Resolves `true` when the
|
|
465
|
+
* request should be retried.
|
|
466
|
+
* - Requests that got a 401 while a refresh is running share that one call.
|
|
467
|
+
* - A request whose 401 arrives after a refresh already finished (it was
|
|
468
|
+
* sent with the old token) doesn't start a second refresh: if the current
|
|
469
|
+
* token differs from the one it sent, it just retries with the new one.
|
|
470
|
+
* - A refresh that throws, or doesn't settle within the request timeout,
|
|
471
|
+
* counts as "still signed out": the original 401 surfaces.
|
|
472
|
+
*/
|
|
473
|
+
async recoverSession(sentAuthorization) {
|
|
474
|
+
if (!this.onUnauthorized || !this.getAccessToken) return false;
|
|
475
|
+
if (!this.unauthorizedInFlight) {
|
|
476
|
+
try {
|
|
477
|
+
const current = await withTimeout(this.getAccessToken(), this.timeout, "getAccessToken");
|
|
478
|
+
if (current && `Bearer ${current}` !== sentAuthorization) return true;
|
|
479
|
+
} catch {
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (!this.unauthorizedInFlight) {
|
|
484
|
+
const call = this.onUnauthorized;
|
|
485
|
+
const inFlight = (async () => {
|
|
486
|
+
try {
|
|
487
|
+
return await withTimeout(call(), this.timeout, "onUnauthorized");
|
|
488
|
+
} catch {
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
})().finally(() => {
|
|
492
|
+
if (this.unauthorizedInFlight === inFlight) this.unauthorizedInFlight = void 0;
|
|
493
|
+
});
|
|
494
|
+
this.unauthorizedInFlight = inFlight;
|
|
495
|
+
}
|
|
496
|
+
return this.unauthorizedInFlight;
|
|
497
|
+
}
|
|
431
498
|
buildUrl(path, params) {
|
|
432
499
|
const url = new URL(`${this.baseUrl}${path}`);
|
|
433
500
|
if (params) {
|
|
@@ -455,7 +522,8 @@ var HttpClient = class {
|
|
|
455
522
|
body = await response.json();
|
|
456
523
|
} catch {
|
|
457
524
|
}
|
|
458
|
-
const
|
|
525
|
+
const envelope = body["error"];
|
|
526
|
+
const message = (typeof body["message"] === "string" ? body["message"] : void 0) ?? (typeof envelope?.message === "string" ? envelope.message : void 0) ?? response.statusText ?? "Request failed";
|
|
459
527
|
switch (response.status) {
|
|
460
528
|
case 400: {
|
|
461
529
|
const fieldErrors = body["errors"] ?? [];
|
|
@@ -482,6 +550,29 @@ var HttpClient = class {
|
|
|
482
550
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
483
551
|
}
|
|
484
552
|
};
|
|
553
|
+
function timeoutSignal(ms) {
|
|
554
|
+
if (typeof AbortSignal.timeout === "function") {
|
|
555
|
+
return { signal: AbortSignal.timeout(ms), clear: () => void 0 };
|
|
556
|
+
}
|
|
557
|
+
const controller = new AbortController();
|
|
558
|
+
const timer = setTimeout(() => controller.abort(), ms);
|
|
559
|
+
return { signal: controller.signal, clear: () => clearTimeout(timer) };
|
|
560
|
+
}
|
|
561
|
+
function withTimeout(promise, ms, what) {
|
|
562
|
+
return new Promise((resolve, reject) => {
|
|
563
|
+
const timer = setTimeout(() => reject(new Error(`${what} did not settle within ${ms}ms`)), ms);
|
|
564
|
+
Promise.resolve(promise).then(
|
|
565
|
+
(value) => {
|
|
566
|
+
clearTimeout(timer);
|
|
567
|
+
resolve(value);
|
|
568
|
+
},
|
|
569
|
+
(err) => {
|
|
570
|
+
clearTimeout(timer);
|
|
571
|
+
reject(err);
|
|
572
|
+
}
|
|
573
|
+
);
|
|
574
|
+
});
|
|
575
|
+
}
|
|
485
576
|
|
|
486
577
|
// src/resources/base-resource.ts
|
|
487
578
|
var ApiResource = class {
|
|
@@ -511,13 +602,16 @@ var BaseResource = class extends ApiResource {
|
|
|
511
602
|
});
|
|
512
603
|
}
|
|
513
604
|
/**
|
|
514
|
-
* Create a new entity.
|
|
605
|
+
* Create a new entity. Pass `idempotencyKey` (a UUID you keep for retries) to
|
|
606
|
+
* make a retry safe on routes that honor it (customers, opportunities, contacts,
|
|
607
|
+
* tasks): a repeat returns the first response and creates nothing.
|
|
515
608
|
*/
|
|
516
|
-
async create(data) {
|
|
609
|
+
async create(data, options) {
|
|
517
610
|
return this.http.request({
|
|
518
611
|
method: "POST",
|
|
519
612
|
path: this.basePath,
|
|
520
|
-
body: data
|
|
613
|
+
body: data,
|
|
614
|
+
...options?.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}
|
|
521
615
|
});
|
|
522
616
|
}
|
|
523
617
|
/**
|
|
@@ -547,6 +641,12 @@ function toQueryParams(params) {
|
|
|
547
641
|
if (!params) return out;
|
|
548
642
|
for (const [key, value] of Object.entries(params)) {
|
|
549
643
|
if (value === void 0 || value === null) continue;
|
|
644
|
+
if (key === "customFields" && typeof value === "object" && !Array.isArray(value)) {
|
|
645
|
+
for (const [field, v] of Object.entries(value)) {
|
|
646
|
+
if (v !== void 0 && v !== null) out[`cf.${field}`] = String(v);
|
|
647
|
+
}
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
550
650
|
if (Array.isArray(value)) {
|
|
551
651
|
if (value.length === 0) continue;
|
|
552
652
|
out[key] = value.map(String).join(",");
|
|
@@ -573,6 +673,16 @@ var OpportunityResource = class extends BaseResource {
|
|
|
573
673
|
params: toQueryParams(params)
|
|
574
674
|
});
|
|
575
675
|
}
|
|
676
|
+
/**
|
|
677
|
+
* GET /opportunities/trade-options — the active trades a client can request
|
|
678
|
+
* (`requestedTradeIds` on create/update), sorted by name.
|
|
679
|
+
*/
|
|
680
|
+
async tradeOptions() {
|
|
681
|
+
return this.http.request({
|
|
682
|
+
method: "GET",
|
|
683
|
+
path: `${this.basePath}/trade-options`
|
|
684
|
+
});
|
|
685
|
+
}
|
|
576
686
|
/** PUT /opportunities/{id} — partial update; stage moves go through workflow transitions. */
|
|
577
687
|
async update(id, data) {
|
|
578
688
|
return this.http.request({
|
|
@@ -581,6 +691,21 @@ var OpportunityResource = class extends BaseResource {
|
|
|
581
691
|
body: data
|
|
582
692
|
});
|
|
583
693
|
}
|
|
694
|
+
/** GET /opportunities/:id/transitions — the pipeline moves available from the current stage, for the caller's role. */
|
|
695
|
+
async listTransitions(id) {
|
|
696
|
+
return this.http.request({ method: "GET", path: `${this.basePath}/${id}/transitions` });
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* POST /opportunities/:id/transition — take one pipeline move, as the stage button does: required
|
|
700
|
+
* fields are checked, the move is logged, and the stage's automations run (they can send email).
|
|
701
|
+
*/
|
|
702
|
+
async transition(id, data) {
|
|
703
|
+
return this.http.request({ method: "POST", path: `${this.basePath}/${id}/transition`, body: data });
|
|
704
|
+
}
|
|
705
|
+
/** GET /opportunities/:id/workflow — the pipeline instance with its transition history (404 if none). */
|
|
706
|
+
async getWorkflow(id) {
|
|
707
|
+
return this.http.request({ method: "GET", path: `${this.basePath}/${id}/workflow` });
|
|
708
|
+
}
|
|
584
709
|
};
|
|
585
710
|
|
|
586
711
|
// src/resources/customers.ts
|
|
@@ -627,6 +752,19 @@ var CustomerResource = class extends BaseResource {
|
|
|
627
752
|
path: `/customers/${winnerId}/merge/${loserId}`
|
|
628
753
|
});
|
|
629
754
|
}
|
|
755
|
+
/**
|
|
756
|
+
* POST /customers/{id}/convert-to-company — turn a person customer into a
|
|
757
|
+
* company. The person becomes the company's primary contact (their email and
|
|
758
|
+
* phone, with do-not-contact and marketing opt-outs carried over). A customer
|
|
759
|
+
* that's already a company is a 409 `ALREADY_COMPANY`.
|
|
760
|
+
*/
|
|
761
|
+
async convertToCompany(id, data = {}) {
|
|
762
|
+
return this.http.request({
|
|
763
|
+
method: "POST",
|
|
764
|
+
path: `${this.basePath}/${id}/convert-to-company`,
|
|
765
|
+
body: data
|
|
766
|
+
});
|
|
767
|
+
}
|
|
630
768
|
};
|
|
631
769
|
|
|
632
770
|
// src/resources/properties.ts
|
|
@@ -665,6 +803,17 @@ var PropertyResource = class extends ApiResource {
|
|
|
665
803
|
body: data
|
|
666
804
|
});
|
|
667
805
|
}
|
|
806
|
+
/** GET /customers/:customerId/properties/:id — one property, checked against its customer (404 if it isn't theirs). */
|
|
807
|
+
async getForCustomer(customerId, id) {
|
|
808
|
+
return this.http.request({
|
|
809
|
+
method: "GET",
|
|
810
|
+
path: `/customers/${customerId}/properties/${id}`
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
/** POST /properties/:id/geocode — queue a fresh geocode of the property's address. */
|
|
814
|
+
async geocode(id) {
|
|
815
|
+
return this.http.request({ method: "POST", path: `${this.basePath}/${id}/geocode` });
|
|
816
|
+
}
|
|
668
817
|
/** DELETE /customers/{customerId}/properties/{id} — soft delete (204). */
|
|
669
818
|
async delete(customerId, id) {
|
|
670
819
|
await this.http.request({
|
|
@@ -680,7 +829,11 @@ var CalendarEventResource = class extends BaseResource {
|
|
|
680
829
|
super(http2, "/events");
|
|
681
830
|
}
|
|
682
831
|
async list(params) {
|
|
683
|
-
return
|
|
832
|
+
return this.http.request({
|
|
833
|
+
method: "GET",
|
|
834
|
+
path: this.basePath,
|
|
835
|
+
params: params ?? {}
|
|
836
|
+
});
|
|
684
837
|
}
|
|
685
838
|
/**
|
|
686
839
|
* PATCH /events/:id. For an occurrence of a recurring event, `editMode`
|
|
@@ -699,7 +852,8 @@ var CalendarEventResource = class extends BaseResource {
|
|
|
699
852
|
* POST /events/:id/complete — marks the event completed and runs its "on
|
|
700
853
|
* complete" transition, best-effort. The event is completed even when the
|
|
701
854
|
* transition can't run; `workflow_trigger` says whether it fired, was
|
|
702
|
-
* skipped (none set
|
|
855
|
+
* skipped (none set, or `handled_by_pipeline_rule`: the pipeline's rule
|
|
856
|
+
* moves the record instead) or failed (with a reason).
|
|
703
857
|
*/
|
|
704
858
|
async complete(id, body = {}) {
|
|
705
859
|
return this.http.request({
|
|
@@ -708,6 +862,34 @@ var CalendarEventResource = class extends BaseResource {
|
|
|
708
862
|
body
|
|
709
863
|
});
|
|
710
864
|
}
|
|
865
|
+
/**
|
|
866
|
+
* POST /events/:id/cancel. For an occurrence of a recurring event, `editMode`
|
|
867
|
+
* picks which occurrences are cancelled (`this` when omitted). 409 if already
|
|
868
|
+
* cancelled; 422 for a completed event.
|
|
869
|
+
*/
|
|
870
|
+
async cancel(id, options = {}) {
|
|
871
|
+
return this.http.request({
|
|
872
|
+
method: "POST",
|
|
873
|
+
path: `${this.basePath}/${id}/cancel`,
|
|
874
|
+
params: { recurrence_edit_mode: options.editMode }
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
/** POST /events/:id/participants — add a user, contact, crew or agent to the event. No invite is sent. */
|
|
878
|
+
async addParticipant(id, data) {
|
|
879
|
+
return this.http.request({
|
|
880
|
+
method: "POST",
|
|
881
|
+
path: `${this.basePath}/${id}/participants`,
|
|
882
|
+
body: data
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
/** PATCH /events/:id/participants/:participantId — set a participant's response. */
|
|
886
|
+
async updateParticipantStatus(id, participantId, status) {
|
|
887
|
+
return this.http.request({
|
|
888
|
+
method: "PATCH",
|
|
889
|
+
path: `${this.basePath}/${id}/participants/${participantId}`,
|
|
890
|
+
body: { status }
|
|
891
|
+
});
|
|
892
|
+
}
|
|
711
893
|
};
|
|
712
894
|
|
|
713
895
|
// src/resources/contracts.ts
|
|
@@ -715,6 +897,73 @@ var ContractResource = class extends BaseResource {
|
|
|
715
897
|
constructor(http2) {
|
|
716
898
|
super(http2, "/contracts");
|
|
717
899
|
}
|
|
900
|
+
/** POST /contracts/for-subject — a bare agreement for a customer or a service schedule (no opportunity, no line groups). */
|
|
901
|
+
async createForSubject(data) {
|
|
902
|
+
return this.http.request({ method: "POST", path: `${this.basePath}/for-subject`, body: data });
|
|
903
|
+
}
|
|
904
|
+
/** POST /contracts/flat — a contract on an opportunity with a manually entered total and a contract template, no estimates. */
|
|
905
|
+
async createFlat(data) {
|
|
906
|
+
return this.http.request({ method: "POST", path: `${this.basePath}/flat`, body: data });
|
|
907
|
+
}
|
|
908
|
+
/** POST /contracts/{id}/change-orders — a change order on a signed or fulfilled contract (409 otherwise). */
|
|
909
|
+
async createChangeOrder(id, data) {
|
|
910
|
+
return this.http.request({
|
|
911
|
+
method: "POST",
|
|
912
|
+
path: `${this.basePath}/${id}/change-orders`,
|
|
913
|
+
body: data
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* GET /contracts/{id}/change-orders — the change orders written against a
|
|
918
|
+
* contract, oldest first, each with its line groups. A change order has none
|
|
919
|
+
* of its own, so asking for one returns an empty list.
|
|
920
|
+
*/
|
|
921
|
+
async listChangeOrders(id) {
|
|
922
|
+
return this.http.request({
|
|
923
|
+
method: "GET",
|
|
924
|
+
path: `${this.basePath}/${id}/change-orders`
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
/** POST /contracts/{id}/submit — submit for approval; auto-approved when under the tenant's threshold. */
|
|
928
|
+
async submit(id) {
|
|
929
|
+
return this.http.request({ method: "POST", path: `${this.basePath}/${id}/submit` });
|
|
930
|
+
}
|
|
931
|
+
/** POST /contracts/{id}/approve — approve a contract pending approval (needs the approve grant). */
|
|
932
|
+
async approve(id) {
|
|
933
|
+
return this.http.request({ method: "POST", path: `${this.basePath}/${id}/approve` });
|
|
934
|
+
}
|
|
935
|
+
/** POST /contracts/{id}/cancel — cancel a contract in any state before it's signed. */
|
|
936
|
+
async cancel(id) {
|
|
937
|
+
return this.http.request({ method: "POST", path: `${this.basePath}/${id}/cancel` });
|
|
938
|
+
}
|
|
939
|
+
/** POST /contracts/{id}/reopen — return a kicked-back contract to draft for review and re-send. */
|
|
940
|
+
async reopen(id) {
|
|
941
|
+
return this.http.request({ method: "POST", path: `${this.basePath}/${id}/reopen` });
|
|
942
|
+
}
|
|
943
|
+
/**
|
|
944
|
+
* POST /contracts/{id}/send — **emails the signer a "Review & Sign" link.**
|
|
945
|
+
* Snapshots the clauses and creates the signing token. An empty body
|
|
946
|
+
* addresses the customer; `signerContactId` sends it to that contact instead.
|
|
947
|
+
* The first send of a contract uses one unit of the tenant's e-signature
|
|
948
|
+
* allowance; a re-send doesn't. Not safe to retry blindly after a timeout: a
|
|
949
|
+
* repeat emails again.
|
|
950
|
+
*/
|
|
951
|
+
async send(id, data = {}) {
|
|
952
|
+
return this.http.request({ method: "POST", path: `${this.basePath}/${id}/send`, body: data });
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* GET /contracts/{id}/pdf — the contract PDF bytes. `type: 'signed'` returns
|
|
956
|
+
* the stored signed copy (404 until it's signed); otherwise a preview renders.
|
|
957
|
+
*/
|
|
958
|
+
async downloadPdf(id, options = {}) {
|
|
959
|
+
return this.http.request({
|
|
960
|
+
method: "GET",
|
|
961
|
+
path: `${this.basePath}/${id}/pdf`,
|
|
962
|
+
params: options.type ? { type: options.type } : {},
|
|
963
|
+
headers: { Accept: "application/pdf" },
|
|
964
|
+
responseType: "arrayBuffer"
|
|
965
|
+
});
|
|
966
|
+
}
|
|
718
967
|
};
|
|
719
968
|
|
|
720
969
|
// src/resources/jobs.ts
|
|
@@ -784,6 +1033,28 @@ var JobResource = class extends ApiResource {
|
|
|
784
1033
|
path: "/jobs/trade-coverage"
|
|
785
1034
|
});
|
|
786
1035
|
}
|
|
1036
|
+
/** GET /jobs/{id}/workflow — the job's pipeline instance with its transition history (404 if none). */
|
|
1037
|
+
async getWorkflow(id) {
|
|
1038
|
+
return this.http.request({ method: "GET", path: `${this.basePath}/${id}/workflow` });
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* POST /jobs/{id}/transition — take one pipeline move, as the stage button
|
|
1042
|
+
* does: required fields are checked, the move is logged, and **the
|
|
1043
|
+
* transition's actions run**. A tenant can attach actions that create or send
|
|
1044
|
+
* an invoice, charge a saved card, send a contract, or send email, SMS or a
|
|
1045
|
+
* webhook. Not safe to retry blindly after a timeout: check `getWorkflow()`
|
|
1046
|
+
* first, because a repeat runs the actions again.
|
|
1047
|
+
*/
|
|
1048
|
+
async transition(id, data) {
|
|
1049
|
+
return this.http.request({ method: "POST", path: `${this.basePath}/${id}/transition`, body: data });
|
|
1050
|
+
}
|
|
1051
|
+
/** GET /jobs/{id}/transitions — the pipeline moves available from the job's current stage, for the caller's role. */
|
|
1052
|
+
async listTransitions(id) {
|
|
1053
|
+
return this.http.request({
|
|
1054
|
+
method: "GET",
|
|
1055
|
+
path: `${this.basePath}/${id}/transitions`
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
787
1058
|
};
|
|
788
1059
|
|
|
789
1060
|
// src/resources/estimates.ts
|
|
@@ -841,6 +1112,74 @@ var EstimateResource = class extends ApiResource {
|
|
|
841
1112
|
path: `/jobs/${jobId}/estimates/${id}`
|
|
842
1113
|
});
|
|
843
1114
|
}
|
|
1115
|
+
/** GET /estimates/{id}/workflow — the estimate's pipeline instance with its transition history (404 if none). */
|
|
1116
|
+
async getWorkflow(id) {
|
|
1117
|
+
return this.http.request({ method: "GET", path: `${this.basePath}/${id}/workflow` });
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* POST /estimates/{id}/transition — take one pipeline move. **The
|
|
1121
|
+
* transition's actions run**, as for `jobs.transition()`: a tenant can attach
|
|
1122
|
+
* actions that bill, charge, send a contract, or send email, SMS or a webhook.
|
|
1123
|
+
* Not safe to retry blindly after a timeout: check `getWorkflow()` first.
|
|
1124
|
+
*/
|
|
1125
|
+
async transition(id, data) {
|
|
1126
|
+
return this.http.request({ method: "POST", path: `${this.basePath}/${id}/transition`, body: data });
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* PUT /jobs/{jobId}/estimates/{id}/status — move the estimate's status (see
|
|
1130
|
+
* `UpdateEstimateStatusDto` for the allowed moves). The route itself sends
|
|
1131
|
+
* nothing and creates no contract or invoice; a tenant's webhooks or
|
|
1132
|
+
* automations on the status change can.
|
|
1133
|
+
*/
|
|
1134
|
+
async updateStatus(jobId, id, data) {
|
|
1135
|
+
return this.http.request({
|
|
1136
|
+
method: "PUT",
|
|
1137
|
+
path: `/jobs/${jobId}/estimates/${id}/status`,
|
|
1138
|
+
body: data
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
/** GET /estimates/{id}/transitions — the pipeline moves available from the estimate's current stage, for the caller's role. */
|
|
1142
|
+
async listTransitions(id) {
|
|
1143
|
+
return this.http.request({
|
|
1144
|
+
method: "GET",
|
|
1145
|
+
path: `${this.basePath}/${id}/transitions`
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* POST /jobs/{jobId}/estimates/{estimateId}/line-items — add a line item. With
|
|
1150
|
+
* `materialCatalogId` or `tradeLaborItemId`, the catalog fills in description,
|
|
1151
|
+
* unit and price. The estimate's subtotal and total are recalculated.
|
|
1152
|
+
*/
|
|
1153
|
+
async createLineItem(jobId, estimateId, data) {
|
|
1154
|
+
return this.http.request({
|
|
1155
|
+
method: "POST",
|
|
1156
|
+
path: `/jobs/${jobId}/estimates/${estimateId}/line-items`,
|
|
1157
|
+
body: data
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
/** PUT /jobs/{jobId}/estimates/{estimateId}/line-items/{id} — partial update; totals are recalculated. */
|
|
1161
|
+
async updateLineItem(jobId, estimateId, id, data) {
|
|
1162
|
+
return this.http.request({
|
|
1163
|
+
method: "PUT",
|
|
1164
|
+
path: `/jobs/${jobId}/estimates/${estimateId}/line-items/${id}`,
|
|
1165
|
+
body: data
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
/** DELETE /jobs/{jobId}/estimates/{estimateId}/line-items/{id} — totals are recalculated. */
|
|
1169
|
+
async deleteLineItem(jobId, estimateId, id) {
|
|
1170
|
+
await this.http.request({
|
|
1171
|
+
method: "DELETE",
|
|
1172
|
+
path: `/jobs/${jobId}/estimates/${estimateId}/line-items/${id}`
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1175
|
+
/** PUT /jobs/{jobId}/estimates/{estimateId}/line-items/reorder — set each item's `sortOrder` (and optionally `section`) in one call. */
|
|
1176
|
+
async reorderLineItems(jobId, estimateId, data) {
|
|
1177
|
+
return this.http.request({
|
|
1178
|
+
method: "PUT",
|
|
1179
|
+
path: `/jobs/${jobId}/estimates/${estimateId}/line-items/reorder`,
|
|
1180
|
+
body: data
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
844
1183
|
};
|
|
845
1184
|
|
|
846
1185
|
// src/resources/invoices.ts
|
|
@@ -903,6 +1242,24 @@ var InvoiceResource = class extends BaseResource {
|
|
|
903
1242
|
params
|
|
904
1243
|
});
|
|
905
1244
|
}
|
|
1245
|
+
/** GET /admin/invoices/{invoiceId}/payments — every payment recorded against the invoice (Read Payment). */
|
|
1246
|
+
async listPayments(invoiceId) {
|
|
1247
|
+
return this.http.request({
|
|
1248
|
+
method: "GET",
|
|
1249
|
+
path: `${this.basePath}/${invoiceId}/payments`
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
/**
|
|
1253
|
+
* GET /admin/payments/recording-settings — the tenant's resolved
|
|
1254
|
+
* payment-recording settings (whether manual recording is on, and which
|
|
1255
|
+
* methods). Read Invoice.
|
|
1256
|
+
*/
|
|
1257
|
+
async getPaymentRecordingSettings() {
|
|
1258
|
+
return this.http.request({
|
|
1259
|
+
method: "GET",
|
|
1260
|
+
path: "/admin/payments/recording-settings"
|
|
1261
|
+
});
|
|
1262
|
+
}
|
|
906
1263
|
/** GET /admin/invoices/overdue/summary — `{ count, total }` of every overdue invoice. Read Invoice. */
|
|
907
1264
|
async overdueSummary() {
|
|
908
1265
|
return this.http.request({
|
|
@@ -932,33 +1289,72 @@ var InventoryResource = class extends BaseResource {
|
|
|
932
1289
|
body: data
|
|
933
1290
|
});
|
|
934
1291
|
}
|
|
1292
|
+
// ---- Material used on a job (E64-S9). The routes live under /jobs/{jobId}. ----
|
|
1293
|
+
/**
|
|
1294
|
+
* POST /jobs/{jobId}/consumption — record stock drawn from a location for a
|
|
1295
|
+
* job. Each line lowers that location's on-hand quantity.
|
|
1296
|
+
*/
|
|
1297
|
+
async recordJobConsumption(jobId, data) {
|
|
1298
|
+
return this.http.request({
|
|
1299
|
+
method: "POST",
|
|
1300
|
+
path: `/jobs/${jobId}/consumption`,
|
|
1301
|
+
body: data
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* GET /jobs/{jobId}/consumption — one page of the job's consume and return
|
|
1306
|
+
* movements (newest first by default), the job's totals across every page,
|
|
1307
|
+
* and page metadata. The rows are under `items`, not `data`.
|
|
1308
|
+
*/
|
|
1309
|
+
async listJobConsumption(jobId, params) {
|
|
1310
|
+
return this.http.request({
|
|
1311
|
+
method: "GET",
|
|
1312
|
+
path: `/jobs/${jobId}/consumption`,
|
|
1313
|
+
params: toQueryParams(params)
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
/** POST /jobs/{jobId}/return — put unused stock from a job back into a location. */
|
|
1317
|
+
async returnFromJob(jobId, data) {
|
|
1318
|
+
return this.http.request({
|
|
1319
|
+
method: "POST",
|
|
1320
|
+
path: `/jobs/${jobId}/return`,
|
|
1321
|
+
body: data
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
/** GET /jobs/{jobId}/material-variance — estimated vs actual material for the job. */
|
|
1325
|
+
async getJobMaterialVariance(jobId) {
|
|
1326
|
+
return this.http.request({
|
|
1327
|
+
method: "GET",
|
|
1328
|
+
path: `/jobs/${jobId}/material-variance`
|
|
1329
|
+
});
|
|
1330
|
+
}
|
|
935
1331
|
};
|
|
936
1332
|
|
|
937
1333
|
// src/resources/contacts.ts
|
|
938
1334
|
var ContactChannelResource = class {
|
|
939
1335
|
constructor(http2, contactId) {
|
|
940
1336
|
this.http = http2;
|
|
941
|
-
this.
|
|
1337
|
+
this.contactId = contactId;
|
|
942
1338
|
}
|
|
943
1339
|
/** Returns `{ channel, duplicateOf }` — `duplicateOf` is the dedup signal. */
|
|
944
1340
|
async create(data) {
|
|
945
1341
|
return this.http.request({
|
|
946
1342
|
method: "POST",
|
|
947
|
-
path: this.
|
|
1343
|
+
path: `/contacts/${this.contactId}/channels`,
|
|
948
1344
|
body: data
|
|
949
1345
|
});
|
|
950
1346
|
}
|
|
951
1347
|
async update(channelId, data) {
|
|
952
1348
|
return this.http.request({
|
|
953
1349
|
method: "PATCH",
|
|
954
|
-
path:
|
|
1350
|
+
path: `/contacts/${this.contactId}/channels/${channelId}`,
|
|
955
1351
|
body: data
|
|
956
1352
|
});
|
|
957
1353
|
}
|
|
958
1354
|
async delete(channelId) {
|
|
959
1355
|
await this.http.request({
|
|
960
1356
|
method: "DELETE",
|
|
961
|
-
path:
|
|
1357
|
+
path: `/contacts/${this.contactId}/channels/${channelId}`
|
|
962
1358
|
});
|
|
963
1359
|
}
|
|
964
1360
|
};
|
|
@@ -986,6 +1382,46 @@ var ContactResource = class extends BaseResource {
|
|
|
986
1382
|
path: `/contacts/${winnerId}/merge/${loserId}`
|
|
987
1383
|
});
|
|
988
1384
|
}
|
|
1385
|
+
/** GET /contacts/roles — the distinct roles in use on active links, for a role picker. */
|
|
1386
|
+
async listRoles() {
|
|
1387
|
+
return this.http.request({ method: "GET", path: "/contacts/roles" });
|
|
1388
|
+
}
|
|
1389
|
+
/** GET /contacts/:id/links — every link, active and ended (history). `get()` returns active links only. */
|
|
1390
|
+
async listLinks(contactId) {
|
|
1391
|
+
return this.http.request({ method: "GET", path: `/contacts/${contactId}/links` });
|
|
1392
|
+
}
|
|
1393
|
+
/** POST /contacts/:id/links — link the contact to a customer or property. */
|
|
1394
|
+
async addLink(contactId, data) {
|
|
1395
|
+
return this.http.request({ method: "POST", path: `/contacts/${contactId}/links`, body: data });
|
|
1396
|
+
}
|
|
1397
|
+
/** PATCH /contacts/:id/links/:linkId — change role/primary, or end the link (`endedAt`). */
|
|
1398
|
+
async updateLink(contactId, linkId, data) {
|
|
1399
|
+
return this.http.request({
|
|
1400
|
+
method: "PATCH",
|
|
1401
|
+
path: `/contacts/${contactId}/links/${linkId}`,
|
|
1402
|
+
body: data
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
/** POST /contacts/:id/workflow/start — opt the contact into the relationship pipeline (409 if started). */
|
|
1406
|
+
async startWorkflow(contactId) {
|
|
1407
|
+
return this.http.request({ method: "POST", path: `/contacts/${contactId}/workflow/start` });
|
|
1408
|
+
}
|
|
1409
|
+
/** GET /contacts/:id/workflow — current stage and the transitions available from it. */
|
|
1410
|
+
async getWorkflow(contactId) {
|
|
1411
|
+
return this.http.request({ method: "GET", path: `/contacts/${contactId}/workflow` });
|
|
1412
|
+
}
|
|
1413
|
+
/** POST /contacts/:id/workflow/transitions/:transitionId — 422 for an invalid move or unmet required fields. */
|
|
1414
|
+
async transition(contactId, transitionId) {
|
|
1415
|
+
return this.http.request({
|
|
1416
|
+
method: "POST",
|
|
1417
|
+
path: `/contacts/${contactId}/workflow/transitions/${transitionId}`,
|
|
1418
|
+
body: {}
|
|
1419
|
+
});
|
|
1420
|
+
}
|
|
1421
|
+
/** GET /contacts/:id/workflow/history */
|
|
1422
|
+
async getWorkflowHistory(contactId) {
|
|
1423
|
+
return this.http.request({ method: "GET", path: `/contacts/${contactId}/workflow/history` });
|
|
1424
|
+
}
|
|
989
1425
|
channels(contactId) {
|
|
990
1426
|
return new ContactChannelResource(this.http, contactId);
|
|
991
1427
|
}
|
|
@@ -1232,6 +1668,14 @@ var TagResource = class extends ApiResource {
|
|
|
1232
1668
|
async delete(id) {
|
|
1233
1669
|
await this.http.request({ method: "DELETE", path: `${this.basePath}/${id}` });
|
|
1234
1670
|
}
|
|
1671
|
+
/**
|
|
1672
|
+
* POST /tags/:winnerId/merge/:loserId — fold a duplicate tag into another: records
|
|
1673
|
+
* move to the winner (`retagged`), duplicates are dropped (`dropped`), and the
|
|
1674
|
+
* loser is deleted. Irreversible.
|
|
1675
|
+
*/
|
|
1676
|
+
async merge(winnerId, loserId) {
|
|
1677
|
+
return this.http.request({ method: "POST", path: `${this.basePath}/${winnerId}/merge/${loserId}` });
|
|
1678
|
+
}
|
|
1235
1679
|
/** `TagsService.setEntityTags()` returns the final tag set wrapped in `{ tags }`. */
|
|
1236
1680
|
async setEntityTags(entityType, entityId, data) {
|
|
1237
1681
|
return this.http.request({
|
|
@@ -1240,6 +1684,31 @@ var TagResource = class extends ApiResource {
|
|
|
1240
1684
|
body: data
|
|
1241
1685
|
});
|
|
1242
1686
|
}
|
|
1687
|
+
/**
|
|
1688
|
+
* POST /entities/:type/:id/tags — add ONE tag, keeping the record's other tags.
|
|
1689
|
+
* Safe to call concurrently with other tag changes on the same record (unlike
|
|
1690
|
+
* read-modify-write through `setEntityTags()`). Adding a tag the record already
|
|
1691
|
+
* carries is a no-op. Returns the record's full tag set.
|
|
1692
|
+
*/
|
|
1693
|
+
async addEntityTag(entityType, entityId, ref) {
|
|
1694
|
+
const body = ref.tagId !== void 0 ? { tag_id: ref.tagId } : { tag_name: ref.tagName };
|
|
1695
|
+
return this.http.request({
|
|
1696
|
+
method: "POST",
|
|
1697
|
+
path: `/entities/${entityType}/${entityId}/tags`,
|
|
1698
|
+
body
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
/**
|
|
1702
|
+
* DELETE /entities/:type/:id/tags/:tagId — remove ONE tag, keeping the rest.
|
|
1703
|
+
* Removing a tag the record doesn't carry is a no-op. Returns the record's
|
|
1704
|
+
* full tag set.
|
|
1705
|
+
*/
|
|
1706
|
+
async removeEntityTag(entityType, entityId, tagId) {
|
|
1707
|
+
return this.http.request({
|
|
1708
|
+
method: "DELETE",
|
|
1709
|
+
path: `/entities/${entityType}/${entityId}/tags/${tagId}`
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1243
1712
|
async getEntityTags(entityType, entityId) {
|
|
1244
1713
|
return this.http.request({
|
|
1245
1714
|
method: "GET",
|
|
@@ -1255,6 +1724,111 @@ var TagResource = class extends ApiResource {
|
|
|
1255
1724
|
}
|
|
1256
1725
|
};
|
|
1257
1726
|
|
|
1727
|
+
// src/resources/lists.ts
|
|
1728
|
+
var ListsResource = class extends ApiResource {
|
|
1729
|
+
constructor(http2) {
|
|
1730
|
+
super(http2, "/lists");
|
|
1731
|
+
}
|
|
1732
|
+
/** GET /lists — every list, newest first, each with `member_count`. A bare array. */
|
|
1733
|
+
async list() {
|
|
1734
|
+
return this.http.request({ method: "GET", path: this.basePath });
|
|
1735
|
+
}
|
|
1736
|
+
/**
|
|
1737
|
+
* GET /lists/filter-fields — the standard fields, their types and the operators
|
|
1738
|
+
* each accepts for a smart list of this entity type, and whether custom fields
|
|
1739
|
+
* are accepted (E66-S3). A save that breaks these rules is a 400.
|
|
1740
|
+
*/
|
|
1741
|
+
async filterFields(entityType) {
|
|
1742
|
+
return this.http.request({
|
|
1743
|
+
method: "GET",
|
|
1744
|
+
path: `${this.basePath}/filter-fields`,
|
|
1745
|
+
params: { entity_type: entityType }
|
|
1746
|
+
});
|
|
1747
|
+
}
|
|
1748
|
+
/** GET /lists/:id — one list with an on-demand `member_count`. */
|
|
1749
|
+
async get(id) {
|
|
1750
|
+
return this.http.request({ method: "GET", path: `${this.basePath}/${id}` });
|
|
1751
|
+
}
|
|
1752
|
+
/** POST /lists */
|
|
1753
|
+
async create(data) {
|
|
1754
|
+
return this.http.request({ method: "POST", path: this.basePath, body: data });
|
|
1755
|
+
}
|
|
1756
|
+
/** PATCH /lists/:id — rename, or replace a smart list's definition. */
|
|
1757
|
+
async update(id, data) {
|
|
1758
|
+
return this.http.request({ method: "PATCH", path: `${this.basePath}/${id}`, body: data });
|
|
1759
|
+
}
|
|
1760
|
+
/** DELETE /lists/:id — soft delete; the records on the list are untouched. */
|
|
1761
|
+
async delete(id) {
|
|
1762
|
+
await this.http.request({ method: "DELETE", path: `${this.basePath}/${id}` });
|
|
1763
|
+
}
|
|
1764
|
+
/** POST /lists/:id/members — static lists only; idempotent on duplicates. */
|
|
1765
|
+
async addMembers(id, members) {
|
|
1766
|
+
return this.http.request({
|
|
1767
|
+
method: "POST",
|
|
1768
|
+
path: `${this.basePath}/${id}/members`,
|
|
1769
|
+
body: { members }
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1772
|
+
/** DELETE /lists/:id/members — static lists only. */
|
|
1773
|
+
async removeMembers(id, members) {
|
|
1774
|
+
return this.http.request({
|
|
1775
|
+
method: "DELETE",
|
|
1776
|
+
path: `${this.basePath}/${id}/members`,
|
|
1777
|
+
body: { members }
|
|
1778
|
+
});
|
|
1779
|
+
}
|
|
1780
|
+
/** GET /lists/:id/recipients — people (audience, contact and customer lists), with consent. */
|
|
1781
|
+
async getRecipients(id, params) {
|
|
1782
|
+
return this.http.request({
|
|
1783
|
+
method: "GET",
|
|
1784
|
+
path: `${this.basePath}/${id}/recipients`,
|
|
1785
|
+
params
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
/** POST /lists/:id/export — queues a CSV export; a notification links the file when it's ready. */
|
|
1789
|
+
async export(id, options) {
|
|
1790
|
+
return this.http.request({
|
|
1791
|
+
method: "POST",
|
|
1792
|
+
path: `${this.basePath}/${id}/export`,
|
|
1793
|
+
body: options ?? {}
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
};
|
|
1797
|
+
|
|
1798
|
+
// src/resources/lead-sources.ts
|
|
1799
|
+
var LeadSourcesResource = class extends ApiResource {
|
|
1800
|
+
constructor(http2) {
|
|
1801
|
+
super(http2, "/lead-sources");
|
|
1802
|
+
}
|
|
1803
|
+
/** GET /lead-sources — active ones by default, in display order. */
|
|
1804
|
+
async list(options) {
|
|
1805
|
+
const res = await this.http.request({
|
|
1806
|
+
method: "GET",
|
|
1807
|
+
path: this.basePath,
|
|
1808
|
+
...options?.includeInactive ? { params: { includeInactive: "true" } } : {}
|
|
1809
|
+
});
|
|
1810
|
+
return res.data;
|
|
1811
|
+
}
|
|
1812
|
+
/** POST /lead-sources — 409 on a duplicate key. */
|
|
1813
|
+
async create(data) {
|
|
1814
|
+
const res = await this.http.request({ method: "POST", path: this.basePath, body: data });
|
|
1815
|
+
return res.data;
|
|
1816
|
+
}
|
|
1817
|
+
/** PATCH /lead-sources/:id — label, active flag or order; the key can't change. */
|
|
1818
|
+
async update(id, data) {
|
|
1819
|
+
const res = await this.http.request({
|
|
1820
|
+
method: "PATCH",
|
|
1821
|
+
path: `${this.basePath}/${id}`,
|
|
1822
|
+
body: data
|
|
1823
|
+
});
|
|
1824
|
+
return res.data;
|
|
1825
|
+
}
|
|
1826
|
+
/** DELETE /lead-sources/:id — soft delete. */
|
|
1827
|
+
async delete(id) {
|
|
1828
|
+
await this.http.request({ method: "DELETE", path: `${this.basePath}/${id}` });
|
|
1829
|
+
}
|
|
1830
|
+
};
|
|
1831
|
+
|
|
1258
1832
|
// src/resources/tasks.ts
|
|
1259
1833
|
var TaskResource = class extends ApiResource {
|
|
1260
1834
|
constructor(http2) {
|
|
@@ -1271,9 +1845,14 @@ var TaskResource = class extends ApiResource {
|
|
|
1271
1845
|
async get(id) {
|
|
1272
1846
|
return this.http.request({ method: "GET", path: `${this.basePath}/${id}` });
|
|
1273
1847
|
}
|
|
1274
|
-
/** POST /tasks */
|
|
1275
|
-
async create(data) {
|
|
1276
|
-
return this.http.request({
|
|
1848
|
+
/** POST /tasks. Pass `idempotencyKey` to make a retry safe (E64-S6). */
|
|
1849
|
+
async create(data, options) {
|
|
1850
|
+
return this.http.request({
|
|
1851
|
+
method: "POST",
|
|
1852
|
+
path: this.basePath,
|
|
1853
|
+
body: data,
|
|
1854
|
+
...options?.idempotencyKey ? { idempotencyKey: options.idempotencyKey } : {}
|
|
1855
|
+
});
|
|
1277
1856
|
}
|
|
1278
1857
|
/** PATCH /tasks/:id — non-terminal fields only; terminal status changes use the action methods. */
|
|
1279
1858
|
async update(id, data) {
|
|
@@ -1468,70 +2047,220 @@ var InvoiceSettingsResource = class {
|
|
|
1468
2047
|
path: `${this.basePath}/overdue-offsets`,
|
|
1469
2048
|
body
|
|
1470
2049
|
});
|
|
1471
|
-
return res.offsets;
|
|
2050
|
+
return res.offsets;
|
|
2051
|
+
}
|
|
2052
|
+
/**
|
|
2053
|
+
* GET /admin/invoice-settings/attach-pdf — whether invoice emails carry the
|
|
2054
|
+
* invoice PDF (on unless an admin turned it off).
|
|
2055
|
+
*/
|
|
2056
|
+
async getAttachPdf() {
|
|
2057
|
+
const res = await this.http.request({
|
|
2058
|
+
method: "GET",
|
|
2059
|
+
path: `${this.basePath}/attach-pdf`
|
|
2060
|
+
});
|
|
2061
|
+
return res.enabled;
|
|
2062
|
+
}
|
|
2063
|
+
/**
|
|
2064
|
+
* PUT /admin/invoice-settings/attach-pdf — turn the PDF attachment on
|
|
2065
|
+
* invoice emails on or off. Returns the stored value.
|
|
2066
|
+
*/
|
|
2067
|
+
async setAttachPdf(enabled) {
|
|
2068
|
+
const body = { enabled };
|
|
2069
|
+
const res = await this.http.request({
|
|
2070
|
+
method: "PUT",
|
|
2071
|
+
path: `${this.basePath}/attach-pdf`,
|
|
2072
|
+
body
|
|
2073
|
+
});
|
|
2074
|
+
return res.enabled;
|
|
2075
|
+
}
|
|
2076
|
+
};
|
|
2077
|
+
|
|
2078
|
+
// src/resources/invoice-reminders.ts
|
|
2079
|
+
var InvoiceRemindersResource = class {
|
|
2080
|
+
constructor(http2) {
|
|
2081
|
+
this.http = http2;
|
|
2082
|
+
this.basePath = "/admin/invoice-reminders";
|
|
2083
|
+
}
|
|
2084
|
+
/** GET /admin/invoice-reminders — the rules, ascending by offset, and the overdue offsets. */
|
|
2085
|
+
async list() {
|
|
2086
|
+
return this.http.request({
|
|
2087
|
+
method: "GET",
|
|
2088
|
+
path: this.basePath
|
|
2089
|
+
});
|
|
2090
|
+
}
|
|
2091
|
+
/**
|
|
2092
|
+
* PUT /admin/invoice-reminders — replace the WHOLE rule set (a rule left out
|
|
2093
|
+
* is deleted): up to 5 rules at unique offsets. Templates may use only
|
|
2094
|
+
* `{{customerName}}`, `{{invoiceNumber}}`, `{{amountDue}}`, `{{total}}`,
|
|
2095
|
+
* `{{dueDate}}`, `{{daysOverdue}}` and `{{companyName}}`; any other is a 422
|
|
2096
|
+
* `REMINDER_TEMPLATE_INVALID` listing each one. Returns the stored set.
|
|
2097
|
+
*/
|
|
2098
|
+
async update(rules) {
|
|
2099
|
+
return this.http.request({
|
|
2100
|
+
method: "PUT",
|
|
2101
|
+
path: this.basePath,
|
|
2102
|
+
body: { rules }
|
|
2103
|
+
});
|
|
2104
|
+
}
|
|
2105
|
+
/**
|
|
2106
|
+
* POST /admin/invoice-reminders/preview — render a subject and body exactly
|
|
2107
|
+
* as the reminder email would, against an invoice or a sample. Sends nothing.
|
|
2108
|
+
*/
|
|
2109
|
+
async preview(params) {
|
|
2110
|
+
return this.http.request({
|
|
2111
|
+
method: "POST",
|
|
2112
|
+
path: `${this.basePath}/preview`,
|
|
2113
|
+
body: params
|
|
2114
|
+
});
|
|
2115
|
+
}
|
|
2116
|
+
};
|
|
2117
|
+
|
|
2118
|
+
// src/resources/event-automations.ts
|
|
2119
|
+
var EventAutomationsResource = class {
|
|
2120
|
+
constructor(http2) {
|
|
2121
|
+
this.http = http2;
|
|
2122
|
+
}
|
|
2123
|
+
// Each request spells its path out in full (no computed base path) so the
|
|
2124
|
+
// API's SDK route-parity audit (sdk-route-parity-audit.spec.ts) can read it.
|
|
2125
|
+
/** GET — enabled and disabled automations, oldest first, optionally by event type or owner. */
|
|
2126
|
+
async list(params = {}) {
|
|
2127
|
+
const query = {};
|
|
2128
|
+
if (params.owner_tag) query["owner_tag"] = params.owner_tag;
|
|
2129
|
+
if (params.event_type) query["event_type"] = params.event_type;
|
|
2130
|
+
const res = await this.http.request({
|
|
2131
|
+
method: "GET",
|
|
2132
|
+
path: "/admin/event-automations",
|
|
2133
|
+
params: query
|
|
2134
|
+
});
|
|
2135
|
+
return res.data;
|
|
2136
|
+
}
|
|
2137
|
+
/** GET /:id — 404 when it doesn't exist or was deleted. */
|
|
2138
|
+
async get(id) {
|
|
2139
|
+
return this.http.request({
|
|
2140
|
+
method: "GET",
|
|
2141
|
+
path: `/admin/event-automations/${encodeURIComponent(id)}`
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
/**
|
|
2145
|
+
* POST — create (201). 400 `VALIDATION_FAILED` for a bad body, or a 400
|
|
2146
|
+
* naming the step whose `action_type` isn't allowed in automations.
|
|
2147
|
+
*/
|
|
2148
|
+
async create(input) {
|
|
2149
|
+
return this.http.request({
|
|
2150
|
+
method: "POST",
|
|
2151
|
+
path: "/admin/event-automations",
|
|
2152
|
+
body: input
|
|
2153
|
+
});
|
|
1472
2154
|
}
|
|
1473
2155
|
/**
|
|
1474
|
-
*
|
|
1475
|
-
*
|
|
2156
|
+
* PATCH /:id — only the fields sent change. Sending `actions` replaces the
|
|
2157
|
+
* list: keep each existing step's `id` so a step that already ran isn't run
|
|
2158
|
+
* again. `{ enabled: false }` switches it off.
|
|
1476
2159
|
*/
|
|
1477
|
-
async
|
|
1478
|
-
|
|
1479
|
-
method: "
|
|
1480
|
-
path:
|
|
2160
|
+
async update(id, input) {
|
|
2161
|
+
return this.http.request({
|
|
2162
|
+
method: "PATCH",
|
|
2163
|
+
path: `/admin/event-automations/${encodeURIComponent(id)}`,
|
|
2164
|
+
body: input
|
|
1481
2165
|
});
|
|
1482
|
-
return res.enabled;
|
|
1483
2166
|
}
|
|
1484
|
-
/**
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
const body = { enabled };
|
|
1490
|
-
const res = await this.http.request({
|
|
1491
|
-
method: "PUT",
|
|
1492
|
-
path: `${this.basePath}/attach-pdf`,
|
|
1493
|
-
body
|
|
2167
|
+
/** DELETE /:id — soft delete; returns `{ id }` (200). */
|
|
2168
|
+
async delete(id) {
|
|
2169
|
+
return this.http.request({
|
|
2170
|
+
method: "DELETE",
|
|
2171
|
+
path: `/admin/event-automations/${encodeURIComponent(id)}`
|
|
1494
2172
|
});
|
|
1495
|
-
return res.enabled;
|
|
1496
2173
|
}
|
|
1497
2174
|
};
|
|
1498
2175
|
|
|
1499
|
-
// src/resources/
|
|
1500
|
-
var
|
|
2176
|
+
// src/resources/web-forms.ts
|
|
2177
|
+
var WebFormsResource = class {
|
|
1501
2178
|
constructor(http2) {
|
|
1502
2179
|
this.http = http2;
|
|
1503
|
-
this.
|
|
2180
|
+
this.submissions = new WebFormSubmissionsResource(http2);
|
|
1504
2181
|
}
|
|
1505
|
-
|
|
2182
|
+
// Each request spells its path out in full so the API's SDK route-parity
|
|
2183
|
+
// audit (sdk-route-parity-audit.spec.ts) can read it.
|
|
2184
|
+
/** GET — every form not archived, newest first. */
|
|
1506
2185
|
async list() {
|
|
2186
|
+
const res = await this.http.request({ method: "GET", path: "/admin/web-forms" });
|
|
2187
|
+
return res.data;
|
|
2188
|
+
}
|
|
2189
|
+
/** GET /trade-options — the active trades a `trade` question offers, by current name. */
|
|
2190
|
+
async tradeOptions() {
|
|
2191
|
+
const res = await this.http.request({ method: "GET", path: "/admin/web-forms/trade-options" });
|
|
2192
|
+
return res.data;
|
|
2193
|
+
}
|
|
2194
|
+
/** GET /:id */
|
|
2195
|
+
async get(id) {
|
|
2196
|
+
return this.http.request({ method: "GET", path: `/admin/web-forms/${encodeURIComponent(id)}` });
|
|
2197
|
+
}
|
|
2198
|
+
/** POST — create (201) with its default routing. Pass `recipient_agent_ids`. */
|
|
2199
|
+
async create(input) {
|
|
2200
|
+
return this.http.request({ method: "POST", path: "/admin/web-forms", body: input });
|
|
2201
|
+
}
|
|
2202
|
+
/** PATCH /:id — only the fields sent change. `active: false` switches the form off. */
|
|
2203
|
+
async update(id, input) {
|
|
1507
2204
|
return this.http.request({
|
|
1508
|
-
method: "
|
|
1509
|
-
path:
|
|
2205
|
+
method: "PATCH",
|
|
2206
|
+
path: `/admin/web-forms/${encodeURIComponent(id)}`,
|
|
2207
|
+
body: input
|
|
1510
2208
|
});
|
|
1511
2209
|
}
|
|
1512
|
-
/**
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
*/
|
|
1519
|
-
async update(rules) {
|
|
2210
|
+
/** DELETE /:id — archive: the public URL stops working and routing stops. Returns `{ id }`. */
|
|
2211
|
+
async archive(id) {
|
|
2212
|
+
return this.http.request({ method: "DELETE", path: `/admin/web-forms/${encodeURIComponent(id)}` });
|
|
2213
|
+
}
|
|
2214
|
+
/** POST /:id/rotate-key — a new public key; the old embed stops working at once. */
|
|
2215
|
+
async rotateKey(id) {
|
|
1520
2216
|
return this.http.request({
|
|
1521
|
-
method: "
|
|
1522
|
-
path:
|
|
1523
|
-
body: { rules }
|
|
2217
|
+
method: "POST",
|
|
2218
|
+
path: `/admin/web-forms/${encodeURIComponent(id)}/rotate-key`
|
|
1524
2219
|
});
|
|
1525
2220
|
}
|
|
2221
|
+
};
|
|
2222
|
+
var WebFormSubmissionsResource = class {
|
|
2223
|
+
constructor(http2) {
|
|
2224
|
+
this.http = http2;
|
|
2225
|
+
}
|
|
2226
|
+
/** GET /submissions — the first page, newest first, with routing state. Use `listPage` to read on. */
|
|
2227
|
+
async list(params = {}) {
|
|
2228
|
+
return (await this.listPage(params)).data;
|
|
2229
|
+
}
|
|
1526
2230
|
/**
|
|
1527
|
-
*
|
|
1528
|
-
*
|
|
2231
|
+
* GET /submissions — one page plus `next`, which is null on the last page.
|
|
2232
|
+
* Pass `next`'s `before` and `before_id` back in to read the following page.
|
|
1529
2233
|
*/
|
|
1530
|
-
async
|
|
2234
|
+
async listPage(params = {}) {
|
|
2235
|
+
const query = {};
|
|
2236
|
+
for (const [k, v] of Object.entries(params)) if (v !== void 0) query[k] = v;
|
|
2237
|
+
const res = await this.http.request({
|
|
2238
|
+
method: "GET",
|
|
2239
|
+
path: "/admin/web-forms/submissions",
|
|
2240
|
+
params: query
|
|
2241
|
+
});
|
|
2242
|
+
return { data: res.data, next: res.next ?? null };
|
|
2243
|
+
}
|
|
2244
|
+
/** GET /submissions/:id — answers as seen, plus each routing step. */
|
|
2245
|
+
async get(id) {
|
|
2246
|
+
return this.http.request({
|
|
2247
|
+
method: "GET",
|
|
2248
|
+
path: `/admin/web-forms/submissions/${encodeURIComponent(id)}`
|
|
2249
|
+
});
|
|
2250
|
+
}
|
|
2251
|
+
/** PATCH /submissions/:id — set triage status. */
|
|
2252
|
+
async update(id, input) {
|
|
2253
|
+
return this.http.request({
|
|
2254
|
+
method: "PATCH",
|
|
2255
|
+
path: `/admin/web-forms/submissions/${encodeURIComponent(id)}`,
|
|
2256
|
+
body: input
|
|
2257
|
+
});
|
|
2258
|
+
}
|
|
2259
|
+
/** POST /submissions/:id/route — retry failed steps, run never-run ones (not email, text or webhook: `skipped`); safe to repeat. */
|
|
2260
|
+
async route(id) {
|
|
1531
2261
|
return this.http.request({
|
|
1532
2262
|
method: "POST",
|
|
1533
|
-
path:
|
|
1534
|
-
body: params
|
|
2263
|
+
path: `/admin/web-forms/submissions/${encodeURIComponent(id)}/route`
|
|
1535
2264
|
});
|
|
1536
2265
|
}
|
|
1537
2266
|
};
|
|
@@ -1703,23 +2432,119 @@ var WorkflowsResource = class {
|
|
|
1703
2432
|
var ServiceSchedulesResource = class {
|
|
1704
2433
|
constructor(http2) {
|
|
1705
2434
|
this.http = http2;
|
|
1706
|
-
|
|
2435
|
+
}
|
|
2436
|
+
// Each request spells its path out in full (no computed base path) so the
|
|
2437
|
+
// API's SDK route-parity audit (sdk-route-parity-audit.spec.ts) can read it.
|
|
2438
|
+
/** GET — the schedules of one customer or opportunity. */
|
|
2439
|
+
async list(subject_type, subject_id) {
|
|
2440
|
+
return this.http.request({
|
|
2441
|
+
method: "GET",
|
|
2442
|
+
path: "/service-schedules",
|
|
2443
|
+
params: { subject_type, subject_id }
|
|
2444
|
+
});
|
|
2445
|
+
}
|
|
2446
|
+
/** GET /:id — 404 when it doesn't exist or was deleted. */
|
|
2447
|
+
async get(id) {
|
|
2448
|
+
return this.http.request({
|
|
2449
|
+
method: "GET",
|
|
2450
|
+
path: `/service-schedules/${encodeURIComponent(id)}`
|
|
2451
|
+
});
|
|
2452
|
+
}
|
|
2453
|
+
/**
|
|
2454
|
+
* POST — create (201). The horizon's visits are generated straight away.
|
|
2455
|
+
* 400 `INVALID_RECURRENCE_RULE`, `SERVICE_TRADE_REQUIRED` or a validation error.
|
|
2456
|
+
*/
|
|
2457
|
+
async create(input) {
|
|
2458
|
+
return this.http.request({
|
|
2459
|
+
method: "POST",
|
|
2460
|
+
path: "/service-schedules",
|
|
2461
|
+
body: input
|
|
2462
|
+
});
|
|
2463
|
+
}
|
|
2464
|
+
/**
|
|
2465
|
+
* PATCH /:id — only the fields sent change. A cadence, time, crew, duration
|
|
2466
|
+
* or horizon change regenerates future visits from today (`effective_from`,
|
|
2467
|
+
* which must be today) and reports it in `regeneration`; a visit someone changed is never
|
|
2468
|
+
* touched and is listed under `regeneration.protected`. The edit and its
|
|
2469
|
+
* regeneration succeed or fail together. 400 `INVALID_EFFECTIVE_FROM` for any
|
|
2470
|
+
* date but today.
|
|
2471
|
+
*/
|
|
2472
|
+
async update(id, input) {
|
|
2473
|
+
return this.http.request({
|
|
2474
|
+
method: "PATCH",
|
|
2475
|
+
path: `/service-schedules/${encodeURIComponent(id)}`,
|
|
2476
|
+
body: input
|
|
2477
|
+
});
|
|
2478
|
+
}
|
|
2479
|
+
/** POST /:id/preview-update — what `update` with the same input would do. Writes nothing. */
|
|
2480
|
+
async previewUpdate(id, input) {
|
|
2481
|
+
return this.http.request({
|
|
2482
|
+
method: "POST",
|
|
2483
|
+
path: `/service-schedules/${encodeURIComponent(id)}/preview-update`,
|
|
2484
|
+
body: input
|
|
2485
|
+
});
|
|
2486
|
+
}
|
|
2487
|
+
/** PATCH /:id/status — pause, resume (`active`) or end. Resume generates the horizon at once. */
|
|
2488
|
+
async setStatus(id, status) {
|
|
2489
|
+
return this.http.request({
|
|
2490
|
+
method: "PATCH",
|
|
2491
|
+
path: `/service-schedules/${encodeURIComponent(id)}/status`,
|
|
2492
|
+
body: { status }
|
|
2493
|
+
});
|
|
2494
|
+
}
|
|
2495
|
+
/** DELETE /:id — soft-delete (204). */
|
|
2496
|
+
async delete(id) {
|
|
2497
|
+
await this.http.request({
|
|
2498
|
+
method: "DELETE",
|
|
2499
|
+
path: `/service-schedules/${encodeURIComponent(id)}`
|
|
2500
|
+
});
|
|
1707
2501
|
}
|
|
1708
2502
|
/**
|
|
1709
|
-
* POST /
|
|
1710
|
-
*
|
|
1711
|
-
*
|
|
1712
|
-
*
|
|
1713
|
-
*
|
|
1714
|
-
*
|
|
2503
|
+
* POST /conflicts/preview — before booking people, see who is already on
|
|
2504
|
+
* another event (`conflicts`) and who would be outside their working hours
|
|
2505
|
+
* (`working_hours`). Read-only and warn-only: nothing is ever blocked. Give
|
|
2506
|
+
* exactly one of `windows` (single booking) or `schedule` (a cadence). 400 on
|
|
2507
|
+
* invalid input, INVALID_RECURRENCE_RULE, INVALID_TIMEZONE or
|
|
2508
|
+
* TOO_MANY_WINDOWS (> 400).
|
|
1715
2509
|
*/
|
|
1716
2510
|
async previewConflicts(request) {
|
|
1717
2511
|
return this.http.request({
|
|
1718
2512
|
method: "POST",
|
|
1719
|
-
path:
|
|
2513
|
+
path: "/service-schedules/conflicts/preview",
|
|
1720
2514
|
body: request
|
|
1721
2515
|
});
|
|
1722
2516
|
}
|
|
2517
|
+
/** GET /settings/crew-roles — which agent roles count as assignable crew. */
|
|
2518
|
+
async getCrewRoles() {
|
|
2519
|
+
return this.http.request({
|
|
2520
|
+
method: "GET",
|
|
2521
|
+
path: "/service-schedules/settings/crew-roles"
|
|
2522
|
+
});
|
|
2523
|
+
}
|
|
2524
|
+
/** PUT /settings/crew-roles — set the assignable roles (an override of the default). */
|
|
2525
|
+
async setCrewRoles(roles) {
|
|
2526
|
+
return this.http.request({
|
|
2527
|
+
method: "PUT",
|
|
2528
|
+
path: "/service-schedules/settings/crew-roles",
|
|
2529
|
+
body: { roles }
|
|
2530
|
+
});
|
|
2531
|
+
}
|
|
2532
|
+
/** GET /settings/crew — assignable crew with their home-base status. */
|
|
2533
|
+
async listCrew() {
|
|
2534
|
+
const res = await this.http.request({
|
|
2535
|
+
method: "GET",
|
|
2536
|
+
path: "/service-schedules/settings/crew"
|
|
2537
|
+
});
|
|
2538
|
+
return res.crew;
|
|
2539
|
+
}
|
|
2540
|
+
/** PUT /settings/crew/:agentId/home-address — set and geocode a crew member's home base. */
|
|
2541
|
+
async setCrewHomeAddress(agentId, address) {
|
|
2542
|
+
return this.http.request({
|
|
2543
|
+
method: "PUT",
|
|
2544
|
+
path: `/service-schedules/settings/crew/${encodeURIComponent(agentId)}/home-address`,
|
|
2545
|
+
body: { address }
|
|
2546
|
+
});
|
|
2547
|
+
}
|
|
1723
2548
|
};
|
|
1724
2549
|
|
|
1725
2550
|
// src/resources/billing-schedules.ts
|
|
@@ -1924,6 +2749,52 @@ var CustomFieldsResource = class {
|
|
|
1924
2749
|
params
|
|
1925
2750
|
});
|
|
1926
2751
|
}
|
|
2752
|
+
/** GET /admin/custom-fields — the same list through the admin route (Manage grant). */
|
|
2753
|
+
async listAll(params) {
|
|
2754
|
+
return this.http.request({
|
|
2755
|
+
method: "GET",
|
|
2756
|
+
path: "/admin/custom-fields",
|
|
2757
|
+
params
|
|
2758
|
+
});
|
|
2759
|
+
}
|
|
2760
|
+
/** GET /admin/custom-fields/:id */
|
|
2761
|
+
async get(id) {
|
|
2762
|
+
return this.http.request({
|
|
2763
|
+
method: "GET",
|
|
2764
|
+
path: `/admin/custom-fields/${id}`
|
|
2765
|
+
});
|
|
2766
|
+
}
|
|
2767
|
+
/**
|
|
2768
|
+
* POST /admin/custom-fields — define a new field. 400 for `job`, `vendor` or
|
|
2769
|
+
* `agent` (those records can't hold values yet, PRD D6). A `fieldKey` must be
|
|
2770
|
+
* unique per entity type: check `listAll({ entity_type, include_inactive: true })` first.
|
|
2771
|
+
*/
|
|
2772
|
+
async create(data) {
|
|
2773
|
+
return this.http.request({
|
|
2774
|
+
method: "POST",
|
|
2775
|
+
path: "/admin/custom-fields",
|
|
2776
|
+
body: data
|
|
2777
|
+
});
|
|
2778
|
+
}
|
|
2779
|
+
/** PATCH /admin/custom-fields/:id — `fieldKey` and `entityType` can't change. */
|
|
2780
|
+
async update(id, data) {
|
|
2781
|
+
return this.http.request({
|
|
2782
|
+
method: "PATCH",
|
|
2783
|
+
path: `/admin/custom-fields/${id}`,
|
|
2784
|
+
body: data
|
|
2785
|
+
});
|
|
2786
|
+
}
|
|
2787
|
+
/**
|
|
2788
|
+
* DELETE /admin/custom-fields/:id — deactivates the definition (204). Records
|
|
2789
|
+
* keep their stored values; `list({ include_inactive: true })` still shows it,
|
|
2790
|
+
* and `update(id, { active: true })` brings it back.
|
|
2791
|
+
*/
|
|
2792
|
+
async deactivate(id) {
|
|
2793
|
+
await this.http.request({
|
|
2794
|
+
method: "DELETE",
|
|
2795
|
+
path: `/admin/custom-fields/${id}`
|
|
2796
|
+
});
|
|
2797
|
+
}
|
|
1927
2798
|
};
|
|
1928
2799
|
|
|
1929
2800
|
// src/resources/ortto.ts
|
|
@@ -1999,6 +2870,121 @@ var OrttoResource = class {
|
|
|
1999
2870
|
}
|
|
2000
2871
|
};
|
|
2001
2872
|
|
|
2873
|
+
// src/resources/webhook-subscriptions.ts
|
|
2874
|
+
var WebhookSubscriptionsResource = class extends ApiResource {
|
|
2875
|
+
constructor(http2) {
|
|
2876
|
+
super(http2, "/admin/webhooks");
|
|
2877
|
+
}
|
|
2878
|
+
/** POST /admin/webhooks — the returned `secret` is shown only this once. */
|
|
2879
|
+
async create(data) {
|
|
2880
|
+
return this.http.request({ method: "POST", path: this.basePath, body: data });
|
|
2881
|
+
}
|
|
2882
|
+
/** GET /admin/webhooks */
|
|
2883
|
+
async list(params) {
|
|
2884
|
+
return this.http.request({
|
|
2885
|
+
method: "GET",
|
|
2886
|
+
path: this.basePath,
|
|
2887
|
+
params
|
|
2888
|
+
});
|
|
2889
|
+
}
|
|
2890
|
+
/** GET /admin/webhooks/:id */
|
|
2891
|
+
async get(id) {
|
|
2892
|
+
return this.http.request({ method: "GET", path: `${this.basePath}/${id}` });
|
|
2893
|
+
}
|
|
2894
|
+
/** PATCH /admin/webhooks/:id */
|
|
2895
|
+
async update(id, data) {
|
|
2896
|
+
return this.http.request({ method: "PATCH", path: `${this.basePath}/${id}`, body: data });
|
|
2897
|
+
}
|
|
2898
|
+
/** DELETE /admin/webhooks/:id */
|
|
2899
|
+
async delete(id) {
|
|
2900
|
+
return this.http.request({ method: "DELETE", path: `${this.basePath}/${id}` });
|
|
2901
|
+
}
|
|
2902
|
+
/** POST /admin/webhooks/:id/test — sends one signed test event to the URL now. */
|
|
2903
|
+
async test(id) {
|
|
2904
|
+
return this.http.request({ method: "POST", path: `${this.basePath}/${id}/test` });
|
|
2905
|
+
}
|
|
2906
|
+
/**
|
|
2907
|
+
* POST /admin/webhooks/:id/rotate-secret — replaces the signing secret and
|
|
2908
|
+
* returns the new one, shown only this once. New deliveries are signed with
|
|
2909
|
+
* it, but deliveries already queued (and their retries, for several minutes)
|
|
2910
|
+
* keep the old one. `verifyWebhookSignature` checks one secret, so for a few
|
|
2911
|
+
* minutes have your receiver try the new secret and fall back to the old one.
|
|
2912
|
+
*/
|
|
2913
|
+
async rotateSecret(id) {
|
|
2914
|
+
return this.http.request({
|
|
2915
|
+
method: "POST",
|
|
2916
|
+
path: `${this.basePath}/${id}/rotate-secret`
|
|
2917
|
+
});
|
|
2918
|
+
}
|
|
2919
|
+
/** GET /admin/webhooks/:id/deliveries — newest first. */
|
|
2920
|
+
async listDeliveries(id, params) {
|
|
2921
|
+
return this.http.request({
|
|
2922
|
+
method: "GET",
|
|
2923
|
+
path: `${this.basePath}/${id}/deliveries`,
|
|
2924
|
+
params
|
|
2925
|
+
});
|
|
2926
|
+
}
|
|
2927
|
+
/**
|
|
2928
|
+
* POST /admin/webhooks/:id/deliveries/:deliveryId/retry — queues a new
|
|
2929
|
+
* delivery of that event (202). The receiver sees the event again, so make
|
|
2930
|
+
* it idempotent on the event id.
|
|
2931
|
+
*/
|
|
2932
|
+
async retryDelivery(id, deliveryId) {
|
|
2933
|
+
return this.http.request({
|
|
2934
|
+
method: "POST",
|
|
2935
|
+
path: `${this.basePath}/${id}/deliveries/${deliveryId}/retry`
|
|
2936
|
+
});
|
|
2937
|
+
}
|
|
2938
|
+
};
|
|
2939
|
+
|
|
2940
|
+
// src/resources/users.ts
|
|
2941
|
+
var UsersResource = class extends ApiResource {
|
|
2942
|
+
constructor(http2) {
|
|
2943
|
+
super(http2, "/users");
|
|
2944
|
+
}
|
|
2945
|
+
/** GET /users — paged, with email, role, status and org fields (read grant on users). */
|
|
2946
|
+
async list(params) {
|
|
2947
|
+
return this.http.request({
|
|
2948
|
+
method: "GET",
|
|
2949
|
+
path: this.basePath,
|
|
2950
|
+
params
|
|
2951
|
+
});
|
|
2952
|
+
}
|
|
2953
|
+
/**
|
|
2954
|
+
* GET /users/directory — active users' ids, names and roles, for pickers and
|
|
2955
|
+
* for resolving an assignee. Every role can read it.
|
|
2956
|
+
*/
|
|
2957
|
+
async directory(params) {
|
|
2958
|
+
return this.http.request({
|
|
2959
|
+
method: "GET",
|
|
2960
|
+
path: `${this.basePath}/directory`,
|
|
2961
|
+
params
|
|
2962
|
+
});
|
|
2963
|
+
}
|
|
2964
|
+
/**
|
|
2965
|
+
* GET /users/:id/working-hours — the user's own weekly hours and the tenant
|
|
2966
|
+
* default. A non-admin caller may read only their own (403 otherwise).
|
|
2967
|
+
*/
|
|
2968
|
+
async getWorkingHours(id) {
|
|
2969
|
+
return this.http.request({ method: "GET", path: `${this.basePath}/${id}/working-hours` });
|
|
2970
|
+
}
|
|
2971
|
+
};
|
|
2972
|
+
|
|
2973
|
+
// src/resources/teams.ts
|
|
2974
|
+
var TeamsResource = class extends ApiResource {
|
|
2975
|
+
constructor(http2) {
|
|
2976
|
+
super(http2, "/teams");
|
|
2977
|
+
}
|
|
2978
|
+
/** GET /teams — every team, with `member_count`. */
|
|
2979
|
+
async list() {
|
|
2980
|
+
return this.http.request({ method: "GET", path: this.basePath });
|
|
2981
|
+
}
|
|
2982
|
+
/** GET /teams/:id — one team, with `members`. */
|
|
2983
|
+
async get(id) {
|
|
2984
|
+
return this.http.request({ method: "GET", path: `${this.basePath}/${id}` });
|
|
2985
|
+
}
|
|
2986
|
+
};
|
|
2987
|
+
|
|
2002
2988
|
// src/webhooks.ts
|
|
2003
2989
|
var import_node_crypto2 = require("crypto");
|
|
2004
2990
|
var WEBHOOK_EVENT_TYPES = [
|
|
@@ -2034,7 +3020,8 @@ var WEBHOOK_EVENT_TYPES = [
|
|
|
2034
3020
|
"integration_connection.deactivated",
|
|
2035
3021
|
"customer_payment_method.invalidated",
|
|
2036
3022
|
"invoice.autopay_succeeded",
|
|
2037
|
-
"invoice.autopay_failed"
|
|
3023
|
+
"invoice.autopay_failed",
|
|
3024
|
+
"received"
|
|
2038
3025
|
];
|
|
2039
3026
|
function verifyWebhookSignature(payload, signature, secret) {
|
|
2040
3027
|
if (!signature || !secret || !payload) {
|
|
@@ -2236,6 +3223,18 @@ var PlanesClient = class {
|
|
|
2236
3223
|
}
|
|
2237
3224
|
return this._tags;
|
|
2238
3225
|
}
|
|
3226
|
+
get lists() {
|
|
3227
|
+
if (!this._lists) {
|
|
3228
|
+
this._lists = new ListsResource(this.http);
|
|
3229
|
+
}
|
|
3230
|
+
return this._lists;
|
|
3231
|
+
}
|
|
3232
|
+
get leadSources() {
|
|
3233
|
+
if (!this._leadSources) {
|
|
3234
|
+
this._leadSources = new LeadSourcesResource(this.http);
|
|
3235
|
+
}
|
|
3236
|
+
return this._leadSources;
|
|
3237
|
+
}
|
|
2239
3238
|
get tasks() {
|
|
2240
3239
|
if (!this._tasks) {
|
|
2241
3240
|
this._tasks = new TaskResource(this.http);
|
|
@@ -2291,6 +3290,20 @@ var PlanesClient = class {
|
|
|
2291
3290
|
}
|
|
2292
3291
|
return this._invoiceReminders;
|
|
2293
3292
|
}
|
|
3293
|
+
/** Event automations, admin only — `eventAutomations.list/get/create/update/delete` (E60-S2). */
|
|
3294
|
+
get eventAutomations() {
|
|
3295
|
+
if (!this._eventAutomations) {
|
|
3296
|
+
this._eventAutomations = new EventAutomationsResource(this.http);
|
|
3297
|
+
}
|
|
3298
|
+
return this._eventAutomations;
|
|
3299
|
+
}
|
|
3300
|
+
/** Web forms and their submissions, admin only — `webForms.list/get/create/update/archive/rotateKey`, `webForms.submissions.list/get/update/route` (E60-S6). */
|
|
3301
|
+
get webForms() {
|
|
3302
|
+
if (!this._webForms) {
|
|
3303
|
+
this._webForms = new WebFormsResource(this.http);
|
|
3304
|
+
}
|
|
3305
|
+
return this._webForms;
|
|
3306
|
+
}
|
|
2294
3307
|
/** Published sequences a contact can be enrolled in (E51-F2-S6). */
|
|
2295
3308
|
get contactSequences() {
|
|
2296
3309
|
if (!this._contactSequences) {
|
|
@@ -2341,6 +3354,30 @@ var PlanesClient = class {
|
|
|
2341
3354
|
}
|
|
2342
3355
|
return this._ortto;
|
|
2343
3356
|
}
|
|
3357
|
+
/**
|
|
3358
|
+
* Webhook subscriptions — where entity events are delivered (E64-S10). Admin
|
|
3359
|
+
* grants and the `open_api` feature. Not `webhooks`: that is the event router.
|
|
3360
|
+
*/
|
|
3361
|
+
get webhookSubscriptions() {
|
|
3362
|
+
if (!this._webhookSubscriptions) {
|
|
3363
|
+
this._webhookSubscriptions = new WebhookSubscriptionsResource(this.http);
|
|
3364
|
+
}
|
|
3365
|
+
return this._webhookSubscriptions;
|
|
3366
|
+
}
|
|
3367
|
+
/** The tenant's users, read-only: list and directory (E64-S10). */
|
|
3368
|
+
get users() {
|
|
3369
|
+
if (!this._users) {
|
|
3370
|
+
this._users = new UsersResource(this.http);
|
|
3371
|
+
}
|
|
3372
|
+
return this._users;
|
|
3373
|
+
}
|
|
3374
|
+
/** Teams, read-only (E64-S10). */
|
|
3375
|
+
get teams() {
|
|
3376
|
+
if (!this._teams) {
|
|
3377
|
+
this._teams = new TeamsResource(this.http);
|
|
3378
|
+
}
|
|
3379
|
+
return this._teams;
|
|
3380
|
+
}
|
|
2344
3381
|
/**
|
|
2345
3382
|
* Ergonomic webhook event router — `client.webhooks.on(pattern, handler)`
|
|
2346
3383
|
* plus `client.webhooks.process({ body, signature, secret })`. Auth-independent
|
|
@@ -2380,14 +3417,14 @@ var BILLING_ADJUSTMENT_STATUSES = ["pending", "consumed", "cancelled"];
|
|
|
2380
3417
|
var BILLING_ADJUSTMENT_TARGETS = ["next_invoice"];
|
|
2381
3418
|
|
|
2382
3419
|
// src/types/contact-types.ts
|
|
2383
|
-
var CONTACT_SORT_KEYS = ["name", "created_at", "recent"];
|
|
3420
|
+
var CONTACT_SORT_KEYS = ["name", "created_at", "recent", "updated_at"];
|
|
2384
3421
|
var CONTACT_LIST_INCLUDES = ["channels", "links"];
|
|
2385
3422
|
|
|
2386
3423
|
// src/types/customer-types.ts
|
|
2387
|
-
var CUSTOMER_SORT_KEYS = ["created_at", "last_name", "first_name", "email", "phone", "source", "recent"];
|
|
3424
|
+
var CUSTOMER_SORT_KEYS = ["created_at", "last_name", "first_name", "email", "phone", "source", "recent", "updated_at"];
|
|
2388
3425
|
|
|
2389
3426
|
// src/types/opportunity-types.ts
|
|
2390
|
-
var OPPORTUNITY_SORT_KEYS = ["created_at", "total", "title", "customer_name", "stage", "priority", "recent"];
|
|
3427
|
+
var OPPORTUNITY_SORT_KEYS = ["created_at", "total", "title", "customer_name", "stage", "priority", "recent", "updated_at"];
|
|
2391
3428
|
var OPPORTUNITY_STATES = ["open", "closed", "won", "lost"];
|
|
2392
3429
|
|
|
2393
3430
|
// src/types/job-types.ts
|
|
@@ -2415,7 +3452,8 @@ var MODULE_KEYS = [
|
|
|
2415
3452
|
"contacts",
|
|
2416
3453
|
"tasks",
|
|
2417
3454
|
"comments",
|
|
2418
|
-
"conversations"
|
|
3455
|
+
"conversations",
|
|
3456
|
+
"web_forms"
|
|
2419
3457
|
];
|
|
2420
3458
|
|
|
2421
3459
|
// src/types/plan-types.ts
|
|
@@ -2477,6 +3515,12 @@ var WORKFLOW_EVENT_RULE_EVENT_TYPES = [
|
|
|
2477
3515
|
var WORKFLOW_EVENT_RULE_MATCH_ALL_EVENT_TYPES = [
|
|
2478
3516
|
"calendar_event.created"
|
|
2479
3517
|
];
|
|
3518
|
+
var WORKFLOW_EVENT_RULE_CATCH_UP_EVENT_TYPES = [
|
|
3519
|
+
"calendar_event.created"
|
|
3520
|
+
];
|
|
3521
|
+
|
|
3522
|
+
// src/types/task-types.ts
|
|
3523
|
+
var TASK_SORT_KEYS = ["due_at", "created_at", "updated_at"];
|
|
2480
3524
|
|
|
2481
3525
|
// src/agent.ts
|
|
2482
3526
|
var http = __toESM(require("http"));
|
|
@@ -2677,6 +3721,7 @@ function readBody(req) {
|
|
|
2677
3721
|
ESTIMATE_STATUSES,
|
|
2678
3722
|
EnrollmentsResource,
|
|
2679
3723
|
EstimateResource,
|
|
3724
|
+
EventAutomationsResource,
|
|
2680
3725
|
FEATURE_KEYS,
|
|
2681
3726
|
GmailResource,
|
|
2682
3727
|
InventoryResource,
|
|
@@ -2685,6 +3730,8 @@ function readBody(req) {
|
|
|
2685
3730
|
InvoiceSettingsResource,
|
|
2686
3731
|
JOB_SORT_KEYS,
|
|
2687
3732
|
JobResource,
|
|
3733
|
+
LeadSourcesResource,
|
|
3734
|
+
ListsResource,
|
|
2688
3735
|
MODULE_KEYS,
|
|
2689
3736
|
MeResource,
|
|
2690
3737
|
MemoryTokenStorage,
|
|
@@ -2708,13 +3755,20 @@ function readBody(req) {
|
|
|
2708
3755
|
RolesResource,
|
|
2709
3756
|
ServerError,
|
|
2710
3757
|
ServiceSchedulesResource,
|
|
3758
|
+
TASK_SORT_KEYS,
|
|
2711
3759
|
TagResource,
|
|
2712
3760
|
TaskResource,
|
|
3761
|
+
TeamsResource,
|
|
3762
|
+
UsersResource,
|
|
2713
3763
|
ValidationError,
|
|
2714
3764
|
WEBHOOK_EVENT_TYPES,
|
|
3765
|
+
WORKFLOW_EVENT_RULE_CATCH_UP_EVENT_TYPES,
|
|
2715
3766
|
WORKFLOW_EVENT_RULE_EVENT_TYPES,
|
|
2716
3767
|
WORKFLOW_EVENT_RULE_MATCH_ALL_EVENT_TYPES,
|
|
3768
|
+
WebFormSubmissionsResource,
|
|
3769
|
+
WebFormsResource,
|
|
2717
3770
|
WebhookRouter,
|
|
3771
|
+
WebhookSubscriptionsResource,
|
|
2718
3772
|
WebhookVerificationError,
|
|
2719
3773
|
WorkflowEventRulesResource,
|
|
2720
3774
|
WorkflowsResource,
|