node-autotask 0.16.0 → 0.18.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/ARCHITECTURE.md +10 -4
- package/MCP_TOOL_CATALOG.json +2 -2
- package/MCP_TOOL_MANIFEST.md +4 -4
- package/N8N_MCP_MANIFEST.json +2 -2
- package/N8N_MCP_MANIFEST.md +3 -3
- package/README.md +36 -0
- package/capabilities.json +1571 -1571
- package/dist/capabilities.cjs +2 -2
- package/dist/capabilities.js +1 -1
- package/dist/{chunk-EWMG3HHU.cjs → chunk-5O5EPYBO.cjs} +1570 -1570
- package/dist/{chunk-L3TK4CN5.cjs → chunk-7I3F3AJJ.cjs} +429 -419
- package/dist/{chunk-VCGC6YCI.cjs → chunk-G564CHXM.cjs} +135 -135
- package/dist/{chunk-NB254T3F.js → chunk-HXOWEYV3.js} +49 -39
- package/dist/{chunk-JIDR3NPV.js → chunk-VV2UUW2J.js} +1570 -1570
- package/dist/{chunk-U6JUHHMB.js → chunk-VYUZZIEE.js} +66 -32
- package/dist/{chunk-GWALLMAM.js → chunk-W2WSZOZA.js} +2 -2
- package/dist/{chunk-MAYYTYV5.cjs → chunk-WRWX6WIK.cjs} +68 -34
- package/dist/{index-CUsvZndu.d.ts → index-BqPY04ZG.d.ts} +89 -16
- package/dist/{index-leMyBJze.d.cts → index-D7x3MkLK.d.cts} +89 -16
- package/dist/index.cjs +24 -22
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +6 -4
- package/dist/operations/index.cjs +178 -105
- package/dist/operations/index.d.cts +22 -7
- package/dist/operations/index.d.ts +22 -7
- package/dist/operations/index.js +77 -4
- package/dist/pool.cjs +8 -8
- package/dist/pool.d.cts +1 -1
- package/dist/pool.d.ts +1 -1
- package/dist/pool.js +4 -4
- package/dist/resources/index.cjs +4 -4
- package/dist/resources/index.d.cts +1 -1
- package/dist/resources/index.d.ts +1 -1
- package/dist/resources/index.js +3 -3
- package/docs/API.md +78 -15
- package/examples/basic.ts +2 -2
- package/examples/mcp-server-http.ts +5 -4
- package/examples/mcp-server.ts +3 -3
- package/package.json +3 -2
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
decodeFileData,
|
|
25
25
|
formatVendorDetail,
|
|
26
26
|
isAutotaskError,
|
|
27
|
+
mergeDetails,
|
|
27
28
|
mergePartialBody,
|
|
28
29
|
parseIdentifier,
|
|
29
30
|
parseVendorErrors,
|
|
@@ -33,10 +34,10 @@ import {
|
|
|
33
34
|
unwrapItem,
|
|
34
35
|
unwrapItems,
|
|
35
36
|
unwrapOperation
|
|
36
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-VYUZZIEE.js";
|
|
37
38
|
import {
|
|
38
39
|
CAPABILITIES
|
|
39
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-VV2UUW2J.js";
|
|
40
41
|
|
|
41
42
|
// src/resources/appointments.ts
|
|
42
43
|
var AppointmentsResource = class extends BaseResource {
|
|
@@ -1102,13 +1103,13 @@ async function upsertByName(resource, companyName, params, opts, http) {
|
|
|
1102
1103
|
if (ownerBody === null || !("item" in ownerBody) || ownerBody.item === null || ownerBody.item === void 0) {
|
|
1103
1104
|
throw new CompanyResourceInvalidError(
|
|
1104
1105
|
`upsertByName: owner resource ${owner} was not found (resources.get returned item: null \u2014 the resources table is blind to this API user; get is the authority)`,
|
|
1105
|
-
{ resourceIds: [owner], operation: OP_UPSERT_NAME, category: "not_found", suggestedAction: "Pass a known active non-API-only resource id (e.g. a ticket assignedResourceID via tickets.fullContext)." }
|
|
1106
|
+
{ resourceIds: [owner], details: { idRefs: [{ entity: "resource", id: owner }] }, operation: OP_UPSERT_NAME, category: "not_found", suggestedAction: "Pass a known active non-API-only resource id (e.g. a ticket assignedResourceID via tickets.fullContext)." }
|
|
1106
1107
|
);
|
|
1107
1108
|
}
|
|
1108
1109
|
if (ownerBody.item.isActive !== true) {
|
|
1109
1110
|
throw new CompanyResourceInvalidError(
|
|
1110
1111
|
`upsertByName: owner resource ${owner} is not active (isActive !== true) \u2014 the vendor requires an active NON-API-only owner`,
|
|
1111
|
-
{ resourceIds: [owner], operation: OP_UPSERT_NAME, suggestedAction: "Activate the resource or pass an active non-API-only one." }
|
|
1112
|
+
{ resourceIds: [owner], details: { idRefs: [{ entity: "resource", id: owner }] }, operation: OP_UPSERT_NAME, suggestedAction: "Activate the resource or pass an active non-API-only one." }
|
|
1112
1113
|
);
|
|
1113
1114
|
}
|
|
1114
1115
|
let merges = 0;
|
|
@@ -1145,7 +1146,8 @@ async function upsertByName(resource, companyName, params, opts, http) {
|
|
|
1145
1146
|
operation: OP_UPSERT_NAME,
|
|
1146
1147
|
httpStatus: err.httpStatus,
|
|
1147
1148
|
vendorError: err.vendorError,
|
|
1148
|
-
resourceIds: [owner]
|
|
1149
|
+
resourceIds: [owner],
|
|
1150
|
+
details: { idRefs: [{ entity: "resource", id: owner }] }
|
|
1149
1151
|
}
|
|
1150
1152
|
);
|
|
1151
1153
|
}
|
|
@@ -2443,7 +2445,7 @@ async function moveConfigurationItem(_resource, sourceId, opts, http) {
|
|
|
2443
2445
|
if (current !== opts.lastModifiedDate) {
|
|
2444
2446
|
throw new StaleObjectError(
|
|
2445
2447
|
`moveConfigurationItem: source item ${numericSource} was modified since the snapshot (expected lastModifiedDate ${opts.lastModifiedDate}, current ${current ?? "unknown"})`,
|
|
2446
|
-
{ operation: "configuration_items.moveConfigurationItem", resourceIds: [numericSource] }
|
|
2448
|
+
{ operation: "configuration_items.moveConfigurationItem", resourceIds: [numericSource], details: { idRefs: [{ entity: "configuration_item", id: numericSource }] } }
|
|
2447
2449
|
);
|
|
2448
2450
|
}
|
|
2449
2451
|
}
|
|
@@ -2496,7 +2498,7 @@ function profilePartLimit2(value, name) {
|
|
|
2496
2498
|
}
|
|
2497
2499
|
return n;
|
|
2498
2500
|
}
|
|
2499
|
-
async function contextFetchPart(http, entityPath, referenceId) {
|
|
2501
|
+
async function contextFetchPart(http, entityPath, referenceId, entity) {
|
|
2500
2502
|
if (typeof referenceId !== "number" || !Number.isSafeInteger(referenceId) || referenceId < 0) {
|
|
2501
2503
|
return null;
|
|
2502
2504
|
}
|
|
@@ -2508,10 +2510,10 @@ async function contextFetchPart(http, entityPath, referenceId) {
|
|
|
2508
2510
|
});
|
|
2509
2511
|
const body = res;
|
|
2510
2512
|
if (body === null || !("item" in body)) {
|
|
2511
|
-
throw new NotFoundError(`context part not found (unexpected envelope shape)`, { resourceIds: [referenceId] });
|
|
2513
|
+
throw new NotFoundError(`context part not found (unexpected envelope shape)`, { resourceIds: [referenceId], details: { idRefs: [{ entity, id: referenceId }] } });
|
|
2512
2514
|
}
|
|
2513
2515
|
if (body.item === null || body.item === void 0) {
|
|
2514
|
-
throw new NotFoundError("context part not found", { resourceIds: [referenceId] });
|
|
2516
|
+
throw new NotFoundError("context part not found", { resourceIds: [referenceId], details: { idRefs: [{ entity, id: referenceId }] } });
|
|
2515
2517
|
}
|
|
2516
2518
|
return body.item;
|
|
2517
2519
|
} catch (err) {
|
|
@@ -2534,8 +2536,8 @@ async function profile(resource, id, opts = {}, http) {
|
|
|
2534
2536
|
const relatedLimit = profilePartLimit2(opts.relatedLimit, "relatedLimit");
|
|
2535
2537
|
const ci = await resource.get(id);
|
|
2536
2538
|
const root = ci;
|
|
2537
|
-
const type = await contextFetchPart(http, "/V1.0/ConfigurationItemTypes", root["configurationItemType"]);
|
|
2538
|
-
const category = await contextFetchPart(http, "/V1.0/ConfigurationItemCategories", root["configurationItemCategoryID"]);
|
|
2539
|
+
const type = await contextFetchPart(http, "/V1.0/ConfigurationItemTypes", root["configurationItemType"], "configuration_item_type");
|
|
2540
|
+
const category = await contextFetchPart(http, "/V1.0/ConfigurationItemCategories", root["configurationItemCategoryID"], "configuration_item_category");
|
|
2539
2541
|
const notes = await ciScopedQuery(http, "/V1.0/ConfigurationItemNotes/query", id, noteLimit);
|
|
2540
2542
|
const relatedItems = await ciScopedQuery(http, "/V1.0/ConfigurationItemRelatedItems/query", id, relatedLimit);
|
|
2541
2543
|
const associations = await ciScopedQuery(http, "/V1.0/ConfigurationItemBillingProductAssociations/query", id, PROFILE_PART_LIMIT2);
|
|
@@ -3026,7 +3028,7 @@ async function moveToCompany(_resource, sourceContactId, destinationCompanyId, o
|
|
|
3026
3028
|
if (current !== opts.lastModifiedDate) {
|
|
3027
3029
|
throw new StaleObjectError(
|
|
3028
3030
|
`moveToCompany: source contact ${sourceId} was modified since the snapshot (expected lastModifiedDate ${opts.lastModifiedDate}, current ${typeof current === "string" ? current : "unknown"})`,
|
|
3029
|
-
{ operation: "contacts.moveToCompany", resourceIds: [sourceId, destId] }
|
|
3031
|
+
{ operation: "contacts.moveToCompany", resourceIds: [sourceId, destId], details: { idRefs: [{ entity: "contact", id: sourceId }, { entity: "company", id: destId }] } }
|
|
3030
3032
|
);
|
|
3031
3033
|
}
|
|
3032
3034
|
}
|
|
@@ -3051,7 +3053,7 @@ async function moveToCompany(_resource, sourceContactId, destinationCompanyId, o
|
|
|
3051
3053
|
if (clash !== void 0) {
|
|
3052
3054
|
throw new ConflictError(
|
|
3053
3055
|
`Contact email "${email}" already exists under company ${destId} (contact ${String(clash.id)}); aborting the move`,
|
|
3054
|
-
{ resourceIds: [sourceId, destId] }
|
|
3056
|
+
{ resourceIds: [sourceId, destId], details: { idRefs: [{ entity: "contact", id: sourceId }, { entity: "company", id: destId }] } }
|
|
3055
3057
|
);
|
|
3056
3058
|
}
|
|
3057
3059
|
}
|
|
@@ -3155,7 +3157,7 @@ async function upsertByEmail(resource, companyID, email, extra, opts, http) {
|
|
|
3155
3157
|
if (parentBody === null || !("item" in parentBody) || parentBody.item === null || parentBody.item === void 0) {
|
|
3156
3158
|
throw new CompanyNotFoundError(
|
|
3157
3159
|
`upsertByEmail: company ${cid} was not found (companies.get returned item: null \u2014 the companies count/query surface is blind to this API user; get is the authority)`,
|
|
3158
|
-
{ resourceIds: [cid], operation: OP_UPSERT_EMAIL }
|
|
3160
|
+
{ resourceIds: [cid], details: { idRefs: [{ entity: "company", id: cid }] }, operation: OP_UPSERT_EMAIL }
|
|
3159
3161
|
);
|
|
3160
3162
|
}
|
|
3161
3163
|
const q = await resource.queryBounded({
|
|
@@ -3244,7 +3246,8 @@ async function upsertByEmail(resource, companyID, email, extra, opts, http) {
|
|
|
3244
3246
|
operation: OP_UPSERT_EMAIL,
|
|
3245
3247
|
httpStatus: err.httpStatus,
|
|
3246
3248
|
vendorError: err.vendorError,
|
|
3247
|
-
resourceIds: [cid]
|
|
3249
|
+
resourceIds: [cid],
|
|
3250
|
+
details: { idRefs: [{ entity: "company", id: cid }] }
|
|
3248
3251
|
}
|
|
3249
3252
|
);
|
|
3250
3253
|
}
|
|
@@ -4814,7 +4817,7 @@ function capJoinSet2(ids, cap) {
|
|
|
4814
4817
|
if (sorted.length <= cap) return { ids: sorted, truncated: false, total: sorted.length };
|
|
4815
4818
|
return { ids: sorted.slice(0, cap), truncated: true, total: sorted.length };
|
|
4816
4819
|
}
|
|
4817
|
-
async function fetchByIdsPart(http, entityPath, ids, fields, operation) {
|
|
4820
|
+
async function fetchByIdsPart(http, entityPath, ids, fields, operation, entity) {
|
|
4818
4821
|
const unique = [...new Set(ids)].sort((a, b) => a - b);
|
|
4819
4822
|
const byKey = /* @__PURE__ */ new Map();
|
|
4820
4823
|
const misses = [];
|
|
@@ -4845,7 +4848,7 @@ async function fetchByIdsPart(http, entityPath, ids, fields, operation) {
|
|
|
4845
4848
|
for (const k of queryMissed) {
|
|
4846
4849
|
try {
|
|
4847
4850
|
const res = await http.request({ method: "GET", path: `${entityPath}/${k}`, operation });
|
|
4848
|
-
byKey.set(k, unwrapItem(res, k, operation));
|
|
4851
|
+
byKey.set(k, unwrapItem(res, k, operation, entity));
|
|
4849
4852
|
} catch (err) {
|
|
4850
4853
|
if (err instanceof NotFoundError) {
|
|
4851
4854
|
misses.push({ id: k, code: "NOT_FOUND", message: err.message });
|
|
@@ -5051,7 +5054,7 @@ async function profitability(resource, contractId, opts, rawHttp) {
|
|
|
5051
5054
|
path: `/V1.0/Companies/${companyID}`,
|
|
5052
5055
|
operation: OP2
|
|
5053
5056
|
});
|
|
5054
|
-
const item = unwrapItem(res, companyID, OP2);
|
|
5057
|
+
const item = unwrapItem(res, companyID, OP2, "company");
|
|
5055
5058
|
customer = item ?? null;
|
|
5056
5059
|
customerStatus = "ok";
|
|
5057
5060
|
} catch (err) {
|
|
@@ -5118,7 +5121,7 @@ async function profitability(resource, contractId, opts, rawHttp) {
|
|
|
5118
5121
|
} else {
|
|
5119
5122
|
const capped = capJoinSet2(invoiceIds, FETCH_BY_IDS_MAX_ITEMS);
|
|
5120
5123
|
if (capped.truncated) wires.note = (wires.note ? wires.note + " | " : "") + `invoiceJoinTruncated: the join set is capped at ${FETCH_BY_IDS_MAX_ITEMS} of ${capped.total} invoice ids (the fetch-by-ids cap) \u2014 the figures cover the returned set only`;
|
|
5121
|
-
const fb = await fetchByIdsPart(http, "/V1.0/Invoices", capped.ids, ["id", "invoiceNumber", "invoiceTotal", "paidDate", "isVoided", "dueDate"], OP2);
|
|
5124
|
+
const fb = await fetchByIdsPart(http, "/V1.0/Invoices", capped.ids, ["id", "invoiceNumber", "invoiceTotal", "paidDate", "isVoided", "dueDate"], OP2, "invoice");
|
|
5122
5125
|
wires.issued += 1;
|
|
5123
5126
|
invoices = fb.items;
|
|
5124
5127
|
invoicesPart = { items: fb.items, capped: capped.truncated, hasMore: capped.truncated, status: "ok", note: fb.notes.join(" | ") || void 0 };
|
|
@@ -5170,7 +5173,7 @@ async function profitability(resource, contractId, opts, rawHttp) {
|
|
|
5170
5173
|
} else {
|
|
5171
5174
|
const capped = capJoinSet2(resourceIds, FETCH_BY_IDS_MAX_ITEMS);
|
|
5172
5175
|
if (capped.truncated) wires.note = (wires.note ? wires.note + " | " : "") + `resourceJoinTruncated: the join set is capped at ${FETCH_BY_IDS_MAX_ITEMS} of ${capped.total} resource ids (the fetch-by-ids cap) \u2014 the figures cover the returned set only`;
|
|
5173
|
-
const fb = await fetchByIdsPart(http, "/V1.0/Resources", capped.ids, ["id", "internalCost", "firstName", "lastName", "isActive", "userType"], OP2);
|
|
5176
|
+
const fb = await fetchByIdsPart(http, "/V1.0/Resources", capped.ids, ["id", "internalCost", "firstName", "lastName", "isActive", "userType"], OP2, "resource");
|
|
5174
5177
|
wires.issued += 1;
|
|
5175
5178
|
resources = fb.items;
|
|
5176
5179
|
for (const m of fb.misses) if (m.code === "NOT_FOUND") resourceMissIds.add(m.id);
|
|
@@ -6583,7 +6586,7 @@ async function markup(_resource, id, format, http) {
|
|
|
6583
6586
|
if (body === null || typeof body !== "object" || typeof body.invoiceMarkup !== "string") {
|
|
6584
6587
|
throw new AutotaskError(
|
|
6585
6588
|
`Expected the InvoiceMarkupResultModel envelope { invoiceMarkup: string } for invoice ${numericId}; got a different shape`,
|
|
6586
|
-
{ code: "INVALID_RESPONSE", category: "server", resourceIds: [numericId] }
|
|
6589
|
+
{ code: "INVALID_RESPONSE", category: "server", resourceIds: [numericId], details: { idRefs: [{ entity: "invoice", id: numericId }] } }
|
|
6587
6590
|
);
|
|
6588
6591
|
}
|
|
6589
6592
|
return body.invoiceMarkup;
|
|
@@ -8044,7 +8047,7 @@ async function whoAmI(_resource, userName, http) {
|
|
|
8044
8047
|
const ids = matches.map((m) => m.id).filter((id) => typeof id === "number");
|
|
8045
8048
|
throw new ResolutionError(
|
|
8046
8049
|
`whoAmI: "${value}" is ambiguous (matched ${matches.length} resources)`,
|
|
8047
|
-
{ code: "RESOLUTION_AMBIGUOUS", resourceIds: ids }
|
|
8050
|
+
{ code: "RESOLUTION_AMBIGUOUS", resourceIds: ids, details: { idRefs: ids.map((cid) => ({ entity: "resource", id: cid })) } }
|
|
8048
8051
|
);
|
|
8049
8052
|
}
|
|
8050
8053
|
return matches[0];
|
|
@@ -8066,7 +8069,7 @@ async function transferOwnership(_resource, companyIds, destinationResourceId, h
|
|
|
8066
8069
|
operation: "resources.transferOwnership",
|
|
8067
8070
|
maxRetries: opts?.maxRetries
|
|
8068
8071
|
});
|
|
8069
|
-
const company = unwrapItem(res, id);
|
|
8072
|
+
const company = unwrapItem(res, id, void 0, "company");
|
|
8070
8073
|
const current = typeof company.ownerResourceID === "number" ? company.ownerResourceID : null;
|
|
8071
8074
|
changes.push({ id, fromOwnerResourceID: current, toOwnerResourceID: destination });
|
|
8072
8075
|
if (!dryRun) {
|
|
@@ -8083,6 +8086,7 @@ async function transferOwnership(_resource, companyIds, destinationResourceId, h
|
|
|
8083
8086
|
} catch (err) {
|
|
8084
8087
|
if (!dryRun && err instanceof AutotaskError) {
|
|
8085
8088
|
err.resourceIds = [id];
|
|
8089
|
+
err.details = mergeDetails(err.details, { idRefs: [{ entity: "company", id }] });
|
|
8086
8090
|
err.suggestedAction = `Partial transfer: ${applied.length} of ${ids.length} companies were re-owned (ids: ${applied.join(", ") || "none"}); id ${id} failed. Re-run transferOwnership for the remaining ids (from ${id} on).`;
|
|
8087
8091
|
}
|
|
8088
8092
|
throw err;
|
|
@@ -8306,7 +8310,7 @@ async function getByYear(_resource, resourceId, year, http) {
|
|
|
8306
8310
|
path: `/V1.0/Resources/${numericResource}/TimeOffBalances/${year}`,
|
|
8307
8311
|
operation: "resource_time_off_balances.getByYear"
|
|
8308
8312
|
});
|
|
8309
|
-
return unwrapItem(res, year, "resource_time_off_balances.getByYear");
|
|
8313
|
+
return unwrapItem(res, year, "resource_time_off_balances.getByYear", "resource_time_off_balance");
|
|
8310
8314
|
}
|
|
8311
8315
|
|
|
8312
8316
|
// src/resources/resource_time_off_balances.ts
|
|
@@ -10228,9 +10232,9 @@ async function fetchTicket(http, id, operation) {
|
|
|
10228
10232
|
});
|
|
10229
10233
|
const body = res;
|
|
10230
10234
|
if (body === null || !("item" in body)) {
|
|
10231
|
-
throw new NotFoundError(`Ticket ${id} not found (unexpected envelope shape)`, { resourceIds: [id] });
|
|
10235
|
+
throw new NotFoundError(`Ticket ${id} not found (unexpected envelope shape)`, { resourceIds: [id], details: { idRefs: [{ entity: "ticket", id }] } });
|
|
10232
10236
|
}
|
|
10233
|
-
if (body.item === null || body.item === void 0) throw new NotFoundError(`Ticket ${id} not found`, { resourceIds: [id] });
|
|
10237
|
+
if (body.item === null || body.item === void 0) throw new NotFoundError(`Ticket ${id} not found`, { resourceIds: [id], details: { idRefs: [{ entity: "ticket", id }] } });
|
|
10234
10238
|
return body.item;
|
|
10235
10239
|
}
|
|
10236
10240
|
async function ticketQueryItems(http, path, filter, maxRecords, operation) {
|
|
@@ -10611,13 +10615,13 @@ async function openWithNoteAndAssign(resource, params, note, assign, opts, http)
|
|
|
10611
10615
|
if (rec === null) {
|
|
10612
10616
|
throw new TicketResourceInvalidError(
|
|
10613
10617
|
`openWithNoteAndAssign: assignee resource ${assignee} was not found (resources.get returned item: null \u2014 the resources table is blind to the API user; get is the authority)`,
|
|
10614
|
-
{ resourceIds: [rid], operation: OP_OPEN, category: "not_found", suggestedAction: "Resolve the resource id (a ticket assignedResourceID via tickets.fullContext) and retry." }
|
|
10618
|
+
{ resourceIds: [rid], details: { idRefs: [{ entity: "resource", id: rid }] }, operation: OP_OPEN, category: "not_found", suggestedAction: "Resolve the resource id (a ticket assignedResourceID via tickets.fullContext) and retry." }
|
|
10615
10619
|
);
|
|
10616
10620
|
}
|
|
10617
10621
|
if (rec.isActive !== true) {
|
|
10618
10622
|
throw new TicketResourceInvalidError(
|
|
10619
10623
|
`openWithNoteAndAssign: assignee resource ${assignee} is not active (isActive !== true) \u2014 the vendor would reject the assignment`,
|
|
10620
|
-
{ resourceIds: [rid], operation: OP_OPEN, suggestedAction: "Activate the resource or assign an active one." }
|
|
10624
|
+
{ resourceIds: [rid], details: { idRefs: [{ entity: "resource", id: rid }] }, operation: OP_OPEN, suggestedAction: "Activate the resource or assign an active one." }
|
|
10621
10625
|
);
|
|
10622
10626
|
}
|
|
10623
10627
|
}
|
|
@@ -10698,7 +10702,8 @@ async function openWithNoteAndAssign(resource, params, note, assign, opts, http)
|
|
|
10698
10702
|
operation: OP_OPEN,
|
|
10699
10703
|
httpStatus: isAutotaskError(err) ? err.httpStatus : void 0,
|
|
10700
10704
|
vendorError: isAutotaskError(err) ? err.vendorError : void 0,
|
|
10701
|
-
resourceIds: [companyID]
|
|
10705
|
+
resourceIds: [companyID],
|
|
10706
|
+
details: { idRefs: [{ entity: "company", id: companyID }] }
|
|
10702
10707
|
}
|
|
10703
10708
|
);
|
|
10704
10709
|
}
|
|
@@ -10719,7 +10724,7 @@ async function openWithNoteAndAssign(resource, params, note, assign, opts, http)
|
|
|
10719
10724
|
try {
|
|
10720
10725
|
const current = await fetchItemUnredacted(http, `/V1.0/Tickets/${ticketId}`, OP_OPEN, { maxRetries: opts.maxRetries });
|
|
10721
10726
|
if (current === null) {
|
|
10722
|
-
throw new NotFoundError(`openWithNoteAndAssign: ticket ${ticketId} vanished between the create and the assign merge read`, { operation: OP_OPEN, resourceIds: [ticketId] });
|
|
10727
|
+
throw new NotFoundError(`openWithNoteAndAssign: ticket ${ticketId} vanished between the create and the assign merge read`, { operation: OP_OPEN, resourceIds: [ticketId], details: { idRefs: [{ entity: "ticket", id: ticketId }] } });
|
|
10723
10728
|
}
|
|
10724
10729
|
ticketNumber = typeof current.ticketNumber === "string" ? current.ticketNumber : null;
|
|
10725
10730
|
const merged = mergePartialBody(current, { id: ticketId, assignedResourceID: assignee, assignedResourceRoleID: roleID });
|
|
@@ -10826,7 +10831,7 @@ async function changeStatus(resource, id, targetStatus, fields, opts, http) {
|
|
|
10826
10831
|
}
|
|
10827
10832
|
const current = await fetchItemUnredacted(http, `/V1.0/Tickets/${ticketId}`, OP_STATUS, { maxRetries: opts.maxRetries });
|
|
10828
10833
|
if (current === null) {
|
|
10829
|
-
throw new NotFoundError(`changeStatus: ticket ${ticketId} was not found (tickets.get returned item: null)`, { operation: OP_STATUS, resourceIds: [ticketId] });
|
|
10834
|
+
throw new NotFoundError(`changeStatus: ticket ${ticketId} was not found (tickets.get returned item: null)`, { operation: OP_STATUS, resourceIds: [ticketId], details: { idRefs: [{ entity: "ticket", id: ticketId }] } });
|
|
10830
10835
|
}
|
|
10831
10836
|
const previousStatus = typeof current.status === "number" ? current.status : null;
|
|
10832
10837
|
if (previousStatus === targetStatus) {
|
|
@@ -10869,7 +10874,7 @@ async function changeStatus(resource, id, targetStatus, fields, opts, http) {
|
|
|
10869
10874
|
throw new StatusFieldsRequiredError(
|
|
10870
10875
|
`changeStatus: status ${targetStatus} requires the field '${field}' and the live-discovered map has no proven-safe default for it \u2014 the op is safe-without-the-map (never a blind full update): supply the value in fields and retry`,
|
|
10871
10876
|
{ vendorFields: [field], currentStatus: previousStatus, targetStatus, currentValues: { [field]: current[field] ?? null } },
|
|
10872
|
-
{ operation: OP_STATUS, resourceIds: [ticketId] }
|
|
10877
|
+
{ operation: OP_STATUS, resourceIds: [ticketId], details: { idRefs: [{ entity: "ticket", id: ticketId }] } }
|
|
10873
10878
|
);
|
|
10874
10879
|
}
|
|
10875
10880
|
mergeData[field] = safe;
|
|
@@ -10930,7 +10935,8 @@ async function changeStatus(resource, id, targetStatus, fields, opts, http) {
|
|
|
10930
10935
|
operation: OP_STATUS,
|
|
10931
10936
|
httpStatus: isAutotaskError(err) ? err.httpStatus : void 0,
|
|
10932
10937
|
vendorError: isAutotaskError(err) ? err.vendorError : void 0,
|
|
10933
|
-
resourceIds: [ticketId]
|
|
10938
|
+
resourceIds: [ticketId],
|
|
10939
|
+
details: { idRefs: [{ entity: "ticket", id: ticketId }] }
|
|
10934
10940
|
}
|
|
10935
10941
|
);
|
|
10936
10942
|
}
|
|
@@ -10943,7 +10949,8 @@ async function changeStatus(resource, id, targetStatus, fields, opts, http) {
|
|
|
10943
10949
|
code: "INVALID_RESPONSE",
|
|
10944
10950
|
category: "server",
|
|
10945
10951
|
operation: OP_STATUS,
|
|
10946
|
-
resourceIds: [ticketId]
|
|
10952
|
+
resourceIds: [ticketId],
|
|
10953
|
+
details: { idRefs: [{ entity: "ticket", id: ticketId }] }
|
|
10947
10954
|
});
|
|
10948
10955
|
}
|
|
10949
10956
|
const finalStatus = typeof verify.status === "number" ? verify.status : null;
|
|
@@ -10956,6 +10963,7 @@ async function changeStatus(resource, id, targetStatus, fields, opts, http) {
|
|
|
10956
10963
|
httpStatus: 200,
|
|
10957
10964
|
operation: OP_STATUS,
|
|
10958
10965
|
resourceIds: [ticketId],
|
|
10966
|
+
details: { idRefs: [{ entity: "ticket", id: ticketId }] },
|
|
10959
10967
|
suggestedAction: "Re-run changeStatus (the merge re-reads the current record) or inspect the ticket (tickets.get)."
|
|
10960
10968
|
}
|
|
10961
10969
|
);
|
|
@@ -11405,7 +11413,7 @@ var NoApplicableAllocationCodeError = class extends AutotaskError {
|
|
|
11405
11413
|
}
|
|
11406
11414
|
);
|
|
11407
11415
|
this.name = "NoApplicableAllocationCodeError";
|
|
11408
|
-
this.details = details;
|
|
11416
|
+
this.details = { ...details, idRefs: [{ entity: "resource", id: details.resourceID }] };
|
|
11409
11417
|
}
|
|
11410
11418
|
};
|
|
11411
11419
|
async function boundedQuery(http, path, filter, totalCap, operation) {
|
|
@@ -11489,21 +11497,21 @@ async function resolveCode(http, family, familyLabel, pinnedId, departments, ope
|
|
|
11489
11497
|
throw new AllocationCodeError(
|
|
11490
11498
|
"NOT_FOUND",
|
|
11491
11499
|
`Pinned allocation code ${pinnedId} was not found (billing_codes.get returned item: null)`,
|
|
11492
|
-
{ resourceIds: [pinnedId], operation, suggestedAction: `Resolve an existing ${familyLabel} allocation code id (billing_codes.query with a filter) and re-pin.` }
|
|
11500
|
+
{ resourceIds: [pinnedId], details: { idRefs: [{ entity: "billing_code", id: pinnedId }] }, operation, suggestedAction: `Resolve an existing ${familyLabel} allocation code id (billing_codes.query with a filter) and re-pin.` }
|
|
11493
11501
|
);
|
|
11494
11502
|
}
|
|
11495
11503
|
if (rec.isActive !== true) {
|
|
11496
11504
|
throw new AllocationCodeError(
|
|
11497
11505
|
"INACTIVE",
|
|
11498
11506
|
`Pinned allocation code ${pinnedId} (${typeof rec.name === "string" ? rec.name : "unknown name"}) is not active (isActive !== true)`,
|
|
11499
|
-
{ resourceIds: [pinnedId], operation, suggestedAction: "Activate the code in the tenant administration or pin an active one." }
|
|
11507
|
+
{ resourceIds: [pinnedId], details: { idRefs: [{ entity: "billing_code", id: pinnedId }] }, operation, suggestedAction: "Activate the code in the tenant administration or pin an active one." }
|
|
11500
11508
|
);
|
|
11501
11509
|
}
|
|
11502
11510
|
if (rec.useType !== family) {
|
|
11503
11511
|
throw new AllocationCodeError(
|
|
11504
11512
|
"WRONG_TYPE",
|
|
11505
11513
|
`Pinned allocation code ${pinnedId} is useType ${String(rec.useType ?? "unknown")}, not the ${familyLabel} family (useType ${family})`,
|
|
11506
|
-
{ resourceIds: [pinnedId], operation, suggestedAction: `Pin a code from the right family (billing_codes.query with useType eq ${family} + isActive eq true).` }
|
|
11514
|
+
{ resourceIds: [pinnedId], details: { idRefs: [{ entity: "billing_code", id: pinnedId }] }, operation, suggestedAction: `Pin a code from the right family (billing_codes.query with useType eq ${family} + isActive eq true).` }
|
|
11507
11515
|
);
|
|
11508
11516
|
}
|
|
11509
11517
|
const dept = typeof rec.department === "number" ? rec.department : null;
|
|
@@ -11511,7 +11519,7 @@ async function resolveCode(http, family, familyLabel, pinnedId, departments, ope
|
|
|
11511
11519
|
throw new AllocationCodeError(
|
|
11512
11520
|
"NOT_APPLICABLE",
|
|
11513
11521
|
`Pinned allocation code ${pinnedId} is scoped to department ${dept}, which is not among the resource's active role departments [${[...departments].sort((a, b) => a - b).join(", ")}] (and the code is not tenant-wide)`,
|
|
11514
|
-
{ resourceIds: [pinnedId], operation, suggestedAction: "Pin a tenant-wide (department null) code or one scoped to a resource department, or broaden the resource role assignments." }
|
|
11522
|
+
{ resourceIds: [pinnedId], details: { idRefs: [{ entity: "billing_code", id: pinnedId }] }, operation, suggestedAction: "Pin a tenant-wide (department null) code or one scoped to a resource department, or broaden the resource role assignments." }
|
|
11515
11523
|
);
|
|
11516
11524
|
}
|
|
11517
11525
|
return rec;
|
|
@@ -11574,6 +11582,7 @@ async function createForResource(resource, resourceID, params, opts = {}, http)
|
|
|
11574
11582
|
if (rec === null) {
|
|
11575
11583
|
throw new ResourceInvalidError(`Resource ${rid} was not found \u2014 a time entry cannot attach to a missing resource`, {
|
|
11576
11584
|
resourceIds: [rid],
|
|
11585
|
+
details: { idRefs: [{ entity: "resource", id: rid }] },
|
|
11577
11586
|
operation: OP,
|
|
11578
11587
|
category: "not_found",
|
|
11579
11588
|
suggestedAction: "Resolve the resource id (resources.get / resources.search) and retry."
|
|
@@ -11582,6 +11591,7 @@ async function createForResource(resource, resourceID, params, opts = {}, http)
|
|
|
11582
11591
|
if (rec.isActive !== true) {
|
|
11583
11592
|
throw new ResourceInvalidError(`Resource ${rid} is not active (isActive !== true) \u2014 the vendor would reject a time entry for an inactive resource`, {
|
|
11584
11593
|
resourceIds: [rid],
|
|
11594
|
+
details: { idRefs: [{ entity: "resource", id: rid }] },
|
|
11585
11595
|
operation: OP,
|
|
11586
11596
|
suggestedAction: "Activate the resource or pick an active one."
|
|
11587
11597
|
});
|