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.
Files changed (40) hide show
  1. package/ARCHITECTURE.md +10 -4
  2. package/MCP_TOOL_CATALOG.json +2 -2
  3. package/MCP_TOOL_MANIFEST.md +4 -4
  4. package/N8N_MCP_MANIFEST.json +2 -2
  5. package/N8N_MCP_MANIFEST.md +3 -3
  6. package/README.md +36 -0
  7. package/capabilities.json +1571 -1571
  8. package/dist/capabilities.cjs +2 -2
  9. package/dist/capabilities.js +1 -1
  10. package/dist/{chunk-EWMG3HHU.cjs → chunk-5O5EPYBO.cjs} +1570 -1570
  11. package/dist/{chunk-L3TK4CN5.cjs → chunk-7I3F3AJJ.cjs} +429 -419
  12. package/dist/{chunk-VCGC6YCI.cjs → chunk-G564CHXM.cjs} +135 -135
  13. package/dist/{chunk-NB254T3F.js → chunk-HXOWEYV3.js} +49 -39
  14. package/dist/{chunk-JIDR3NPV.js → chunk-VV2UUW2J.js} +1570 -1570
  15. package/dist/{chunk-U6JUHHMB.js → chunk-VYUZZIEE.js} +66 -32
  16. package/dist/{chunk-GWALLMAM.js → chunk-W2WSZOZA.js} +2 -2
  17. package/dist/{chunk-MAYYTYV5.cjs → chunk-WRWX6WIK.cjs} +68 -34
  18. package/dist/{index-CUsvZndu.d.ts → index-BqPY04ZG.d.ts} +89 -16
  19. package/dist/{index-leMyBJze.d.cts → index-D7x3MkLK.d.cts} +89 -16
  20. package/dist/index.cjs +24 -22
  21. package/dist/index.d.cts +2 -2
  22. package/dist/index.d.ts +2 -2
  23. package/dist/index.js +6 -4
  24. package/dist/operations/index.cjs +178 -105
  25. package/dist/operations/index.d.cts +22 -7
  26. package/dist/operations/index.d.ts +22 -7
  27. package/dist/operations/index.js +77 -4
  28. package/dist/pool.cjs +8 -8
  29. package/dist/pool.d.cts +1 -1
  30. package/dist/pool.d.ts +1 -1
  31. package/dist/pool.js +4 -4
  32. package/dist/resources/index.cjs +4 -4
  33. package/dist/resources/index.d.cts +1 -1
  34. package/dist/resources/index.d.ts +1 -1
  35. package/dist/resources/index.js +3 -3
  36. package/docs/API.md +78 -15
  37. package/examples/basic.ts +2 -2
  38. package/examples/mcp-server-http.ts +5 -4
  39. package/examples/mcp-server.ts +3 -3
  40. 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-U6JUHHMB.js";
37
+ } from "./chunk-VYUZZIEE.js";
37
38
  import {
38
39
  CAPABILITIES
39
- } from "./chunk-JIDR3NPV.js";
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
  });