node-autotask 0.26.0 → 0.28.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 (49) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/MCP_TOOL_CATALOG.json +941 -14
  3. package/MCP_TOOL_MANIFEST.md +71 -16
  4. package/N8N_MCP_MANIFEST.json +91 -10
  5. package/N8N_MCP_MANIFEST.md +14 -11
  6. package/README.md +11 -9
  7. package/capabilities.json +13623 -7738
  8. package/capabilities.schema.json +125 -0
  9. package/dist/capabilities.cjs +2 -2
  10. package/dist/capabilities.js +1 -1
  11. package/dist/{chunk-QXWIQ67O.js → chunk-6EQSXYRW.js} +13622 -7737
  12. package/dist/{chunk-HQGE4WAV.cjs → chunk-D5IVS6GY.cjs} +1054 -235
  13. package/dist/{chunk-5OGJTS2E.cjs → chunk-GUY2KK7U.cjs} +157 -136
  14. package/dist/{chunk-SSUECUVN.js → chunk-IRVZGW5K.js} +947 -128
  15. package/dist/{chunk-7DOSJS6S.js → chunk-O2D7KDDD.js} +23 -2
  16. package/dist/{chunk-VVQ6NJYZ.js → chunk-SYX6EWMT.js} +694 -76
  17. package/dist/{chunk-SG4LIVMG.cjs → chunk-WYKGD5UD.cjs} +1189 -571
  18. package/dist/{chunk-WM6E5O55.cjs → chunk-ZEGMVEF3.cjs} +13622 -7737
  19. package/dist/{index-CKYr0unq.d.cts → index-CORcZUNm.d.cts} +457 -50
  20. package/dist/{index-1zBdJIEy.d.cts → index-DQ9NgraW.d.cts} +281 -1
  21. package/dist/{index-1zBdJIEy.d.ts → index-DQ9NgraW.d.ts} +281 -1
  22. package/dist/{index-C8QH3BkP.d.ts → index-DXuV04zM.d.ts} +457 -50
  23. package/dist/index.cjs +44 -24
  24. package/dist/index.d.cts +11 -5
  25. package/dist/index.d.ts +11 -5
  26. package/dist/index.js +26 -6
  27. package/dist/operations/index.cjs +147 -120
  28. package/dist/operations/index.d.cts +136 -7
  29. package/dist/operations/index.d.ts +136 -7
  30. package/dist/operations/index.js +33 -6
  31. package/dist/pool.cjs +42 -9
  32. package/dist/pool.d.cts +14 -3
  33. package/dist/pool.d.ts +14 -3
  34. package/dist/pool.js +38 -5
  35. package/dist/resources/index.cjs +19 -5
  36. package/dist/resources/index.d.cts +2 -2
  37. package/dist/resources/index.d.ts +2 -2
  38. package/dist/resources/index.js +18 -4
  39. package/dist/types/index.cjs +1 -1
  40. package/dist/types/index.d.cts +1 -1
  41. package/dist/types/index.d.ts +1 -1
  42. package/dist/types/index.js +1 -1
  43. package/docs/API.md +246 -10
  44. package/examples/mcp/schemas.ts +16 -7
  45. package/package.json +1 -1
  46. /package/dist/{chunk-S643FDOR.cjs → chunk-IXF7AKOJ.cjs} +0 -0
  47. /package/dist/{chunk-2TWKEB2L.js → chunk-RMG55JTF.js} +0 -0
  48. /package/dist/{chunk-W3K5BEVY.cjs → chunk-SDYRQFGL.cjs} +0 -0
  49. /package/dist/{chunk-EBCHCNPK.js → chunk-WLTLB2ND.js} +0 -0
@@ -38,10 +38,10 @@ import {
38
38
  unwrapItem,
39
39
  unwrapItems,
40
40
  unwrapOperation
41
- } from "./chunk-SSUECUVN.js";
41
+ } from "./chunk-IRVZGW5K.js";
42
42
  import {
43
43
  CAPABILITIES
44
- } from "./chunk-QXWIQ67O.js";
44
+ } from "./chunk-6EQSXYRW.js";
45
45
 
46
46
  // src/resources/appointments.ts
47
47
  var AppointmentsResource = class extends BaseResource {
@@ -172,6 +172,42 @@ var BillingCodesResource = class extends BaseResource {
172
172
  }
173
173
  };
174
174
 
175
+ // src/resources/billing_item_approval_levels.ts
176
+ var BillingItemApprovalLevelsResource = class extends BaseResource {
177
+ constructor(http, pageSize) {
178
+ super(http, {
179
+ key: "billing_item_approval_levels",
180
+ resourcePath: "/V1.0/BillingItemApprovalLevels",
181
+ deletable: false,
182
+ udfCapable: false
183
+ }, pageSize);
184
+ }
185
+ /** Query BillingItemApprovalLevel records with the G3 filter surface (server-side JSON filter). */
186
+ query(params) {
187
+ return this.queryItems(params);
188
+ }
189
+ /** Get every BillingItemApprovalLevel record (the MCP-preferred read; 1000-page guarded). */
190
+ async queryAll(params) {
191
+ return this.queryAllItems(params);
192
+ }
193
+ /** Count matching records (queryCount envelope unwrapped to a plain number). */
194
+ async count(params) {
195
+ return this.countGlobal(params);
196
+ }
197
+ /** Get one record by id (item envelope; item: null → NotFoundError). */
198
+ async get(id) {
199
+ return this.getGlobal(id);
200
+ }
201
+ /** Create a record (OperationResultModel → { id}). `dryRun: true` returns the
202
+ * pinned envelope (section 19.3) and never issues the create. A 2xx that is not the
203
+ * specced envelope runs the plan-pinned marker recovery (issue #13 — one bounded read;
204
+ * `recoverIdFromMarker: false` restores the pre-recovery behaviour). */
205
+ async create(data, opts = {}) {
206
+ if (opts.dryRun === true) return this.runCreateDryRun(data);
207
+ return this.createGlobal(data, opts);
208
+ }
209
+ };
210
+
175
211
  // src/resources/special/dateWindow.ts
176
212
  var DAY_MS = 864e5;
177
213
  var DEFAULT_LIMIT = 500;
@@ -1255,6 +1291,82 @@ async function upsertByName(resource, companyName, params, opts, http) {
1255
1291
  return createWithDiscovery(resource, body, owner, caveat, opts);
1256
1292
  }
1257
1293
 
1294
+ // src/resources/special/_shared/ticketStatus.ts
1295
+ var ASSUMED_CLOSED_STATUS_ID = 5;
1296
+ var CLOSED_STATUS_LABELS = ["complete", "completed", "closed", "cancelled", "canceled", "resolved"];
1297
+ var OP = "tickets.statusSemantics";
1298
+ function picklistOf(fields) {
1299
+ if (!Array.isArray(fields)) return [];
1300
+ const status = fields.find((f) => typeof f?.name === "string" && f.name.toLowerCase() === "status");
1301
+ if (status === void 0 || status.isPickList !== true || !Array.isArray(status.picklistValues)) return [];
1302
+ const out = [];
1303
+ for (const v of status.picklistValues) {
1304
+ const id = Number(v?.value);
1305
+ if (!Number.isSafeInteger(id)) continue;
1306
+ out.push({ id, label: typeof v.label === "string" ? v.label : "" });
1307
+ }
1308
+ return out;
1309
+ }
1310
+ async function readFields(http, fresh) {
1311
+ const cache = http.fieldCache;
1312
+ const key = "tickets";
1313
+ if (!fresh && cache !== void 0) {
1314
+ const shared = cache.inflight(key, "fields");
1315
+ if (shared !== void 0) return await shared;
1316
+ const hit = cache.get(key, "fields");
1317
+ if (hit !== void 0) return hit;
1318
+ }
1319
+ const res = await http.request({ method: "GET", path: "/V1.0/Tickets/entityInformation/fields", operation: OP, maxRetries: 0 });
1320
+ const fields = res?.fields;
1321
+ if (!Array.isArray(fields)) {
1322
+ throw new AutotaskError("Expected the FieldInformationResultModel envelope { fields: [] }; got a different shape", { code: "INVALID_RESPONSE", category: "server" });
1323
+ }
1324
+ cache?.set(key, "fields", fields);
1325
+ return fields;
1326
+ }
1327
+ function assumed(excludeStatusIds) {
1328
+ const closedIds = excludeStatusIds === void 0 ? [ASSUMED_CLOSED_STATUS_ID] : [...excludeStatusIds];
1329
+ return {
1330
+ picklist: [],
1331
+ closedIds,
1332
+ source: "assumed",
1333
+ assumedId: ASSUMED_CLOSED_STATUS_ID,
1334
+ note: `closed = status ${closedIds.join(", ")} \u2014 ASSUMED, not read from the tenant: the Tickets status picklist could not be resolved, so the historical id ${ASSUMED_CLOSED_STATUS_ID} stands in (fix: expose the Tickets field catalog to this API user, or pass excludeStatusIds)`
1335
+ };
1336
+ }
1337
+ async function resolveTicketStatusSemantics(http, opts = {}) {
1338
+ if (opts.excludeStatusIds !== void 0) {
1339
+ const closedIds2 = [...new Set(opts.excludeStatusIds.map((id) => Number(id)))];
1340
+ if (closedIds2.length === 0 || closedIds2.some((id) => !Number.isSafeInteger(id) || id < 1)) {
1341
+ throw new AutotaskConfigError(`excludeStatusIds must be a non-empty list of positive status ids (got ${JSON.stringify(opts.excludeStatusIds)})`, { operation: OP });
1342
+ }
1343
+ return { picklist: [], closedIds: closedIds2, source: "picklist", note: `closed = status ${closedIds2.join(", ")} \u2014 supplied by the caller` };
1344
+ }
1345
+ let fields;
1346
+ try {
1347
+ fields = await readFields(http, opts.fresh === true);
1348
+ } catch (e) {
1349
+ if (e instanceof AutotaskError && e.code === "ABORTED") throw e;
1350
+ return assumed();
1351
+ }
1352
+ const picklist = picklistOf(fields);
1353
+ const closedIds = picklist.filter((v) => CLOSED_STATUS_LABELS.includes(v.label.trim().toLowerCase())).map((v) => v.id);
1354
+ if (closedIds.length === 0) return assumed();
1355
+ const labels = picklist.filter((v) => closedIds.includes(v.id)).map((v) => `${v.id} ${v.label}`).join(", ");
1356
+ return {
1357
+ picklist,
1358
+ closedIds,
1359
+ source: "picklist",
1360
+ note: `closed = status not in [${closedIds.join(", ")}] (${labels}) \u2014 resolved from the tenant's Tickets status picklist`
1361
+ };
1362
+ }
1363
+ function openStatusFilter(closedIds) {
1364
+ return closedIds.length === 1 ? { field: "status", op: "noteq", value: closedIds[0] } : { field: "status", op: "notIn", value: [...closedIds] };
1365
+ }
1366
+ function isClosedStatus(status, closedIds) {
1367
+ return typeof status === "number" && closedIds.includes(status);
1368
+ }
1369
+
1258
1370
  // src/resources/special/companies/fullProfile.ts
1259
1371
  var FULL_PROFILE_PROJECTABLE_PARTS = ["locations", "contacts", "notes", "projects", "openTickets"];
1260
1372
  var PART_ENTITY = {
@@ -1264,7 +1376,7 @@ var PART_ENTITY = {
1264
1376
  projects: "projects",
1265
1377
  openTickets: "tickets"
1266
1378
  };
1267
- var OPEN_TICKETS_NOTE = "open = status != 5 (Complete) \u2014 the ww29 ticket-status picklist has no Closed value; tickets in any other status count as open";
1379
+ var OPEN_TICKETS_NOTE = "open = status != 5 (Complete) \u2014 ASSUMED: the tenant Tickets status picklist could not be read, so id 5 is the fallback closed id; tickets in any other status count as open";
1268
1380
  var PROFILE_PART_LIMIT = 50;
1269
1381
  var LOCATION_LIMIT_MAX = 500;
1270
1382
  function locationLimit(value) {
@@ -1325,6 +1437,8 @@ async function scopedCompanyQuery(http, path, companyID, limit, includeFields) {
1325
1437
  return project(unwrapItems(res).items, includeFields);
1326
1438
  }
1327
1439
  async function readOpenTickets(http, companyID, ticketLimit, includeFields) {
1440
+ const semantics = await resolveTicketStatusSemantics(http);
1441
+ const openLeaf = openStatusFilter(semantics.closedIds);
1328
1442
  const openRows = await (async () => {
1329
1443
  const res = await http.request({
1330
1444
  method: "POST",
@@ -1332,30 +1446,31 @@ async function readOpenTickets(http, companyID, ticketLimit, includeFields) {
1332
1446
  path: "/V1.0/Tickets/query",
1333
1447
  body: {
1334
1448
  MaxRecords: ticketLimit,
1335
- filter: [{ field: "companyID", op: "eq", value: companyID }, { field: "status", op: "noteq", value: 5 }],
1449
+ filter: [{ field: "companyID", op: "eq", value: companyID }, openLeaf],
1336
1450
  ...includeFields ? { IncludeFields: includeFields } : {}
1337
1451
  },
1338
1452
  operation: "companies.fullProfile"
1339
1453
  });
1340
1454
  return project(unwrapItems(res).items, includeFields);
1341
1455
  })();
1456
+ const closedStatus = { closedIds: semantics.closedIds, source: semantics.source, note: semantics.note };
1457
+ const partNote = semantics.source === "assumed" ? OPEN_TICKETS_NOTE : semantics.note;
1342
1458
  let openTickets = {
1343
1459
  items: openRows,
1344
1460
  capped: openRows.length >= ticketLimit,
1345
1461
  blind: false,
1346
- note: OPEN_TICKETS_NOTE
1462
+ note: partNote,
1463
+ closedStatus
1347
1464
  };
1348
1465
  if (openRows.length === 0) {
1349
- const stamp = blindStampFor(BLIND_SURFACE_STAMPS["tickets"]?.query, [
1350
- { field: "companyID", op: "eq", value: companyID },
1351
- { field: "status", op: "noteq", value: 5 }
1352
- ]);
1466
+ const stamp = blindStampFor(BLIND_SURFACE_STAMPS["tickets"]?.query, [{ field: "companyID", op: "eq", value: companyID }, openLeaf]);
1353
1467
  if (stamp !== void 0) {
1354
1468
  openTickets = {
1355
1469
  items: openRows,
1356
1470
  capped: openRows.length >= ticketLimit,
1357
1471
  blind: true,
1358
- note: `${stamp.note} ${OPEN_TICKETS_NOTE}`,
1472
+ note: `${stamp.note} ${partNote}`,
1473
+ closedStatus,
1359
1474
  blindSurface: stamp
1360
1475
  };
1361
1476
  }
@@ -1553,7 +1668,7 @@ function validateUnbilledWorkOptions(companyId, opts) {
1553
1668
  }
1554
1669
 
1555
1670
  // src/resources/special/companies/unbilledWork.ts
1556
- var OP = "companies.unbilledWork";
1671
+ var OP2 = "companies.unbilledWork";
1557
1672
  function capJoinSet(ids2, cap) {
1558
1673
  const sorted = [...new Set(ids2)].sort((a, b) => a - b);
1559
1674
  if (sorted.length <= cap) return { ids: sorted, truncated: false };
@@ -1590,14 +1705,14 @@ async function readCompanyAndParent(resource, id, includeParent) {
1590
1705
  }
1591
1706
  async function readJoinSets(http, companyIds, v) {
1592
1707
  const ticketsCond = [{ field: "companyID", op: "in", value: companyIds }];
1593
- const ticketsRaw = await boundedPart(http, "/V1.0/Tickets/query", ticketsCond, v.limit, OP);
1708
+ const ticketsRaw = await boundedPart(http, "/V1.0/Tickets/query", ticketsCond, v.limit, OP2);
1594
1709
  const projectsCond = [{ field: "companyID", op: "in", value: companyIds }];
1595
- const projectsRaw = await boundedPart(http, "/V1.0/Projects/query", projectsCond, v.limit, OP);
1710
+ const projectsRaw = await boundedPart(http, "/V1.0/Projects/query", projectsCond, v.limit, OP2);
1596
1711
  const projectIds = projectsRaw.items.map((p) => num(p.id)).filter((n) => n > 0);
1597
1712
  let taskIds = [];
1598
1713
  let tasksHasMore = false;
1599
1714
  if (projectIds.length > 0) {
1600
- const tasksRaw = await boundedPart(http, "/V1.0/Tasks/query", [{ field: "projectID", op: "in", value: projectIds }], v.limit, OP);
1715
+ const tasksRaw = await boundedPart(http, "/V1.0/Tasks/query", [{ field: "projectID", op: "in", value: projectIds }], v.limit, OP2);
1601
1716
  taskIds = tasksRaw.items.map((t) => num(t.id)).filter((n) => n > 0);
1602
1717
  tasksHasMore = tasksRaw.hasMore;
1603
1718
  }
@@ -1621,7 +1736,7 @@ async function readBillingPart(http, v, companyIds) {
1621
1736
  }
1622
1737
  const billingFields = resolveFieldsPreset("billing_join", "billing_items");
1623
1738
  const billingIncludeFields = [...billingFields, "id", ...v.includeNotes ? ["description"] : []];
1624
- const billingRaw = await boundedPart(http, "/V1.0/BillingItems/query", billingConditions, v.limit, OP, billingIncludeFields);
1739
+ const billingRaw = await boundedPart(http, "/V1.0/BillingItems/query", billingConditions, v.limit, OP2, billingIncludeFields);
1625
1740
  let billingItems = billingRaw.items;
1626
1741
  let billingNoteCharsTruncated = 0;
1627
1742
  if (v.includeNotes) {
@@ -1665,7 +1780,7 @@ async function readTimePart(http, v, join) {
1665
1780
  if (v.dateFrom !== void 0) timeConditions.push({ field: "dateWorked", op: "gte", value: v.dateFrom });
1666
1781
  if (v.dateTo !== void 0) timeConditions.push({ field: "dateWorked", op: "lte", value: v.dateTo });
1667
1782
  const timeFields = ["taskID", "ticketID", "hoursWorked", "dateWorked", "id", ...v.includeNotes ? ["summaryNotes"] : []];
1668
- const raw = await boundedPart(http, "/V1.0/TimeEntries/query", timeConditions, v.limit, OP, timeFields);
1783
+ const raw = await boundedPart(http, "/V1.0/TimeEntries/query", timeConditions, v.limit, OP2, timeFields);
1669
1784
  entries = raw.items;
1670
1785
  let cappedItems = entries;
1671
1786
  let noteCharsTruncated = 0;
@@ -5184,7 +5299,7 @@ function appendWiresNote(wires, note) {
5184
5299
  wires.note = (wires.note ? wires.note + " | " : "") + note;
5185
5300
  }
5186
5301
  function makeFetchCtx(rawHttp, v, contractId, budget) {
5187
- const OP6 = PROFITABILITY_OP;
5302
+ const OP9 = PROFITABILITY_OP;
5188
5303
  const id = parseIdentifier(contractId, "contractId");
5189
5304
  const idNum = typeof id === "number" ? id : Number(id);
5190
5305
  const wires = { issued: 0, requests: 0, budget };
@@ -5196,19 +5311,19 @@ function makeFetchCtx(rawHttp, v, contractId, budget) {
5196
5311
  } };
5197
5312
  const readPart = (path, conditions, fields) => {
5198
5313
  wires.issued += 1;
5199
- return degradable(() => boundedPart(http, path, conditions, v.limit, OP6, fields));
5314
+ return degradable(() => boundedPart(http, path, conditions, v.limit, OP9, fields));
5200
5315
  };
5201
5316
  const truncatedMonths = {};
5202
5317
  const windowedPart = async (name, key, path, cond, fields, dateField) => {
5203
5318
  const first = await readPart(path, cond, fields);
5204
5319
  if (first.absent !== void 0 || !first.hasMore) return { part: stampPart(key, first, cond), rows: first.items };
5205
- const wp = await degradable(() => expandByMonth(http, path, cond, v.limit, OP6, fields, dateField, v.dateFrom, v.dateTo, wires));
5320
+ const wp = await degradable(() => expandByMonth(http, path, cond, v.limit, OP9, fields, dateField, v.dateFrom, v.dateTo, wires));
5206
5321
  if (!("truncatedMonths" in wp)) return { part: stampPart(key, wp, cond), rows: [] };
5207
5322
  const { truncatedMonths: tm, ...part } = wp;
5208
5323
  truncatedMonths[name] = tm;
5209
5324
  return { part, rows: wp.items };
5210
5325
  };
5211
- return { v, OP: OP6, id, idNum, rawHttp, http, wires, windowOn, readPart, windowedPart, truncatedMonths };
5326
+ return { v, OP: OP9, id, idNum, rawHttp, http, wires, windowOn, readPart, windowedPart, truncatedMonths };
5212
5327
  }
5213
5328
 
5214
5329
  // src/resources/special/contracts/profitability.stages.ts
@@ -7131,6 +7246,34 @@ var InvoicesResource = class extends BaseResource {
7131
7246
  }
7132
7247
  };
7133
7248
 
7249
+ // src/resources/invoice_templates.ts
7250
+ var InvoiceTemplatesResource = class extends BaseResource {
7251
+ constructor(http, pageSize) {
7252
+ super(http, {
7253
+ key: "invoice_templates",
7254
+ resourcePath: "/V1.0/InvoiceTemplates",
7255
+ deletable: false,
7256
+ udfCapable: false
7257
+ }, pageSize);
7258
+ }
7259
+ /** Query InvoiceTemplate records with the G3 filter surface (server-side JSON filter). */
7260
+ query(params) {
7261
+ return this.queryItems(params);
7262
+ }
7263
+ /** Get every InvoiceTemplate record (the MCP-preferred read; 1000-page guarded). */
7264
+ async queryAll(params) {
7265
+ return this.queryAllItems(params);
7266
+ }
7267
+ /** Count matching records (queryCount envelope unwrapped to a plain number). */
7268
+ async count(params) {
7269
+ return this.countGlobal(params);
7270
+ }
7271
+ /** Get one record by id (item envelope; item: null → NotFoundError). */
7272
+ async get(id) {
7273
+ return this.getGlobal(id);
7274
+ }
7275
+ };
7276
+
7134
7277
  // src/resources/modules.ts
7135
7278
  var ModulesResource = class {
7136
7279
  http;
@@ -7369,6 +7512,159 @@ var OpportunityCategoriesResource = class extends BaseResource {
7369
7512
  }
7370
7513
  };
7371
7514
 
7515
+ // src/resources/organizational_level1s.ts
7516
+ var OrganizationalLevel1sResource = class extends BaseResource {
7517
+ constructor(http, pageSize) {
7518
+ super(http, {
7519
+ key: "organizational_level1s",
7520
+ resourcePath: "/V1.0/OrganizationalLevel1s",
7521
+ deletable: false,
7522
+ udfCapable: false,
7523
+ collectionUpdate: true
7524
+ // D1: the spec's item path is GET-only — confirmed updates PUT the collection (id in the body)
7525
+ }, pageSize);
7526
+ }
7527
+ /** Query OrganizationalLevel1 records with the G3 filter surface (server-side JSON filter). */
7528
+ query(params) {
7529
+ return this.queryItems(params);
7530
+ }
7531
+ /** Get every OrganizationalLevel1 record (the MCP-preferred read; 1000-page guarded). */
7532
+ async queryAll(params) {
7533
+ return this.queryAllItems(params);
7534
+ }
7535
+ /** Count matching records (queryCount envelope unwrapped to a plain number). */
7536
+ async count(params) {
7537
+ return this.countGlobal(params);
7538
+ }
7539
+ /** Get one record by id (item envelope; item: null → NotFoundError). */
7540
+ async get(id) {
7541
+ return this.getGlobal(id);
7542
+ }
7543
+ /** Create a record (OperationResultModel → { id}). `dryRun: true` returns the
7544
+ * pinned envelope (section 19.3) and never issues the create. A 2xx that is not the
7545
+ * specced envelope runs the plan-pinned marker recovery (issue #13 — one bounded read;
7546
+ * `recoverIdFromMarker: false` restores the pre-recovery behaviour). */
7547
+ async create(data, opts = {}) {
7548
+ if (opts.dryRun === true) return this.runCreateDryRun(data);
7549
+ return this.createGlobal(data, opts);
7550
+ }
7551
+ /** Update a record (PUT on the COLLECTION — the vendored spec's item path is GET-only; the vendor
7552
+ * keys the record by the id in the body). The vendor validates the FULL model on this route (V2), so a
7553
+ * partial body is merged into the CURRENT record first: one fresh GET (which also serves the
7554
+ * stale-object guard's read) then the collection PUT of the merged model — an explicit null in the
7555
+ * caller body sends null; an omitted field keeps the current value. `raw: true` in the opts bag sends
7556
+ * the body as-is (full model or intentional partial — the compatibility escape hatch; no merge read).
7557
+ * `dryRun: true` returns the pinned envelope whose `data` is the merged body (the exact confirmed
7558
+ * wire) with the best-effort diff, from that same single read; a caller-supplied lastModifiedDate that
7559
+ * mismatches the vendor's current value throws STALE_OBJECT before the mutation. */
7560
+ async update(id, data, opts = {}) {
7561
+ if (opts.dryRun === true) return this.runUpdateDryRun(id, data, opts);
7562
+ return this.updateGlobal(id, data, opts);
7563
+ }
7564
+ };
7565
+
7566
+ // src/resources/organizational_level2s.ts
7567
+ var OrganizationalLevel2sResource = class extends BaseResource {
7568
+ constructor(http, pageSize) {
7569
+ super(http, {
7570
+ key: "organizational_level2s",
7571
+ resourcePath: "/V1.0/OrganizationalLevel2s",
7572
+ deletable: false,
7573
+ udfCapable: false,
7574
+ collectionUpdate: true
7575
+ // D1: the spec's item path is GET-only — confirmed updates PUT the collection (id in the body)
7576
+ }, pageSize);
7577
+ }
7578
+ /** Query OrganizationalLevel2 records with the G3 filter surface (server-side JSON filter). */
7579
+ query(params) {
7580
+ return this.queryItems(params);
7581
+ }
7582
+ /** Get every OrganizationalLevel2 record (the MCP-preferred read; 1000-page guarded). */
7583
+ async queryAll(params) {
7584
+ return this.queryAllItems(params);
7585
+ }
7586
+ /** Count matching records (queryCount envelope unwrapped to a plain number). */
7587
+ async count(params) {
7588
+ return this.countGlobal(params);
7589
+ }
7590
+ /** Get one record by id (item envelope; item: null → NotFoundError). */
7591
+ async get(id) {
7592
+ return this.getGlobal(id);
7593
+ }
7594
+ /** Create a record (OperationResultModel → { id}). `dryRun: true` returns the
7595
+ * pinned envelope (section 19.3) and never issues the create. A 2xx that is not the
7596
+ * specced envelope runs the plan-pinned marker recovery (issue #13 — one bounded read;
7597
+ * `recoverIdFromMarker: false` restores the pre-recovery behaviour). */
7598
+ async create(data, opts = {}) {
7599
+ if (opts.dryRun === true) return this.runCreateDryRun(data);
7600
+ return this.createGlobal(data, opts);
7601
+ }
7602
+ /** Update a record (PUT on the COLLECTION — the vendored spec's item path is GET-only; the vendor
7603
+ * keys the record by the id in the body). The vendor validates the FULL model on this route (V2), so a
7604
+ * partial body is merged into the CURRENT record first: one fresh GET (which also serves the
7605
+ * stale-object guard's read) then the collection PUT of the merged model — an explicit null in the
7606
+ * caller body sends null; an omitted field keeps the current value. `raw: true` in the opts bag sends
7607
+ * the body as-is (full model or intentional partial — the compatibility escape hatch; no merge read).
7608
+ * `dryRun: true` returns the pinned envelope whose `data` is the merged body (the exact confirmed
7609
+ * wire) with the best-effort diff, from that same single read; a caller-supplied lastModifiedDate that
7610
+ * mismatches the vendor's current value throws STALE_OBJECT before the mutation. */
7611
+ async update(id, data, opts = {}) {
7612
+ if (opts.dryRun === true) return this.runUpdateDryRun(id, data, opts);
7613
+ return this.updateGlobal(id, data, opts);
7614
+ }
7615
+ };
7616
+
7617
+ // src/resources/organizational_level_associations.ts
7618
+ var OrganizationalLevelAssociationsResource = class extends BaseResource {
7619
+ constructor(http, pageSize) {
7620
+ super(http, {
7621
+ key: "organizational_level_associations",
7622
+ resourcePath: "/V1.0/OrganizationalLevelAssociations",
7623
+ deletable: false,
7624
+ udfCapable: false,
7625
+ collectionUpdate: true
7626
+ // D1: the spec's item path is GET-only — confirmed updates PUT the collection (id in the body)
7627
+ }, pageSize);
7628
+ }
7629
+ /** Query OrganizationalLevelAssociation records with the G3 filter surface (server-side JSON filter). */
7630
+ query(params) {
7631
+ return this.queryItems(params);
7632
+ }
7633
+ /** Get every OrganizationalLevelAssociation record (the MCP-preferred read; 1000-page guarded). */
7634
+ async queryAll(params) {
7635
+ return this.queryAllItems(params);
7636
+ }
7637
+ /** Count matching records (queryCount envelope unwrapped to a plain number). */
7638
+ async count(params) {
7639
+ return this.countGlobal(params);
7640
+ }
7641
+ /** Get one record by id (item envelope; item: null → NotFoundError). */
7642
+ async get(id) {
7643
+ return this.getGlobal(id);
7644
+ }
7645
+ /** Create a record (OperationResultModel → { id}). `dryRun: true` returns the
7646
+ * pinned envelope (section 19.3) and never issues the create. A 2xx that is not the
7647
+ * specced envelope runs the plan-pinned marker recovery (issue #13 — one bounded read;
7648
+ * `recoverIdFromMarker: false` restores the pre-recovery behaviour). */
7649
+ async create(data, opts = {}) {
7650
+ if (opts.dryRun === true) return this.runCreateDryRun(data);
7651
+ return this.createGlobal(data, opts);
7652
+ }
7653
+ /** Update a record (PUT on the COLLECTION — the vendored spec's item path is GET-only; the vendor
7654
+ * keys the record by the id in the body). The vendor validates the FULL model on this route (V2), so a
7655
+ * partial body is merged into the CURRENT record first: one fresh GET (which also serves the
7656
+ * stale-object guard's read) then the collection PUT of the merged model — an explicit null in the
7657
+ * caller body sends null; an omitted field keeps the current value. `raw: true` in the opts bag sends
7658
+ * the body as-is (full model or intentional partial — the compatibility escape hatch; no merge read).
7659
+ * `dryRun: true` returns the pinned envelope whose `data` is the merged body (the exact confirmed
7660
+ * wire) with the best-effort diff, from that same single read; a caller-supplied lastModifiedDate that
7661
+ * mismatches the vendor's current value throws STALE_OBJECT before the mutation. */
7662
+ async update(id, data, opts = {}) {
7663
+ if (opts.dryRun === true) return this.runUpdateDryRun(id, data, opts);
7664
+ return this.updateGlobal(id, data, opts);
7665
+ }
7666
+ };
7667
+
7372
7668
  // src/resources/phases.ts
7373
7669
  var PhasesResource = class extends BaseResource {
7374
7670
  constructor(http, pageSize) {
@@ -8807,6 +9103,49 @@ var RolesResource = class extends BaseResource {
8807
9103
  }
8808
9104
  };
8809
9105
 
9106
+ // src/resources/sales_orders.ts
9107
+ var SalesOrdersResource = class extends BaseResource {
9108
+ constructor(http, pageSize) {
9109
+ super(http, {
9110
+ key: "sales_orders",
9111
+ resourcePath: "/V1.0/SalesOrders",
9112
+ deletable: false,
9113
+ udfCapable: true,
9114
+ collectionUpdate: true
9115
+ // D1: the spec's item path is GET-only — confirmed updates PUT the collection (id in the body)
9116
+ }, pageSize);
9117
+ }
9118
+ /** Query SalesOrder records with the G3 filter surface (server-side JSON filter). */
9119
+ query(params) {
9120
+ return this.queryItems(params);
9121
+ }
9122
+ /** Get every SalesOrder record (the MCP-preferred read; 1000-page guarded). */
9123
+ async queryAll(params) {
9124
+ return this.queryAllItems(params);
9125
+ }
9126
+ /** Count matching records (queryCount envelope unwrapped to a plain number). */
9127
+ async count(params) {
9128
+ return this.countGlobal(params);
9129
+ }
9130
+ /** Get one record by id (item envelope; item: null → NotFoundError). */
9131
+ async get(id) {
9132
+ return this.getGlobal(id);
9133
+ }
9134
+ /** Update a record (PUT on the COLLECTION — the vendored spec's item path is GET-only; the vendor
9135
+ * keys the record by the id in the body). The vendor validates the FULL model on this route (V2), so a
9136
+ * partial body is merged into the CURRENT record first: one fresh GET (which also serves the
9137
+ * stale-object guard's read) then the collection PUT of the merged model — an explicit null in the
9138
+ * caller body sends null; an omitted field keeps the current value. `raw: true` in the opts bag sends
9139
+ * the body as-is (full model or intentional partial — the compatibility escape hatch; no merge read).
9140
+ * `dryRun: true` returns the pinned envelope whose `data` is the merged body (the exact confirmed
9141
+ * wire) with the best-effort diff, from that same single read; a caller-supplied lastModifiedDate that
9142
+ * mismatches the vendor's current value throws STALE_OBJECT before the mutation. */
9143
+ async update(id, data, opts = {}) {
9144
+ if (opts.dryRun === true) return this.runUpdateDryRun(id, data, opts);
9145
+ return this.updateGlobal(id, data, opts);
9146
+ }
9147
+ };
9148
+
8810
9149
  // src/resources/service_calls.ts
8811
9150
  var ServiceCallsResource = class extends BaseResource {
8812
9151
  constructor(http, pageSize) {
@@ -9762,6 +10101,57 @@ var TasksResource = class extends BaseResource {
9762
10101
  }
9763
10102
  };
9764
10103
 
10104
+ // src/resources/tax_regions.ts
10105
+ var TaxRegionsResource = class extends BaseResource {
10106
+ constructor(http, pageSize) {
10107
+ super(http, {
10108
+ key: "tax_regions",
10109
+ resourcePath: "/V1.0/TaxRegions",
10110
+ deletable: false,
10111
+ udfCapable: false,
10112
+ collectionUpdate: true
10113
+ // D1: the spec's item path is GET-only — confirmed updates PUT the collection (id in the body)
10114
+ }, pageSize);
10115
+ }
10116
+ /** Query TaxRegion records with the G3 filter surface (server-side JSON filter). */
10117
+ query(params) {
10118
+ return this.queryItems(params);
10119
+ }
10120
+ /** Get every TaxRegion record (the MCP-preferred read; 1000-page guarded). */
10121
+ async queryAll(params) {
10122
+ return this.queryAllItems(params);
10123
+ }
10124
+ /** Count matching records (queryCount envelope unwrapped to a plain number). */
10125
+ async count(params) {
10126
+ return this.countGlobal(params);
10127
+ }
10128
+ /** Get one record by id (item envelope; item: null → NotFoundError). */
10129
+ async get(id) {
10130
+ return this.getGlobal(id);
10131
+ }
10132
+ /** Create a record (OperationResultModel → { id}). `dryRun: true` returns the
10133
+ * pinned envelope (section 19.3) and never issues the create. A 2xx that is not the
10134
+ * specced envelope runs the plan-pinned marker recovery (issue #13 — one bounded read;
10135
+ * `recoverIdFromMarker: false` restores the pre-recovery behaviour). */
10136
+ async create(data, opts = {}) {
10137
+ if (opts.dryRun === true) return this.runCreateDryRun(data);
10138
+ return this.createGlobal(data, opts);
10139
+ }
10140
+ /** Update a record (PUT on the COLLECTION — the vendored spec's item path is GET-only; the vendor
10141
+ * keys the record by the id in the body). The vendor validates the FULL model on this route (V2), so a
10142
+ * partial body is merged into the CURRENT record first: one fresh GET (which also serves the
10143
+ * stale-object guard's read) then the collection PUT of the merged model — an explicit null in the
10144
+ * caller body sends null; an omitted field keeps the current value. `raw: true` in the opts bag sends
10145
+ * the body as-is (full model or intentional partial — the compatibility escape hatch; no merge read).
10146
+ * `dryRun: true` returns the pinned envelope whose `data` is the merged body (the exact confirmed
10147
+ * wire) with the best-effort diff, from that same single read; a caller-supplied lastModifiedDate that
10148
+ * mismatches the vendor's current value throws STALE_OBJECT before the mutation. */
10149
+ async update(id, data, opts = {}) {
10150
+ if (opts.dryRun === true) return this.runUpdateDryRun(id, data, opts);
10151
+ return this.updateGlobal(id, data, opts);
10152
+ }
10153
+ };
10154
+
9765
10155
  // src/resources/threshold_information.ts
9766
10156
  var ThresholdInformationResource = class {
9767
10157
  http;
@@ -10123,7 +10513,6 @@ var TicketChangeRequestApprovalsResource = class extends BaseResource {
10123
10513
  // src/resources/special/_shared/move-engine.ts
10124
10514
  import { randomUUID } from "crypto";
10125
10515
  var MOVE_SOURCE_AGE_DEFAULT_DAYS = 14;
10126
- var TICKET_COMPLETE_STATUS = 5;
10127
10516
  var MOVE_EFFECTS_NOTE = "Create then delete: the new record gets a fresh create date and creator; creating/deleting may trigger notifications, SLA and workflow rules the API cannot suppress; vendor acceptance of the create is not validated by a dry run";
10128
10517
  var refuse = (code, message, ids2, operation, extra = {}) => new MoveRefusedError(message, { code, operation, details: { status: "failed", ...ids2, ...extra } });
10129
10518
  async function queryRows(http, resource, filter, operation, retries) {
@@ -10176,11 +10565,19 @@ async function preview(http, spec, sourceId, toTicketId, opts) {
10176
10565
  }
10177
10566
  const target = await readTicket(http, toTicketId, opts.maxRetries, op);
10178
10567
  if (target === null) throw refuse("MOVE_TARGET_MISSING", `${op}: target ticket ${toTicketId} does not exist`, ids2, op);
10179
- if (target.status === TICKET_COMPLETE_STATUS || target.completedDate !== void 0 && target.completedDate !== null) {
10180
- throw refuse("MOVE_TARGET_CLOSED", `${op}: target ticket ${toTicketId} is closed (status 5 or completed)`, ids2, op);
10568
+ const closedStatus = await resolveTicketStatusSemantics(http);
10569
+ if (isClosedStatus(target.status, closedStatus.closedIds) || target.completedDate !== void 0 && target.completedDate !== null) {
10570
+ throw refuse(
10571
+ "MOVE_TARGET_CLOSED",
10572
+ `${op}: target ticket ${toTicketId} is closed (status in [${closedStatus.closedIds.join(", ")}] or completed) \u2014 closed ids ${closedStatus.source === "assumed" ? "ASSUMED (the tenant status picklist could not be read)" : "resolved from the tenant Tickets status picklist"}`,
10573
+ ids2,
10574
+ op,
10575
+ { closedStatusIds: closedStatus.closedIds, closedStatusSource: closedStatus.source }
10576
+ );
10181
10577
  }
10182
10578
  const origin = await readTicket(http, sourceTicketId, opts.maxRetries, op);
10183
10579
  const facts = {
10580
+ closedStatus: { closedIds: closedStatus.closedIds, source: closedStatus.source, note: closedStatus.note },
10184
10581
  sourceTicketId,
10185
10582
  sourceCompanyID: origin?.companyID ?? null,
10186
10583
  targetCompanyID: target.companyID ?? null,
@@ -10294,22 +10691,22 @@ function uiLinkFor(http, entity, id) {
10294
10691
  }
10295
10692
 
10296
10693
  // src/resources/special/ticket_charges.ts
10297
- var OP2 = "ticket_charges.moveToTicket";
10694
+ var OP3 = "ticket_charges.moveToTicket";
10298
10695
  var COPY = ["name", "description", "chargeType", "billingCodeID", "productID", "unitQuantity", "unitCost", "unitPrice", "datePurchased", "isBillableToCompany", "notes", "purchaseOrderNumber", "internalPurchaseOrderNumber"];
10299
10696
  var CONTRACT_SCOPED = ["contractServiceID", "contractServiceBundleID"];
10300
10697
  async function readCharge(http, id, retries) {
10301
- const rows = await queryRows(http, "TicketCharges", [{ field: "id", op: "eq", value: id }], OP2, retries);
10698
+ const rows = await queryRows(http, "TicketCharges", [{ field: "id", op: "eq", value: id }], OP3, retries);
10302
10699
  return rows[0] ?? null;
10303
10700
  }
10304
10701
  async function checkSource({ http, source, sourceId, sourceTicketId, toTicketId }) {
10305
10702
  if (source.isBilled === true) {
10306
- throw refuse("MOVE_BLOCKED_BILLED", `${OP2}: ticket charge ${sourceId} is billed \u2014 move it in the Autotask UI`, { sourceId, toTicketId }, OP2, {
10703
+ throw refuse("MOVE_BLOCKED_BILLED", `${OP3}: ticket charge ${sourceId} is billed \u2014 move it in the Autotask UI`, { sourceId, toTicketId }, OP3, {
10307
10704
  manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "tickets", sourceTicketId), reason: "billed charges are not moved by the API (the ticket link opens its charges)" }
10308
10705
  });
10309
10706
  }
10310
10707
  }
10311
10708
  var SPEC = {
10312
- operation: OP2,
10709
+ operation: OP3,
10313
10710
  entity: "ticket_charge",
10314
10711
  readSource: readCharge,
10315
10712
  sourceTicketId: (s) => typeof s.ticketID === "number" ? s.ticketID : null,
@@ -11750,6 +12147,121 @@ async function fullContext(resource, id, opts = {}, http) {
11750
12147
  return result;
11751
12148
  }
11752
12149
 
12150
+ // src/resources/special/tickets/forResource.ts
12151
+ var OP4 = "tickets.forResource";
12152
+ var TICKET_CHUNK = 100;
12153
+ var LIMIT_DEFAULT = 100;
12154
+ var LIMIT_MAX = 500;
12155
+ var LINK_READ_CAP = 500;
12156
+ var TICKET_FIELDS2 = ["id", "ticketNumber", "title", "status", "assignedResourceID", "companyID"];
12157
+ function positiveId(value, name) {
12158
+ const n = typeof value === "string" && /^\d+$/.test(value.trim()) ? Number(value) : value;
12159
+ if (typeof n !== "number" || !Number.isSafeInteger(n) || n < 1) {
12160
+ throw new AutotaskConfigError(`${OP4}: ${name} must be a positive integer id (got ${String(value)})`, { operation: OP4 });
12161
+ }
12162
+ return n;
12163
+ }
12164
+ async function ticketPage(http, filter, maxRecords, fields) {
12165
+ const res = await http.request({
12166
+ method: "POST",
12167
+ idempotent: true,
12168
+ path: "/V1.0/Tickets/query",
12169
+ body: { MaxRecords: maxRecords, filter, IncludeFields: fields },
12170
+ operation: OP4
12171
+ });
12172
+ const page = unwrapItems(res, OP4);
12173
+ return { rows: page.items, more: page.pageDetails.nextPageUrl !== null };
12174
+ }
12175
+ async function forResource(_resource, resourceIDIn, http, opts = {}) {
12176
+ if (opts === null || typeof opts !== "object") throw new AutotaskConfigError(`${OP4}: opts must be an object`, { operation: OP4 });
12177
+ const resourceID = positiveId(resourceIDIn, "resourceID");
12178
+ const scope = opts.scope ?? "both";
12179
+ if (scope !== "primary" && scope !== "secondary" && scope !== "both") {
12180
+ throw new AutotaskConfigError(`${OP4}: scope must be 'primary', 'secondary' or 'both' (got ${JSON.stringify(opts.scope)})`, { operation: OP4 });
12181
+ }
12182
+ if (opts.openOnly !== void 0 && typeof opts.openOnly !== "boolean") {
12183
+ throw new AutotaskConfigError(`${OP4}: openOnly must be a boolean`, { operation: OP4 });
12184
+ }
12185
+ const limit = opts.limit === void 0 ? LIMIT_DEFAULT : opts.limit;
12186
+ if (!Number.isInteger(limit) || limit < 1 || limit > LIMIT_MAX) {
12187
+ throw new AutotaskConfigError(`${OP4}: limit must be an integer 1-${LIMIT_MAX} (got ${String(opts.limit)})`, { operation: OP4 });
12188
+ }
12189
+ const includeFields = opts.includeFields ?? [];
12190
+ if (!Array.isArray(includeFields) || includeFields.some((f) => typeof f !== "string" || f.length === 0)) {
12191
+ throw new AutotaskConfigError(`${OP4}: includeFields must be a list of field names`, { operation: OP4 });
12192
+ }
12193
+ const openOnly = opts.openOnly === true;
12194
+ const fields = [.../* @__PURE__ */ new Set([...TICKET_FIELDS2, ...includeFields])];
12195
+ const closed = await resolveTicketStatusSemantics(http, opts.excludeStatusIds === void 0 ? {} : { excludeStatusIds: opts.excludeStatusIds });
12196
+ const closedStatus = { closedIds: closed.closedIds, source: closed.source, note: closed.note };
12197
+ const openLeaf = openOnly ? [openStatusFilter(closed.closedIds)] : [];
12198
+ const linkIds = [];
12199
+ let capped = false;
12200
+ if (scope !== "primary") {
12201
+ const res = await http.request({
12202
+ method: "POST",
12203
+ idempotent: true,
12204
+ path: "/V1.0/TicketSecondaryResources/query",
12205
+ body: { MaxRecords: LINK_READ_CAP, filter: [{ field: "resourceID", op: "eq", value: resourceID }], IncludeFields: ["ticketID"] },
12206
+ operation: OP4
12207
+ });
12208
+ const page = unwrapItems(res, OP4);
12209
+ for (const row of page.items) if (typeof row.ticketID === "number" && !linkIds.includes(row.ticketID)) linkIds.push(row.ticketID);
12210
+ if (page.items.length >= LINK_READ_CAP || page.pageDetails.nextPageUrl !== null) capped = true;
12211
+ }
12212
+ const primary = /* @__PURE__ */ new Map();
12213
+ if (scope !== "secondary") {
12214
+ const page = await ticketPage(http, [{ field: "assignedResourceID", op: "eq", value: resourceID }, ...openLeaf], limit, fields);
12215
+ if (page.more || page.rows.length >= limit) capped = true;
12216
+ for (const t of page.rows) if (typeof t.id === "number") primary.set(t.id, t);
12217
+ }
12218
+ const secondary = /* @__PURE__ */ new Map();
12219
+ const unresolvedIds = [];
12220
+ for (let c = 0; c < linkIds.length; c += TICKET_CHUNK) {
12221
+ const chunk = linkIds.slice(c, c + TICKET_CHUNK);
12222
+ const page = await ticketPage(http, [{ field: "id", op: "in", value: chunk }, ...openLeaf], chunk.length, fields);
12223
+ const seen = /* @__PURE__ */ new Set();
12224
+ for (const t of page.rows) {
12225
+ if (typeof t.id !== "number") continue;
12226
+ secondary.set(t.id, t);
12227
+ seen.add(t.id);
12228
+ }
12229
+ for (const id of chunk) if (!seen.has(id)) unresolvedIds.push(id);
12230
+ }
12231
+ const merged = /* @__PURE__ */ new Map();
12232
+ for (const [id, t] of primary) merged.set(id, t);
12233
+ for (const [id, t] of secondary) if (!merged.has(id)) merged.set(id, t);
12234
+ let tickets = [...merged.values()];
12235
+ if (openOnly) tickets = tickets.filter((t) => !isClosedStatus(t.status, closed.closedIds));
12236
+ tickets.sort((a, b) => Number(a.id) - Number(b.id));
12237
+ if (tickets.length > limit) {
12238
+ tickets = tickets.slice(0, limit);
12239
+ capped = true;
12240
+ }
12241
+ const openCount = tickets.filter((t) => !isClosedStatus(t.status, closed.closedIds)).length;
12242
+ const primaryIds = [...primary.keys()].sort((a, b) => a - b);
12243
+ const secondaryIds = [...secondary.keys()].sort((a, b) => a - b);
12244
+ const both = primaryIds.filter((id) => secondary.has(id));
12245
+ return {
12246
+ resourceID,
12247
+ scope,
12248
+ openOnly,
12249
+ tickets,
12250
+ primaryIds,
12251
+ secondaryIds,
12252
+ bothIds: both,
12253
+ counts: { primary: primaryIds.length, secondary: secondaryIds.length, both: both.length, open: openCount },
12254
+ capped,
12255
+ unresolvedIds: unresolvedIds.sort((a, b) => a - b),
12256
+ closedStatus
12257
+ };
12258
+ }
12259
+
12260
+ // src/resources/special/tickets/statusSemantics.ts
12261
+ async function statusSemantics(_resource, opts = {}, http) {
12262
+ return resolveTicketStatusSemantics(http, opts);
12263
+ }
12264
+
11753
12265
  // src/resources/tickets.ts
11754
12266
  var TicketsResource = class extends BaseResource {
11755
12267
  constructor(http, pageSize) {
@@ -11855,6 +12367,14 @@ var TicketsResource = class extends BaseResource {
11855
12367
  fullContext(id, opts) {
11856
12368
  return fullContext(this, id, opts, this.http);
11857
12369
  }
12370
+ /** Node-parity special op (hand-written in src/resources/special/tickets.ts). */
12371
+ statusSemantics(opts) {
12372
+ return statusSemantics(this, opts, this.http);
12373
+ }
12374
+ /** Node-parity special op (hand-written in src/resources/special/tickets.ts). */
12375
+ forResource(resourceID, opts) {
12376
+ return forResource(this, resourceID, this.http, opts);
12377
+ }
11858
12378
  };
11859
12379
 
11860
12380
  // src/resources/ticket_webhooks.ts
@@ -11983,12 +12503,12 @@ async function postedDateStatusQuery(_resource, mode, http, opts) {
11983
12503
  return items;
11984
12504
  }
11985
12505
  async function postedWithin(_resource, from, to, http, opts, opLabel = "time_entries.postedWithin") {
11986
- const OP6 = opLabel;
11987
- if (opts?.tz !== void 0) validateZone(opts.tz, OP6);
11988
- const fromB = customRangeDay(from, "from", void 0, OP6);
11989
- const toB = customRangeDay(to, "to", void 0, OP6);
12506
+ const OP9 = opLabel;
12507
+ if (opts?.tz !== void 0) validateZone(opts.tz, OP9);
12508
+ const fromB = customRangeDay(from, "from", void 0, OP9);
12509
+ const toB = customRangeDay(to, "to", void 0, OP9);
11990
12510
  if (fromB.ms > toB.ms) {
11991
- throw new AutotaskConfigError(`${OP6}: from (${iso2(fromB.ms)}) must not be after to (${iso2(toB.ms)})`, { operation: OP6 });
12511
+ throw new AutotaskConfigError(`${OP9}: from (${iso2(fromB.ms)}) must not be after to (${iso2(toB.ms)})`, { operation: OP9 });
11992
12512
  }
11993
12513
  const windowToMs = toB.ms + DAY_MS2 - 1;
11994
12514
  const filter = [
@@ -11999,7 +12519,7 @@ async function postedWithin(_resource, from, to, http, opts, opLabel = "time_ent
11999
12519
  else if (opts?.posted === false) filter.push({ field: "postedDate", op: "notExist" });
12000
12520
  if (opts?.ticketID !== void 0) filter.push({ field: "ticketID", op: "eq", value: opts.ticketID });
12001
12521
  if (opts?.resourceID !== void 0) filter.push({ field: "resourceID", op: "eq", value: opts.resourceID });
12002
- const { items, hasMore } = await queryTimeEntries(_resource, filter, clampWindowLimit(opts?.limit), OP6, http);
12522
+ const { items, hasMore } = await queryTimeEntries(_resource, filter, clampWindowLimit(opts?.limit), OP9, http);
12003
12523
  let postedCount = 0;
12004
12524
  for (const item of items) {
12005
12525
  const postedDate = item.postedDate;
@@ -12209,7 +12729,7 @@ async function resolveCode(http, family, familyLabel2, pinnedId, departments, op
12209
12729
  }
12210
12730
 
12211
12731
  // src/resources/special/time_entries/createForResource.ts
12212
- var OP3 = "time_entries.createForResource";
12732
+ var OP5 = "time_entries.createForResource";
12213
12733
  var PARAM_KEYS = /* @__PURE__ */ new Set([
12214
12734
  "internallAllocationCodeID",
12215
12735
  "ticketID",
@@ -12245,30 +12765,30 @@ function validateCreateParams(params) {
12245
12765
  if (pinCount > 1) {
12246
12766
  throw new AutotaskConfigError(
12247
12767
  "createForResource: exactly one linkage pin is honoured (internallAllocationCodeID | ticketID | taskID) \u2014 the vendor gate requires exactly one; pass one pin (or none for the default internal linkage)",
12248
- { operation: OP3 }
12768
+ { operation: OP5 }
12249
12769
  );
12250
12770
  }
12251
12771
  if (pins.role !== void 0 && pins.code !== void 0) {
12252
12772
  throw new AutotaskConfigError(
12253
12773
  "createForResource: a pinned roleID is only meaningful with the task/ticket linkage (internallAllocationCodeID pins the internal linkage and owns the code) \u2014 drop one",
12254
- { operation: OP3 }
12774
+ { operation: OP5 }
12255
12775
  );
12256
12776
  }
12257
12777
  if (params.hoursWorked !== void 0 && (typeof params.hoursWorked !== "number" || !Number.isFinite(params.hoursWorked) || params.hoursWorked <= 0)) {
12258
12778
  throw new AutotaskConfigError(
12259
12779
  `createForResource: hoursWorked must be a positive finite number (got ${String(params.hoursWorked)})`,
12260
- { operation: OP3 }
12780
+ { operation: OP5 }
12261
12781
  );
12262
12782
  }
12263
12783
  return pins;
12264
12784
  }
12265
12785
  async function assertResourceActive(http, rid) {
12266
- const rec = await fetchItem2(http, `/V1.0/Resources/${rid}`, OP3);
12786
+ const rec = await fetchItem2(http, `/V1.0/Resources/${rid}`, OP5);
12267
12787
  if (rec === null) {
12268
12788
  throw new ResourceInvalidError(`Resource ${rid} was not found \u2014 a time entry cannot attach to a missing resource`, {
12269
12789
  resourceIds: [rid],
12270
12790
  details: { idRefs: [{ entity: "resource", id: rid }] },
12271
- operation: OP3,
12791
+ operation: OP5,
12272
12792
  category: "not_found",
12273
12793
  suggestedAction: "Resolve the resource id (resources.get / resources.search) and retry."
12274
12794
  });
@@ -12277,7 +12797,7 @@ async function assertResourceActive(http, rid) {
12277
12797
  throw new ResourceInvalidError(`Resource ${rid} is not active (isActive !== true) \u2014 the vendor would reject a time entry for an inactive resource`, {
12278
12798
  resourceIds: [rid],
12279
12799
  details: { idRefs: [{ entity: "resource", id: rid }] },
12280
- operation: OP3,
12800
+ operation: OP5,
12281
12801
  suggestedAction: "Activate the resource or pick an active one."
12282
12802
  });
12283
12803
  }
@@ -12287,11 +12807,11 @@ async function resolveAllocationCode(http, rid, linkage, pins, departments) {
12287
12807
  const pinnedForCode = pins.code !== void 0 ? pins.code : pins.role;
12288
12808
  let code = null;
12289
12809
  const familiesTried = [];
12290
- code = await resolveCode(http, familyOf(linkage), familyLabel(familyOf(linkage)), pinnedForCode, departments, OP3);
12810
+ code = await resolveCode(http, familyOf(linkage), familyLabel(familyOf(linkage)), pinnedForCode, departments, OP5);
12291
12811
  familiesTried.push({ family: familyOf(linkage), label: familyLabel(familyOf(linkage)), filters: [{ field: "useType", op: "eq", value: familyOf(linkage) }, { field: "isActive", op: "eq", value: true }] });
12292
12812
  if (code === null && linkage === "task" && pins.role === void 0) {
12293
12813
  usedLinkage = "internal";
12294
- code = await resolveCode(http, USE_TYPE_INTERNAL, familyLabel(USE_TYPE_INTERNAL), void 0, departments, OP3);
12814
+ code = await resolveCode(http, USE_TYPE_INTERNAL, familyLabel(USE_TYPE_INTERNAL), void 0, departments, OP5);
12295
12815
  familiesTried.push({ family: USE_TYPE_INTERNAL, label: familyLabel(USE_TYPE_INTERNAL), filters: [{ field: "useType", op: "eq", value: USE_TYPE_INTERNAL }, { field: "isActive", op: "eq", value: true }] });
12296
12816
  }
12297
12817
  if (code === null) {
@@ -12301,7 +12821,7 @@ async function resolveAllocationCode(http, rid, linkage, pins, departments) {
12301
12821
  families: familiesTried,
12302
12822
  resourceDepartments: [...departments].sort((a, b) => a - b)
12303
12823
  },
12304
- { operation: OP3 }
12824
+ { operation: OP5 }
12305
12825
  );
12306
12826
  }
12307
12827
  const resolvedCode = codeInfo(code);
@@ -12340,7 +12860,7 @@ function createDryRun2(http, rid, body, linkage, usedLinkage, resolvedCode) {
12340
12860
  }
12341
12861
  return buildDryRunResult(
12342
12862
  {
12343
- operation: OP3,
12863
+ operation: OP5,
12344
12864
  method: "POST",
12345
12865
  path: "/V1.0/TimeEntries",
12346
12866
  ids: [rid],
@@ -12382,7 +12902,7 @@ async function createWithAttempts(resource, http, opts, body, defaults, pins, us
12382
12902
  const gate3 = ROLE_REJECTION_PATTERN.test(text);
12383
12903
  if (usedLinkage !== "internal" && gate3) {
12384
12904
  if (!taskRoleFetched && usedLinkage === "task" && pins.task !== void 0) {
12385
- const task = await fetchItem2(http, `/V1.0/Tasks/${pins.task}`, OP3);
12905
+ const task = await fetchItem2(http, `/V1.0/Tasks/${pins.task}`, OP5);
12386
12906
  taskRoleFetched = true;
12387
12907
  if (task !== null) pushLadder(task.assignedResourceRoleID);
12388
12908
  }
@@ -12408,7 +12928,7 @@ async function createForResource(resource, resourceID, params, opts = {}, http)
12408
12928
  const pins = validateCreateParams(params);
12409
12929
  await assertResourceActive(http, rid);
12410
12930
  const linkage = pins.code !== void 0 ? "internal" : pins.ticket !== void 0 ? "ticket" : pins.task !== void 0 ? "task" : "internal";
12411
- const roleRows = await resourceRoleRows(http, rid, OP3);
12931
+ const roleRows = await resourceRoleRows(http, rid, OP5);
12412
12932
  const departments = roleRows.departments;
12413
12933
  const { usedLinkage, resolvedCode } = await resolveAllocationCode(http, rid, linkage, pins, departments);
12414
12934
  const defaults = windowDefaults(params);
@@ -12431,12 +12951,12 @@ function discoveryField(err, body, defaults) {
12431
12951
  }
12432
12952
 
12433
12953
  // src/resources/special/time_entries/move.ts
12434
- var OP4 = "time_entries.moveToTicket";
12954
+ var OP6 = "time_entries.moveToTicket";
12435
12955
  var COPY2 = ["resourceID", "roleID", "billingCodeID", "dateWorked", "startDateTime", "endDateTime", "hoursWorked", "offsetHours", "summaryNotes", "internalNotes", "isNonBillable", "showOnInvoice"];
12436
12956
  var CONTRACT_SCOPED2 = ["contractID", "contractServiceID", "contractServiceBundleID"];
12437
12957
  async function readTimeEntry(http, id, retries) {
12438
12958
  try {
12439
- const res = await http.request({ method: "GET", path: `/V1.0/TimeEntries/${id}`, operation: OP4, maxRetries: retries });
12959
+ const res = await http.request({ method: "GET", path: `/V1.0/TimeEntries/${id}`, operation: OP6, maxRetries: retries });
12440
12960
  return res?.item ?? null;
12441
12961
  } catch (e) {
12442
12962
  if (isAutotaskError(e) && (e.httpStatus === 404 || e.code === "NOT_FOUND")) return null;
@@ -12446,27 +12966,27 @@ async function readTimeEntry(http, id, retries) {
12446
12966
  async function checkSource2({ http, source, sourceId, toTicketId, retries }) {
12447
12967
  const ids2 = { sourceId, toTicketId };
12448
12968
  if (source.taskID !== null && source.taskID !== void 0) {
12449
- throw refuse("MOVE_SOURCE_UNSUPPORTED", `${OP4}: time entry ${sourceId} is on a task \u2014 only ticket time entries are moved`, ids2, OP4);
12969
+ throw refuse("MOVE_SOURCE_UNSUPPORTED", `${OP6}: time entry ${sourceId} is on a task \u2014 only ticket time entries are moved`, ids2, OP6);
12450
12970
  }
12451
12971
  if (source.billingApprovalDateTime || source.billingApprovalResourceID || source.billingApprovalLevelMostRecent) {
12452
- throw refuse("MOVE_BLOCKED_APPROVED", `${OP4}: time entry ${sourceId} has billing approval set \u2014 move it in the Autotask UI`, ids2, OP4, { manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "time_entries", sourceId), reason: "approved entries are not moved by the API" } });
12972
+ throw refuse("MOVE_BLOCKED_APPROVED", `${OP6}: time entry ${sourceId} has billing approval set \u2014 move it in the Autotask UI`, ids2, OP6, { manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "time_entries", sourceId), reason: "approved entries are not moved by the API" } });
12453
12973
  }
12454
12974
  let billing;
12455
12975
  try {
12456
- billing = await queryRows(http, "BillingItems", [{ field: "timeEntryID", op: "eq", value: sourceId }], OP4, retries);
12976
+ billing = await queryRows(http, "BillingItems", [{ field: "timeEntryID", op: "eq", value: sourceId }], OP6, retries);
12457
12977
  } catch {
12458
- throw refuse("MOVE_BLOCKED_BILLED", `${OP4}: the billing items of time entry ${sourceId} could not be read \u2014 refused (fails closed)`, ids2, OP4, { manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "time_entries", sourceId), reason: "billing state unreadable" } });
12978
+ throw refuse("MOVE_BLOCKED_BILLED", `${OP6}: the billing items of time entry ${sourceId} could not be read \u2014 refused (fails closed)`, ids2, OP6, { manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "time_entries", sourceId), reason: "billing state unreadable" } });
12459
12979
  }
12460
12980
  if (billing.some((b) => b.invoiceID || b.postedDate)) {
12461
- throw refuse("MOVE_BLOCKED_BILLED", `${OP4}: time entry ${sourceId} is billed or posted \u2014 move it in the Autotask UI`, ids2, OP4, { manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "time_entries", sourceId), reason: "billed entries are not moved by the API" } });
12981
+ throw refuse("MOVE_BLOCKED_BILLED", `${OP6}: time entry ${sourceId} is billed or posted \u2014 move it in the Autotask UI`, ids2, OP6, { manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "time_entries", sourceId), reason: "billed entries are not moved by the API" } });
12462
12982
  }
12463
- const attachments = await queryRows(http, "TimeEntryAttachments", [{ field: "timeEntryID", op: "eq", value: sourceId }], OP4, retries);
12983
+ const attachments = await queryRows(http, "TimeEntryAttachments", [{ field: "timeEntryID", op: "eq", value: sourceId }], OP6, retries);
12464
12984
  if (attachments.length > 0) {
12465
- throw refuse("MOVE_BLOCKED_ATTACHMENTS", `${OP4}: time entry ${sourceId} has attachments, which a delete would destroy \u2014 move it in the Autotask UI`, ids2, OP4, { manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "time_entries", sourceId), reason: "entries with attachments are not moved by the API" } });
12985
+ throw refuse("MOVE_BLOCKED_ATTACHMENTS", `${OP6}: time entry ${sourceId} has attachments, which a delete would destroy \u2014 move it in the Autotask UI`, ids2, OP6, { manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "time_entries", sourceId), reason: "entries with attachments are not moved by the API" } });
12466
12986
  }
12467
12987
  }
12468
12988
  var SPEC2 = {
12469
- operation: OP4,
12989
+ operation: OP6,
12470
12990
  entity: "time_entry",
12471
12991
  readSource: readTimeEntry,
12472
12992
  sourceTicketId: (s) => typeof s.ticketID === "number" ? s.ticketID : null,
@@ -12500,29 +13020,116 @@ function moveToTicket2(_resource, sourceId, toTicketId, opts, http) {
12500
13020
  return runMove(http, SPEC2, sourceId, toTicketId, opts);
12501
13021
  }
12502
13022
 
13023
+ // src/resources/special/time_entries/approve.ts
13024
+ var OP7 = "time_entries.approve";
13025
+ var APPROVAL_LEVELS_PATH = "/V1.0/BillingItemApprovalLevels";
13026
+ function toIso(value) {
13027
+ if (value instanceof Date) {
13028
+ if (Number.isNaN(value.getTime())) throw new AutotaskConfigError("time_entries.approve: approvalDateTime is an invalid Date");
13029
+ return value.toISOString();
13030
+ }
13031
+ const parsed = new Date(value);
13032
+ if (Number.isNaN(parsed.getTime())) throw new AutotaskConfigError(`time_entries.approve: approvalDateTime "${value}" is not a parseable date`);
13033
+ return parsed.toISOString();
13034
+ }
13035
+ function toNumber(value) {
13036
+ return typeof value === "number" ? value : Number(value);
13037
+ }
13038
+ function sameInstant(a, b) {
13039
+ if (typeof a !== "string" || a === "") return false;
13040
+ const left = new Date(a).getTime();
13041
+ const right = new Date(b).getTime();
13042
+ return !Number.isNaN(left) && left === right;
13043
+ }
13044
+ async function approve(resource, id, opts = {}, http) {
13045
+ const numericId = toNumber(parseIdentifier(id, "id"));
13046
+ if (opts.approvalLevel !== void 0 && (!Number.isInteger(opts.approvalLevel) || opts.approvalLevel < 1)) {
13047
+ throw new AutotaskConfigError(`time_entries.approve: approvalLevel must be a positive integer (got ${String(opts.approvalLevel)})`);
13048
+ }
13049
+ const approvalLevel = opts.approvalLevel ?? 1;
13050
+ const approvalDateTime = toIso(opts.approvalDateTime ?? /* @__PURE__ */ new Date());
13051
+ const entry = await resource.get(numericId);
13052
+ const approvalResourceID = opts.approvalResourceID === void 0 ? entry.resourceID : toNumber(parseIdentifier(opts.approvalResourceID, "approvalResourceID"));
13053
+ if (approvalResourceID === void 0) {
13054
+ throw new AutotaskConfigError(
13055
+ `time_entries.approve: time entry ${numericId} carries no resourceID and the caller supplied no approvalResourceID \u2014 the vendor requires an approving resource`
13056
+ );
13057
+ }
13058
+ const body = { timeEntryID: numericId, approvalLevel, approvalResourceID, approvalDateTime };
13059
+ if (opts.dryRun === true) {
13060
+ return buildDryRunResult(
13061
+ {
13062
+ operation: OP7,
13063
+ method: "POST",
13064
+ path: APPROVAL_LEVELS_PATH,
13065
+ ids: [numericId],
13066
+ checks: [
13067
+ { name: "input-validation", ok: true },
13068
+ { name: "time-entry-exists", ok: true }
13069
+ ],
13070
+ impact: { affected: 1, scope: "single-record", reversible: false },
13071
+ warnings: [
13072
+ "two calls: POST the BillingItemApprovalLevels record, then PUT the time entry (billingApprovalDateTime + billingApprovalResourceID) \u2014 the vendor sets BillingApprovalLevelMostRecent from the create but not these two fields",
13073
+ "approval is not posting: the API exposes no post action on BillingItems, so the entry stays unposted until a vendor workflow posts it",
13074
+ `approvalResourceID ${approvalResourceID} is not existence-checked by the dry run`
13075
+ ],
13076
+ data: body
13077
+ },
13078
+ { redactPersonalData: http.redactPersonalData }
13079
+ );
13080
+ }
13081
+ const created = await http.request({
13082
+ method: "POST",
13083
+ path: APPROVAL_LEVELS_PATH,
13084
+ body,
13085
+ operation: OP7,
13086
+ maxRetries: opts.maxRetries
13087
+ });
13088
+ const levelId = toNumber(parseIdentifier(unwrapOperation(created, OP7).id, "billingItemApprovalLevelID"));
13089
+ await resource.update(
13090
+ numericId,
13091
+ { billingApprovalDateTime: approvalDateTime, billingApprovalResourceID: approvalResourceID },
13092
+ { maxRetries: opts.maxRetries, lastModifiedDate: opts.lastModifiedDate ?? entry.lastModifiedDateTime }
13093
+ );
13094
+ const fresh = await resource.get(numericId);
13095
+ const persisted = sameInstant(fresh.billingApprovalDateTime, approvalDateTime) && fresh.billingApprovalResourceID === approvalResourceID;
13096
+ return {
13097
+ timeEntryID: numericId,
13098
+ billingItemApprovalLevelID: levelId,
13099
+ approvalLevel,
13100
+ approvalResourceID,
13101
+ approvalDateTime,
13102
+ billingApprovalLevelMostRecent: fresh.billingApprovalLevelMostRecent ?? null,
13103
+ billingApprovalDateTime: fresh.billingApprovalDateTime ?? null,
13104
+ billingApprovalResourceID: fresh.billingApprovalResourceID ?? null,
13105
+ status: persisted ? "approved" : "approval_fields_not_persisted",
13106
+ persisted
13107
+ };
13108
+ }
13109
+
12503
13110
  // src/resources/special/time_entries/recentTickets.ts
12504
- var OP5 = "time_entries.recentTickets";
13111
+ var OP8 = "time_entries.recentTickets";
12505
13112
  var DAY_MS3 = 864e5;
12506
- var TICKET_CHUNK = 100;
12507
- var TICKET_FIELDS2 = ["id", "ticketNumber", "title", "status", "assignedResourceID", "companyID"];
12508
- function positiveId(value, name) {
13113
+ var TICKET_CHUNK2 = 100;
13114
+ var TICKET_FIELDS3 = ["id", "ticketNumber", "title", "status", "assignedResourceID", "companyID"];
13115
+ function positiveId2(value, name) {
12509
13116
  const n = typeof value === "string" && /^\d+$/.test(value.trim()) ? Number(value) : value;
12510
13117
  if (typeof n !== "number" || !Number.isSafeInteger(n) || n < 1) {
12511
- throw new AutotaskConfigError(`${OP5}: ${name} must be a positive integer id (got ${String(value)})`, { operation: OP5 });
13118
+ throw new AutotaskConfigError(`${OP8}: ${name} must be a positive integer id (got ${String(value)})`, { operation: OP8 });
12512
13119
  }
12513
13120
  return n;
12514
13121
  }
12515
13122
  async function recentTickets(_resource, from, to, http, opts) {
12516
13123
  if (opts === void 0 || opts === null || typeof opts !== "object") {
12517
- throw new AutotaskConfigError(`${OP5}: opts { resourceID, companyID?, tz?, limit? } is required`, { operation: OP5 });
13124
+ throw new AutotaskConfigError(`${OP8}: opts { resourceID, companyID?, tz?, limit? } is required`, { operation: OP8 });
12518
13125
  }
12519
- const resourceID = positiveId(opts.resourceID, "resourceID");
12520
- const companyID = opts.companyID === void 0 ? void 0 : positiveId(opts.companyID, "companyID");
12521
- if (opts.tz !== void 0) validateZone(opts.tz, OP5);
12522
- const fromB = customRangeDay(from, "from", void 0, OP5);
12523
- const toB = customRangeDay(to, "to", void 0, OP5);
13126
+ const resourceID = positiveId2(opts.resourceID, "resourceID");
13127
+ const companyID = opts.companyID === void 0 ? void 0 : positiveId2(opts.companyID, "companyID");
13128
+ if (opts.tz !== void 0) validateZone(opts.tz, OP8);
13129
+ const fromB = customRangeDay(from, "from", void 0, OP8);
13130
+ const toB = customRangeDay(to, "to", void 0, OP8);
12524
13131
  if (fromB.ms > toB.ms) {
12525
- throw new AutotaskConfigError(`${OP5}: from (${isoUtc(fromB.ms)}) must not be after to (${isoUtc(toB.ms)})`, { operation: OP5 });
13132
+ throw new AutotaskConfigError(`${OP8}: from (${isoUtc(fromB.ms)}) must not be after to (${isoUtc(toB.ms)})`, { operation: OP8 });
12526
13133
  }
12527
13134
  const windowToMs = toB.ms + DAY_MS3 - 1;
12528
13135
  const { items: entries, hasMore } = await queryTimeEntries(
@@ -12533,7 +13140,7 @@ async function recentTickets(_resource, from, to, http, opts) {
12533
13140
  { field: "resourceID", op: "eq", value: resourceID }
12534
13141
  ],
12535
13142
  clampWindowLimit(opts.limit),
12536
- OP5,
13143
+ OP8,
12537
13144
  http
12538
13145
  );
12539
13146
  const groups = /* @__PURE__ */ new Map();
@@ -12551,17 +13158,17 @@ async function recentTickets(_resource, from, to, http, opts) {
12551
13158
  }
12552
13159
  const ids2 = [...groups.keys()];
12553
13160
  const tickets = /* @__PURE__ */ new Map();
12554
- for (let c = 0; c < ids2.length; c += TICKET_CHUNK) {
12555
- const chunk = ids2.slice(c, c + TICKET_CHUNK);
13161
+ for (let c = 0; c < ids2.length; c += TICKET_CHUNK2) {
13162
+ const chunk = ids2.slice(c, c + TICKET_CHUNK2);
12556
13163
  const filter = [{ field: "id", op: "in", value: chunk }];
12557
13164
  const res = await http.request({
12558
13165
  method: "POST",
12559
13166
  idempotent: true,
12560
13167
  path: "/V1.0/Tickets/query",
12561
- body: { MaxRecords: TICKET_CHUNK, filter, IncludeFields: TICKET_FIELDS2 },
12562
- operation: OP5
13168
+ body: { MaxRecords: TICKET_CHUNK2, filter, IncludeFields: TICKET_FIELDS3 },
13169
+ operation: OP8
12563
13170
  });
12564
- for (const t of unwrapItems(res, OP5).items) if (typeof t.id === "number") tickets.set(t.id, t);
13171
+ for (const t of unwrapItems(res, OP8).items) if (typeof t.id === "number") tickets.set(t.id, t);
12565
13172
  }
12566
13173
  const items = [];
12567
13174
  const unresolvedTicketIDs = [];
@@ -12665,6 +13272,10 @@ var TimeEntriesResource = class extends BaseResource {
12665
13272
  return this.resolveBase(identifier);
12666
13273
  }
12667
13274
  /** Node-parity special op (hand-written in src/resources/special/time_entries.ts). */
13275
+ approve(id, opts = {}) {
13276
+ return approve(this, id, opts, this.http);
13277
+ }
13278
+ /** Node-parity special op (hand-written in src/resources/special/time_entries.ts). */
12668
13279
  createForResource(resourceID, params, opts = {}) {
12669
13280
  return createForResource(this, resourceID, params, opts, this.http);
12670
13281
  }
@@ -12805,7 +13416,7 @@ var TimeEntryAttachmentsResource = class extends BaseResource {
12805
13416
  };
12806
13417
 
12807
13418
  // src/resources/special/time_off_requests.ts
12808
- async function approve(_resource, id, opts = {}, http) {
13419
+ async function approve2(_resource, id, opts = {}, http) {
12809
13420
  const numericId = parseIdentifier(id, "id");
12810
13421
  if (opts.dryRun === true) {
12811
13422
  return buildDryRunResult({
@@ -12893,7 +13504,7 @@ var TimeOffRequestsResource = class extends BaseResource {
12893
13504
  }
12894
13505
  /** Node-parity special op (hand-written in src/resources/special/time_off_requests.ts). */
12895
13506
  approve(id, opts = {}) {
12896
- return approve(this, id, opts, this.http);
13507
+ return approve2(this, id, opts, this.http);
12897
13508
  }
12898
13509
  /** Node-parity special op (hand-written in src/resources/special/time_off_requests.ts). */
12899
13510
  reject(id, reason, opts = {}) {
@@ -12965,6 +13576,7 @@ export {
12965
13576
  AppointmentsResource,
12966
13577
  AuthenticateResource,
12967
13578
  BillingCodesResource,
13579
+ BillingItemApprovalLevelsResource,
12968
13580
  BillingItemsResource,
12969
13581
  ChangeRequestLinksResource,
12970
13582
  ChecklistLibrariesResource,
@@ -13024,11 +13636,15 @@ export {
13024
13636
  HolidaysResource,
13025
13637
  InventoryProductsResource,
13026
13638
  InvoicesResource,
13639
+ InvoiceTemplatesResource,
13027
13640
  ModulesResource,
13028
13641
  NotificationHistoryResource,
13029
13642
  OpportunitiesResource,
13030
13643
  OpportunityAttachmentsResource,
13031
13644
  OpportunityCategoriesResource,
13645
+ OrganizationalLevel1sResource,
13646
+ OrganizationalLevel2sResource,
13647
+ OrganizationalLevelAssociationsResource,
13032
13648
  PhasesResource,
13033
13649
  ProductsResource,
13034
13650
  ProductVendorsResource,
@@ -13051,6 +13667,7 @@ export {
13051
13667
  ResourceTimeOffApproversResource,
13052
13668
  ResourceTimeOffBalancesResource,
13053
13669
  RolesResource,
13670
+ SalesOrdersResource,
13054
13671
  ServiceCallsResource,
13055
13672
  ServiceCallTaskResourcesResource,
13056
13673
  ServiceCallTasksResource,
@@ -13068,6 +13685,7 @@ export {
13068
13685
  TagsResource,
13069
13686
  TaskSecondaryResourcesResource,
13070
13687
  TasksResource,
13688
+ TaxRegionsResource,
13071
13689
  ThresholdInformationResource,
13072
13690
  TicketAdditionalConfigurationItemsResource,
13073
13691
  TicketAdditionalContactsResource,