node-autotask 0.27.0 → 0.29.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 (41) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/MCP_TOOL_CATALOG.json +63 -8
  3. package/MCP_TOOL_MANIFEST.md +14 -11
  4. package/N8N_MCP_MANIFEST.json +84 -5
  5. package/N8N_MCP_MANIFEST.md +10 -7
  6. package/README.md +1 -2
  7. package/capabilities.json +1774 -1308
  8. package/dist/capabilities.cjs +2 -2
  9. package/dist/capabilities.js +1 -1
  10. package/dist/{chunk-UHTZLUDE.cjs → chunk-APXBBN3D.cjs} +143 -143
  11. package/dist/{chunk-5GGCSRT4.cjs → chunk-BNIOQEKP.cjs} +840 -586
  12. package/dist/{chunk-ZJ4ONAQF.cjs → chunk-IKWHRUQ5.cjs} +97 -6
  13. package/dist/{chunk-42ZUISDH.js → chunk-KVT5V7SA.js} +93 -2
  14. package/dist/{chunk-5CC7LGNM.cjs → chunk-PY45RBQ2.cjs} +1773 -1307
  15. package/dist/{chunk-ZGTFVZL5.js → chunk-T4SZGOJB.js} +333 -79
  16. package/dist/{chunk-F7FVOXPQ.js → chunk-WKP442HS.js} +1773 -1307
  17. package/dist/{chunk-GURC52J7.js → chunk-X4TUU2Y6.js} +2 -2
  18. package/dist/{index-BIWy9m-a.d.ts → index-6_i-__sN.d.ts} +103 -3
  19. package/dist/{index-DQ9NgraW.d.cts → index-BGZ1Mhbh.d.cts} +105 -82
  20. package/dist/{index-DQ9NgraW.d.ts → index-BGZ1Mhbh.d.ts} +105 -82
  21. package/dist/{index-D4XYtn4s.d.cts → index-BZkfUU59.d.cts} +103 -3
  22. package/dist/index.cjs +22 -22
  23. package/dist/index.d.cts +4 -4
  24. package/dist/index.d.ts +4 -4
  25. package/dist/index.js +4 -4
  26. package/dist/operations/index.cjs +112 -112
  27. package/dist/operations/index.d.cts +4 -4
  28. package/dist/operations/index.d.ts +4 -4
  29. package/dist/operations/index.js +2 -2
  30. package/dist/pool.cjs +9 -9
  31. package/dist/pool.d.cts +2 -2
  32. package/dist/pool.d.ts +2 -2
  33. package/dist/pool.js +4 -4
  34. package/dist/resources/index.cjs +4 -4
  35. package/dist/resources/index.d.cts +2 -2
  36. package/dist/resources/index.d.ts +2 -2
  37. package/dist/resources/index.js +3 -3
  38. package/dist/types/index.d.cts +1 -1
  39. package/dist/types/index.d.ts +1 -1
  40. package/docs/API.md +40 -3
  41. package/package.json +1 -1
@@ -34,14 +34,16 @@ import {
34
34
  parseVendorErrors,
35
35
  resolveCompositeNames,
36
36
  resolveFieldsPreset,
37
+ resolveLineOfBusinessChain,
37
38
  truncateNoteChars,
38
39
  unwrapItem,
39
40
  unwrapItems,
40
- unwrapOperation
41
- } from "./chunk-42ZUISDH.js";
41
+ unwrapOperation,
42
+ vendorId
43
+ } from "./chunk-KVT5V7SA.js";
42
44
  import {
43
45
  CAPABILITIES
44
- } from "./chunk-F7FVOXPQ.js";
46
+ } from "./chunk-WKP442HS.js";
45
47
 
46
48
  // src/resources/appointments.ts
47
49
  var AppointmentsResource = class extends BaseResource {
@@ -1291,6 +1293,82 @@ async function upsertByName(resource, companyName, params, opts, http) {
1291
1293
  return createWithDiscovery(resource, body, owner, caveat, opts);
1292
1294
  }
1293
1295
 
1296
+ // src/resources/special/_shared/ticketStatus.ts
1297
+ var ASSUMED_CLOSED_STATUS_ID = 5;
1298
+ var CLOSED_STATUS_LABELS = ["complete", "completed", "closed", "cancelled", "canceled", "resolved"];
1299
+ var OP = "tickets.statusSemantics";
1300
+ function picklistOf(fields) {
1301
+ if (!Array.isArray(fields)) return [];
1302
+ const status = fields.find((f) => typeof f?.name === "string" && f.name.toLowerCase() === "status");
1303
+ if (status === void 0 || status.isPickList !== true || !Array.isArray(status.picklistValues)) return [];
1304
+ const out = [];
1305
+ for (const v of status.picklistValues) {
1306
+ const id = Number(v?.value);
1307
+ if (!Number.isSafeInteger(id)) continue;
1308
+ out.push({ id, label: typeof v.label === "string" ? v.label : "" });
1309
+ }
1310
+ return out;
1311
+ }
1312
+ async function readFields(http, fresh) {
1313
+ const cache = http.fieldCache;
1314
+ const key = "tickets";
1315
+ if (!fresh && cache !== void 0) {
1316
+ const shared = cache.inflight(key, "fields");
1317
+ if (shared !== void 0) return await shared;
1318
+ const hit = cache.get(key, "fields");
1319
+ if (hit !== void 0) return hit;
1320
+ }
1321
+ const res = await http.request({ method: "GET", path: "/V1.0/Tickets/entityInformation/fields", operation: OP, maxRetries: 0 });
1322
+ const fields = res?.fields;
1323
+ if (!Array.isArray(fields)) {
1324
+ throw new AutotaskError("Expected the FieldInformationResultModel envelope { fields: [] }; got a different shape", { code: "INVALID_RESPONSE", category: "server" });
1325
+ }
1326
+ cache?.set(key, "fields", fields);
1327
+ return fields;
1328
+ }
1329
+ function assumed(excludeStatusIds) {
1330
+ const closedIds = excludeStatusIds === void 0 ? [ASSUMED_CLOSED_STATUS_ID] : [...excludeStatusIds];
1331
+ return {
1332
+ picklist: [],
1333
+ closedIds,
1334
+ source: "assumed",
1335
+ assumedId: ASSUMED_CLOSED_STATUS_ID,
1336
+ 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)`
1337
+ };
1338
+ }
1339
+ async function resolveTicketStatusSemantics(http, opts = {}) {
1340
+ if (opts.excludeStatusIds !== void 0) {
1341
+ const closedIds2 = [...new Set(opts.excludeStatusIds.map((id) => Number(id)))];
1342
+ if (closedIds2.length === 0 || closedIds2.some((id) => !Number.isSafeInteger(id) || id < 1)) {
1343
+ throw new AutotaskConfigError(`excludeStatusIds must be a non-empty list of positive status ids (got ${JSON.stringify(opts.excludeStatusIds)})`, { operation: OP });
1344
+ }
1345
+ return { picklist: [], closedIds: closedIds2, source: "picklist", note: `closed = status ${closedIds2.join(", ")} \u2014 supplied by the caller` };
1346
+ }
1347
+ let fields;
1348
+ try {
1349
+ fields = await readFields(http, opts.fresh === true);
1350
+ } catch (e) {
1351
+ if (e instanceof AutotaskError && e.code === "ABORTED") throw e;
1352
+ return assumed();
1353
+ }
1354
+ const picklist = picklistOf(fields);
1355
+ const closedIds = picklist.filter((v) => CLOSED_STATUS_LABELS.includes(v.label.trim().toLowerCase())).map((v) => v.id);
1356
+ if (closedIds.length === 0) return assumed();
1357
+ const labels = picklist.filter((v) => closedIds.includes(v.id)).map((v) => `${v.id} ${v.label}`).join(", ");
1358
+ return {
1359
+ picklist,
1360
+ closedIds,
1361
+ source: "picklist",
1362
+ note: `closed = status not in [${closedIds.join(", ")}] (${labels}) \u2014 resolved from the tenant's Tickets status picklist`
1363
+ };
1364
+ }
1365
+ function openStatusFilter(closedIds) {
1366
+ return closedIds.length === 1 ? { field: "status", op: "noteq", value: closedIds[0] } : { field: "status", op: "notIn", value: [...closedIds] };
1367
+ }
1368
+ function isClosedStatus(status, closedIds) {
1369
+ return typeof status === "number" && closedIds.includes(status);
1370
+ }
1371
+
1294
1372
  // src/resources/special/companies/fullProfile.ts
1295
1373
  var FULL_PROFILE_PROJECTABLE_PARTS = ["locations", "contacts", "notes", "projects", "openTickets"];
1296
1374
  var PART_ENTITY = {
@@ -1300,7 +1378,7 @@ var PART_ENTITY = {
1300
1378
  projects: "projects",
1301
1379
  openTickets: "tickets"
1302
1380
  };
1303
- 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";
1381
+ 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";
1304
1382
  var PROFILE_PART_LIMIT = 50;
1305
1383
  var LOCATION_LIMIT_MAX = 500;
1306
1384
  function locationLimit(value) {
@@ -1361,6 +1439,8 @@ async function scopedCompanyQuery(http, path, companyID, limit, includeFields) {
1361
1439
  return project(unwrapItems(res).items, includeFields);
1362
1440
  }
1363
1441
  async function readOpenTickets(http, companyID, ticketLimit, includeFields) {
1442
+ const semantics = await resolveTicketStatusSemantics(http);
1443
+ const openLeaf = openStatusFilter(semantics.closedIds);
1364
1444
  const openRows = await (async () => {
1365
1445
  const res = await http.request({
1366
1446
  method: "POST",
@@ -1368,30 +1448,31 @@ async function readOpenTickets(http, companyID, ticketLimit, includeFields) {
1368
1448
  path: "/V1.0/Tickets/query",
1369
1449
  body: {
1370
1450
  MaxRecords: ticketLimit,
1371
- filter: [{ field: "companyID", op: "eq", value: companyID }, { field: "status", op: "noteq", value: 5 }],
1451
+ filter: [{ field: "companyID", op: "eq", value: companyID }, openLeaf],
1372
1452
  ...includeFields ? { IncludeFields: includeFields } : {}
1373
1453
  },
1374
1454
  operation: "companies.fullProfile"
1375
1455
  });
1376
1456
  return project(unwrapItems(res).items, includeFields);
1377
1457
  })();
1458
+ const closedStatus = { closedIds: semantics.closedIds, source: semantics.source, note: semantics.note };
1459
+ const partNote = semantics.source === "assumed" ? OPEN_TICKETS_NOTE : semantics.note;
1378
1460
  let openTickets = {
1379
1461
  items: openRows,
1380
1462
  capped: openRows.length >= ticketLimit,
1381
1463
  blind: false,
1382
- note: OPEN_TICKETS_NOTE
1464
+ note: partNote,
1465
+ closedStatus
1383
1466
  };
1384
1467
  if (openRows.length === 0) {
1385
- const stamp = blindStampFor(BLIND_SURFACE_STAMPS["tickets"]?.query, [
1386
- { field: "companyID", op: "eq", value: companyID },
1387
- { field: "status", op: "noteq", value: 5 }
1388
- ]);
1468
+ const stamp = blindStampFor(BLIND_SURFACE_STAMPS["tickets"]?.query, [{ field: "companyID", op: "eq", value: companyID }, openLeaf]);
1389
1469
  if (stamp !== void 0) {
1390
1470
  openTickets = {
1391
1471
  items: openRows,
1392
1472
  capped: openRows.length >= ticketLimit,
1393
1473
  blind: true,
1394
- note: `${stamp.note} ${OPEN_TICKETS_NOTE}`,
1474
+ note: `${stamp.note} ${partNote}`,
1475
+ closedStatus,
1395
1476
  blindSurface: stamp
1396
1477
  };
1397
1478
  }
@@ -1589,7 +1670,7 @@ function validateUnbilledWorkOptions(companyId, opts) {
1589
1670
  }
1590
1671
 
1591
1672
  // src/resources/special/companies/unbilledWork.ts
1592
- var OP = "companies.unbilledWork";
1673
+ var OP2 = "companies.unbilledWork";
1593
1674
  function capJoinSet(ids2, cap) {
1594
1675
  const sorted = [...new Set(ids2)].sort((a, b) => a - b);
1595
1676
  if (sorted.length <= cap) return { ids: sorted, truncated: false };
@@ -1626,14 +1707,14 @@ async function readCompanyAndParent(resource, id, includeParent) {
1626
1707
  }
1627
1708
  async function readJoinSets(http, companyIds, v) {
1628
1709
  const ticketsCond = [{ field: "companyID", op: "in", value: companyIds }];
1629
- const ticketsRaw = await boundedPart(http, "/V1.0/Tickets/query", ticketsCond, v.limit, OP);
1710
+ const ticketsRaw = await boundedPart(http, "/V1.0/Tickets/query", ticketsCond, v.limit, OP2);
1630
1711
  const projectsCond = [{ field: "companyID", op: "in", value: companyIds }];
1631
- const projectsRaw = await boundedPart(http, "/V1.0/Projects/query", projectsCond, v.limit, OP);
1712
+ const projectsRaw = await boundedPart(http, "/V1.0/Projects/query", projectsCond, v.limit, OP2);
1632
1713
  const projectIds = projectsRaw.items.map((p) => num(p.id)).filter((n) => n > 0);
1633
1714
  let taskIds = [];
1634
1715
  let tasksHasMore = false;
1635
1716
  if (projectIds.length > 0) {
1636
- const tasksRaw = await boundedPart(http, "/V1.0/Tasks/query", [{ field: "projectID", op: "in", value: projectIds }], v.limit, OP);
1717
+ const tasksRaw = await boundedPart(http, "/V1.0/Tasks/query", [{ field: "projectID", op: "in", value: projectIds }], v.limit, OP2);
1637
1718
  taskIds = tasksRaw.items.map((t) => num(t.id)).filter((n) => n > 0);
1638
1719
  tasksHasMore = tasksRaw.hasMore;
1639
1720
  }
@@ -1657,7 +1738,7 @@ async function readBillingPart(http, v, companyIds) {
1657
1738
  }
1658
1739
  const billingFields = resolveFieldsPreset("billing_join", "billing_items");
1659
1740
  const billingIncludeFields = [...billingFields, "id", ...v.includeNotes ? ["description"] : []];
1660
- const billingRaw = await boundedPart(http, "/V1.0/BillingItems/query", billingConditions, v.limit, OP, billingIncludeFields);
1741
+ const billingRaw = await boundedPart(http, "/V1.0/BillingItems/query", billingConditions, v.limit, OP2, billingIncludeFields);
1661
1742
  let billingItems = billingRaw.items;
1662
1743
  let billingNoteCharsTruncated = 0;
1663
1744
  if (v.includeNotes) {
@@ -1701,7 +1782,7 @@ async function readTimePart(http, v, join) {
1701
1782
  if (v.dateFrom !== void 0) timeConditions.push({ field: "dateWorked", op: "gte", value: v.dateFrom });
1702
1783
  if (v.dateTo !== void 0) timeConditions.push({ field: "dateWorked", op: "lte", value: v.dateTo });
1703
1784
  const timeFields = ["taskID", "ticketID", "hoursWorked", "dateWorked", "id", ...v.includeNotes ? ["summaryNotes"] : []];
1704
- const raw = await boundedPart(http, "/V1.0/TimeEntries/query", timeConditions, v.limit, OP, timeFields);
1785
+ const raw = await boundedPart(http, "/V1.0/TimeEntries/query", timeConditions, v.limit, OP2, timeFields);
1705
1786
  entries = raw.items;
1706
1787
  let cappedItems = entries;
1707
1788
  let noteCharsTruncated = 0;
@@ -5220,7 +5301,7 @@ function appendWiresNote(wires, note) {
5220
5301
  wires.note = (wires.note ? wires.note + " | " : "") + note;
5221
5302
  }
5222
5303
  function makeFetchCtx(rawHttp, v, contractId, budget) {
5223
- const OP7 = PROFITABILITY_OP;
5304
+ const OP10 = PROFITABILITY_OP;
5224
5305
  const id = parseIdentifier(contractId, "contractId");
5225
5306
  const idNum = typeof id === "number" ? id : Number(id);
5226
5307
  const wires = { issued: 0, requests: 0, budget };
@@ -5232,19 +5313,19 @@ function makeFetchCtx(rawHttp, v, contractId, budget) {
5232
5313
  } };
5233
5314
  const readPart = (path, conditions, fields) => {
5234
5315
  wires.issued += 1;
5235
- return degradable(() => boundedPart(http, path, conditions, v.limit, OP7, fields));
5316
+ return degradable(() => boundedPart(http, path, conditions, v.limit, OP10, fields));
5236
5317
  };
5237
5318
  const truncatedMonths = {};
5238
5319
  const windowedPart = async (name, key, path, cond, fields, dateField) => {
5239
5320
  const first = await readPart(path, cond, fields);
5240
5321
  if (first.absent !== void 0 || !first.hasMore) return { part: stampPart(key, first, cond), rows: first.items };
5241
- const wp = await degradable(() => expandByMonth(http, path, cond, v.limit, OP7, fields, dateField, v.dateFrom, v.dateTo, wires));
5322
+ const wp = await degradable(() => expandByMonth(http, path, cond, v.limit, OP10, fields, dateField, v.dateFrom, v.dateTo, wires));
5242
5323
  if (!("truncatedMonths" in wp)) return { part: stampPart(key, wp, cond), rows: [] };
5243
5324
  const { truncatedMonths: tm, ...part } = wp;
5244
5325
  truncatedMonths[name] = tm;
5245
5326
  return { part, rows: wp.items };
5246
5327
  };
5247
- return { v, OP: OP7, id, idNum, rawHttp, http, wires, windowOn, readPart, windowedPart, truncatedMonths };
5328
+ return { v, OP: OP10, id, idNum, rawHttp, http, wires, windowOn, readPart, windowedPart, truncatedMonths };
5248
5329
  }
5249
5330
 
5250
5331
  // src/resources/special/contracts/profitability.stages.ts
@@ -10434,7 +10515,6 @@ var TicketChangeRequestApprovalsResource = class extends BaseResource {
10434
10515
  // src/resources/special/_shared/move-engine.ts
10435
10516
  import { randomUUID } from "crypto";
10436
10517
  var MOVE_SOURCE_AGE_DEFAULT_DAYS = 14;
10437
- var TICKET_COMPLETE_STATUS = 5;
10438
10518
  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";
10439
10519
  var refuse = (code, message, ids2, operation, extra = {}) => new MoveRefusedError(message, { code, operation, details: { status: "failed", ...ids2, ...extra } });
10440
10520
  async function queryRows(http, resource, filter, operation, retries) {
@@ -10487,11 +10567,19 @@ async function preview(http, spec, sourceId, toTicketId, opts) {
10487
10567
  }
10488
10568
  const target = await readTicket(http, toTicketId, opts.maxRetries, op);
10489
10569
  if (target === null) throw refuse("MOVE_TARGET_MISSING", `${op}: target ticket ${toTicketId} does not exist`, ids2, op);
10490
- if (target.status === TICKET_COMPLETE_STATUS || target.completedDate !== void 0 && target.completedDate !== null) {
10491
- throw refuse("MOVE_TARGET_CLOSED", `${op}: target ticket ${toTicketId} is closed (status 5 or completed)`, ids2, op);
10570
+ const closedStatus = await resolveTicketStatusSemantics(http);
10571
+ if (isClosedStatus(target.status, closedStatus.closedIds) || target.completedDate !== void 0 && target.completedDate !== null) {
10572
+ throw refuse(
10573
+ "MOVE_TARGET_CLOSED",
10574
+ `${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"}`,
10575
+ ids2,
10576
+ op,
10577
+ { closedStatusIds: closedStatus.closedIds, closedStatusSource: closedStatus.source }
10578
+ );
10492
10579
  }
10493
10580
  const origin = await readTicket(http, sourceTicketId, opts.maxRetries, op);
10494
10581
  const facts = {
10582
+ closedStatus: { closedIds: closedStatus.closedIds, source: closedStatus.source, note: closedStatus.note },
10495
10583
  sourceTicketId,
10496
10584
  sourceCompanyID: origin?.companyID ?? null,
10497
10585
  targetCompanyID: target.companyID ?? null,
@@ -10605,22 +10693,22 @@ function uiLinkFor(http, entity, id) {
10605
10693
  }
10606
10694
 
10607
10695
  // src/resources/special/ticket_charges.ts
10608
- var OP2 = "ticket_charges.moveToTicket";
10696
+ var OP3 = "ticket_charges.moveToTicket";
10609
10697
  var COPY = ["name", "description", "chargeType", "billingCodeID", "productID", "unitQuantity", "unitCost", "unitPrice", "datePurchased", "isBillableToCompany", "notes", "purchaseOrderNumber", "internalPurchaseOrderNumber"];
10610
10698
  var CONTRACT_SCOPED = ["contractServiceID", "contractServiceBundleID"];
10611
10699
  async function readCharge(http, id, retries) {
10612
- const rows = await queryRows(http, "TicketCharges", [{ field: "id", op: "eq", value: id }], OP2, retries);
10700
+ const rows = await queryRows(http, "TicketCharges", [{ field: "id", op: "eq", value: id }], OP3, retries);
10613
10701
  return rows[0] ?? null;
10614
10702
  }
10615
10703
  async function checkSource({ http, source, sourceId, sourceTicketId, toTicketId }) {
10616
10704
  if (source.isBilled === true) {
10617
- throw refuse("MOVE_BLOCKED_BILLED", `${OP2}: ticket charge ${sourceId} is billed \u2014 move it in the Autotask UI`, { sourceId, toTicketId }, OP2, {
10705
+ throw refuse("MOVE_BLOCKED_BILLED", `${OP3}: ticket charge ${sourceId} is billed \u2014 move it in the Autotask UI`, { sourceId, toTicketId }, OP3, {
10618
10706
  manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "tickets", sourceTicketId), reason: "billed charges are not moved by the API (the ticket link opens its charges)" }
10619
10707
  });
10620
10708
  }
10621
10709
  }
10622
10710
  var SPEC = {
10623
- operation: OP2,
10711
+ operation: OP3,
10624
10712
  entity: "ticket_charge",
10625
10713
  readSource: readCharge,
10626
10714
  sourceTicketId: (s) => typeof s.ticketID === "number" ? s.ticketID : null,
@@ -11975,6 +12063,7 @@ async function addCompositeNames(http, ctx, entriesPart, result, opts) {
11975
12063
  builder.addFk("contract", t["contractID"]);
11976
12064
  builder.addFk("billingCode", t["billingCodeID"]);
11977
12065
  builder.addFk("role", t["assignedResourceRoleID"]);
12066
+ builder.addLineOfBusiness(t["organizationalLevelAssociationID"]);
11978
12067
  for (const e of entriesPart.items) {
11979
12068
  builder.addResource(e["resourceID"]);
11980
12069
  builder.addFk("role", e["roleID"]);
@@ -11998,6 +12087,12 @@ async function addCompositeNames(http, ctx, entriesPart, result, opts) {
11998
12087
  return row;
11999
12088
  });
12000
12089
  result.names = names.section;
12090
+ result.lineOfBusiness = names.lineOfBusiness ?? {
12091
+ association: null,
12092
+ organizationalLevel2: null,
12093
+ organizationalLevel1: null,
12094
+ markers: { association: "missing", organizationalLevel2: "missing", organizationalLevel1: "missing" }
12095
+ };
12001
12096
  result.truncated = result.truncated || names.section.truncated;
12002
12097
  }
12003
12098
  async function fullContext(resource, id, opts = {}, http) {
@@ -12061,6 +12156,153 @@ async function fullContext(resource, id, opts = {}, http) {
12061
12156
  return result;
12062
12157
  }
12063
12158
 
12159
+ // src/resources/special/tickets/forResource.ts
12160
+ var OP4 = "tickets.forResource";
12161
+ var TICKET_CHUNK = 100;
12162
+ var LIMIT_DEFAULT = 100;
12163
+ var LIMIT_MAX = 500;
12164
+ var LINK_READ_CAP = 500;
12165
+ var TICKET_FIELDS2 = ["id", "ticketNumber", "title", "status", "assignedResourceID", "companyID"];
12166
+ function positiveId(value, name) {
12167
+ const n = typeof value === "string" && /^\d+$/.test(value.trim()) ? Number(value) : value;
12168
+ if (typeof n !== "number" || !Number.isSafeInteger(n) || n < 1) {
12169
+ throw new AutotaskConfigError(`${OP4}: ${name} must be a positive integer id (got ${String(value)})`, { operation: OP4 });
12170
+ }
12171
+ return n;
12172
+ }
12173
+ async function ticketPage(http, filter, maxRecords, fields) {
12174
+ const res = await http.request({
12175
+ method: "POST",
12176
+ idempotent: true,
12177
+ path: "/V1.0/Tickets/query",
12178
+ body: { MaxRecords: maxRecords, filter, IncludeFields: fields },
12179
+ operation: OP4
12180
+ });
12181
+ const page = unwrapItems(res, OP4);
12182
+ return { rows: page.items, more: page.pageDetails.nextPageUrl !== null };
12183
+ }
12184
+ async function forResource(_resource, resourceIDIn, http, opts = {}) {
12185
+ if (opts === null || typeof opts !== "object") throw new AutotaskConfigError(`${OP4}: opts must be an object`, { operation: OP4 });
12186
+ const resourceID = positiveId(resourceIDIn, "resourceID");
12187
+ const scope = opts.scope ?? "both";
12188
+ if (scope !== "primary" && scope !== "secondary" && scope !== "both") {
12189
+ throw new AutotaskConfigError(`${OP4}: scope must be 'primary', 'secondary' or 'both' (got ${JSON.stringify(opts.scope)})`, { operation: OP4 });
12190
+ }
12191
+ if (opts.openOnly !== void 0 && typeof opts.openOnly !== "boolean") {
12192
+ throw new AutotaskConfigError(`${OP4}: openOnly must be a boolean`, { operation: OP4 });
12193
+ }
12194
+ const limit = opts.limit === void 0 ? LIMIT_DEFAULT : opts.limit;
12195
+ if (!Number.isInteger(limit) || limit < 1 || limit > LIMIT_MAX) {
12196
+ throw new AutotaskConfigError(`${OP4}: limit must be an integer 1-${LIMIT_MAX} (got ${String(opts.limit)})`, { operation: OP4 });
12197
+ }
12198
+ const includeFields = opts.includeFields ?? [];
12199
+ if (!Array.isArray(includeFields) || includeFields.some((f) => typeof f !== "string" || f.length === 0)) {
12200
+ throw new AutotaskConfigError(`${OP4}: includeFields must be a list of field names`, { operation: OP4 });
12201
+ }
12202
+ const openOnly = opts.openOnly === true;
12203
+ const fields = [.../* @__PURE__ */ new Set([...TICKET_FIELDS2, ...includeFields])];
12204
+ const closed = await resolveTicketStatusSemantics(http, opts.excludeStatusIds === void 0 ? {} : { excludeStatusIds: opts.excludeStatusIds });
12205
+ const closedStatus = { closedIds: closed.closedIds, source: closed.source, note: closed.note };
12206
+ const openLeaf = openOnly ? [openStatusFilter(closed.closedIds)] : [];
12207
+ const linkIds = [];
12208
+ let capped = false;
12209
+ if (scope !== "primary") {
12210
+ const res = await http.request({
12211
+ method: "POST",
12212
+ idempotent: true,
12213
+ path: "/V1.0/TicketSecondaryResources/query",
12214
+ body: { MaxRecords: LINK_READ_CAP, filter: [{ field: "resourceID", op: "eq", value: resourceID }], IncludeFields: ["ticketID"] },
12215
+ operation: OP4
12216
+ });
12217
+ const page = unwrapItems(res, OP4);
12218
+ for (const row of page.items) if (typeof row.ticketID === "number" && !linkIds.includes(row.ticketID)) linkIds.push(row.ticketID);
12219
+ if (page.items.length >= LINK_READ_CAP || page.pageDetails.nextPageUrl !== null) capped = true;
12220
+ }
12221
+ const primary = /* @__PURE__ */ new Map();
12222
+ if (scope !== "secondary") {
12223
+ const page = await ticketPage(http, [{ field: "assignedResourceID", op: "eq", value: resourceID }, ...openLeaf], limit, fields);
12224
+ if (page.more || page.rows.length >= limit) capped = true;
12225
+ for (const t of page.rows) if (typeof t.id === "number") primary.set(t.id, t);
12226
+ }
12227
+ const secondary = /* @__PURE__ */ new Map();
12228
+ const unresolvedIds = [];
12229
+ for (let c = 0; c < linkIds.length; c += TICKET_CHUNK) {
12230
+ const chunk = linkIds.slice(c, c + TICKET_CHUNK);
12231
+ const page = await ticketPage(http, [{ field: "id", op: "in", value: chunk }, ...openLeaf], chunk.length, fields);
12232
+ const seen = /* @__PURE__ */ new Set();
12233
+ for (const t of page.rows) {
12234
+ if (typeof t.id !== "number") continue;
12235
+ secondary.set(t.id, t);
12236
+ seen.add(t.id);
12237
+ }
12238
+ for (const id of chunk) if (!seen.has(id)) unresolvedIds.push(id);
12239
+ }
12240
+ const merged = /* @__PURE__ */ new Map();
12241
+ for (const [id, t] of primary) merged.set(id, t);
12242
+ for (const [id, t] of secondary) if (!merged.has(id)) merged.set(id, t);
12243
+ let tickets = [...merged.values()];
12244
+ if (openOnly) tickets = tickets.filter((t) => !isClosedStatus(t.status, closed.closedIds));
12245
+ tickets.sort((a, b) => Number(a.id) - Number(b.id));
12246
+ if (tickets.length > limit) {
12247
+ tickets = tickets.slice(0, limit);
12248
+ capped = true;
12249
+ }
12250
+ const openCount = tickets.filter((t) => !isClosedStatus(t.status, closed.closedIds)).length;
12251
+ const primaryIds = [...primary.keys()].sort((a, b) => a - b);
12252
+ const secondaryIds = [...secondary.keys()].sort((a, b) => a - b);
12253
+ const both = primaryIds.filter((id) => secondary.has(id));
12254
+ return {
12255
+ resourceID,
12256
+ scope,
12257
+ openOnly,
12258
+ tickets,
12259
+ primaryIds,
12260
+ secondaryIds,
12261
+ bothIds: both,
12262
+ counts: { primary: primaryIds.length, secondary: secondaryIds.length, both: both.length, open: openCount },
12263
+ capped,
12264
+ unresolvedIds: unresolvedIds.sort((a, b) => a - b),
12265
+ closedStatus
12266
+ };
12267
+ }
12268
+
12269
+ // src/resources/special/tickets/lineOfBusiness.ts
12270
+ var OP5 = "tickets.lineOfBusiness";
12271
+ function positiveId2(value, name) {
12272
+ const n = typeof value === "string" && /^[0-9]+$/.test(value.trim()) ? Number(value) : value;
12273
+ if (typeof n !== "number" || !Number.isSafeInteger(n) || n < 1) {
12274
+ throw new AutotaskConfigError(OP5 + ": " + name + " must be a positive integer id (got " + String(value) + ")", { operation: OP5 });
12275
+ }
12276
+ return n;
12277
+ }
12278
+ async function lineOfBusiness(resource, ticketIdIn, http, associationIdIn) {
12279
+ const ticketId = positiveId2(ticketIdIn, "ticketId");
12280
+ let associationId;
12281
+ if (associationIdIn !== void 0) {
12282
+ associationId = positiveId2(associationIdIn, "associationId");
12283
+ } else {
12284
+ const ticket = await resource.get(ticketId);
12285
+ associationId = vendorId(ticket.organizationalLevelAssociationID);
12286
+ }
12287
+ if (associationId === void 0) {
12288
+ return {
12289
+ ticketId,
12290
+ association: null,
12291
+ organizationalLevel2: null,
12292
+ organizationalLevel1: null,
12293
+ markers: { association: "missing", organizationalLevel2: "missing", organizationalLevel1: "missing" }
12294
+ };
12295
+ }
12296
+ const chain = await resolveLineOfBusinessChain(http, associationId, { budget: 3, operation: OP5, onError: "propagate" });
12297
+ const { association, organizationalLevel2, organizationalLevel1, markers } = chain.lineOfBusiness;
12298
+ return { ticketId, association, organizationalLevel2, organizationalLevel1, markers };
12299
+ }
12300
+
12301
+ // src/resources/special/tickets/statusSemantics.ts
12302
+ async function statusSemantics(_resource, opts = {}, http) {
12303
+ return resolveTicketStatusSemantics(http, opts);
12304
+ }
12305
+
12064
12306
  // src/resources/tickets.ts
12065
12307
  var TicketsResource = class extends BaseResource {
12066
12308
  constructor(http, pageSize) {
@@ -12166,6 +12408,18 @@ var TicketsResource = class extends BaseResource {
12166
12408
  fullContext(id, opts) {
12167
12409
  return fullContext(this, id, opts, this.http);
12168
12410
  }
12411
+ /** Node-parity special op (hand-written in src/resources/special/tickets.ts). */
12412
+ statusSemantics(opts) {
12413
+ return statusSemantics(this, opts, this.http);
12414
+ }
12415
+ /** Node-parity special op (hand-written in src/resources/special/tickets.ts). */
12416
+ forResource(resourceID, opts) {
12417
+ return forResource(this, resourceID, this.http, opts);
12418
+ }
12419
+ /** Node-parity special op (hand-written in src/resources/special/tickets.ts). */
12420
+ lineOfBusiness(ticketId, associationId) {
12421
+ return lineOfBusiness(this, ticketId, this.http, associationId);
12422
+ }
12169
12423
  };
12170
12424
 
12171
12425
  // src/resources/ticket_webhooks.ts
@@ -12294,12 +12548,12 @@ async function postedDateStatusQuery(_resource, mode, http, opts) {
12294
12548
  return items;
12295
12549
  }
12296
12550
  async function postedWithin(_resource, from, to, http, opts, opLabel = "time_entries.postedWithin") {
12297
- const OP7 = opLabel;
12298
- if (opts?.tz !== void 0) validateZone(opts.tz, OP7);
12299
- const fromB = customRangeDay(from, "from", void 0, OP7);
12300
- const toB = customRangeDay(to, "to", void 0, OP7);
12551
+ const OP10 = opLabel;
12552
+ if (opts?.tz !== void 0) validateZone(opts.tz, OP10);
12553
+ const fromB = customRangeDay(from, "from", void 0, OP10);
12554
+ const toB = customRangeDay(to, "to", void 0, OP10);
12301
12555
  if (fromB.ms > toB.ms) {
12302
- throw new AutotaskConfigError(`${OP7}: from (${iso2(fromB.ms)}) must not be after to (${iso2(toB.ms)})`, { operation: OP7 });
12556
+ throw new AutotaskConfigError(`${OP10}: from (${iso2(fromB.ms)}) must not be after to (${iso2(toB.ms)})`, { operation: OP10 });
12303
12557
  }
12304
12558
  const windowToMs = toB.ms + DAY_MS2 - 1;
12305
12559
  const filter = [
@@ -12310,7 +12564,7 @@ async function postedWithin(_resource, from, to, http, opts, opLabel = "time_ent
12310
12564
  else if (opts?.posted === false) filter.push({ field: "postedDate", op: "notExist" });
12311
12565
  if (opts?.ticketID !== void 0) filter.push({ field: "ticketID", op: "eq", value: opts.ticketID });
12312
12566
  if (opts?.resourceID !== void 0) filter.push({ field: "resourceID", op: "eq", value: opts.resourceID });
12313
- const { items, hasMore } = await queryTimeEntries(_resource, filter, clampWindowLimit(opts?.limit), OP7, http);
12567
+ const { items, hasMore } = await queryTimeEntries(_resource, filter, clampWindowLimit(opts?.limit), OP10, http);
12314
12568
  let postedCount = 0;
12315
12569
  for (const item of items) {
12316
12570
  const postedDate = item.postedDate;
@@ -12520,7 +12774,7 @@ async function resolveCode(http, family, familyLabel2, pinnedId, departments, op
12520
12774
  }
12521
12775
 
12522
12776
  // src/resources/special/time_entries/createForResource.ts
12523
- var OP3 = "time_entries.createForResource";
12777
+ var OP6 = "time_entries.createForResource";
12524
12778
  var PARAM_KEYS = /* @__PURE__ */ new Set([
12525
12779
  "internallAllocationCodeID",
12526
12780
  "ticketID",
@@ -12556,30 +12810,30 @@ function validateCreateParams(params) {
12556
12810
  if (pinCount > 1) {
12557
12811
  throw new AutotaskConfigError(
12558
12812
  "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)",
12559
- { operation: OP3 }
12813
+ { operation: OP6 }
12560
12814
  );
12561
12815
  }
12562
12816
  if (pins.role !== void 0 && pins.code !== void 0) {
12563
12817
  throw new AutotaskConfigError(
12564
12818
  "createForResource: a pinned roleID is only meaningful with the task/ticket linkage (internallAllocationCodeID pins the internal linkage and owns the code) \u2014 drop one",
12565
- { operation: OP3 }
12819
+ { operation: OP6 }
12566
12820
  );
12567
12821
  }
12568
12822
  if (params.hoursWorked !== void 0 && (typeof params.hoursWorked !== "number" || !Number.isFinite(params.hoursWorked) || params.hoursWorked <= 0)) {
12569
12823
  throw new AutotaskConfigError(
12570
12824
  `createForResource: hoursWorked must be a positive finite number (got ${String(params.hoursWorked)})`,
12571
- { operation: OP3 }
12825
+ { operation: OP6 }
12572
12826
  );
12573
12827
  }
12574
12828
  return pins;
12575
12829
  }
12576
12830
  async function assertResourceActive(http, rid) {
12577
- const rec = await fetchItem2(http, `/V1.0/Resources/${rid}`, OP3);
12831
+ const rec = await fetchItem2(http, `/V1.0/Resources/${rid}`, OP6);
12578
12832
  if (rec === null) {
12579
12833
  throw new ResourceInvalidError(`Resource ${rid} was not found \u2014 a time entry cannot attach to a missing resource`, {
12580
12834
  resourceIds: [rid],
12581
12835
  details: { idRefs: [{ entity: "resource", id: rid }] },
12582
- operation: OP3,
12836
+ operation: OP6,
12583
12837
  category: "not_found",
12584
12838
  suggestedAction: "Resolve the resource id (resources.get / resources.search) and retry."
12585
12839
  });
@@ -12588,7 +12842,7 @@ async function assertResourceActive(http, rid) {
12588
12842
  throw new ResourceInvalidError(`Resource ${rid} is not active (isActive !== true) \u2014 the vendor would reject a time entry for an inactive resource`, {
12589
12843
  resourceIds: [rid],
12590
12844
  details: { idRefs: [{ entity: "resource", id: rid }] },
12591
- operation: OP3,
12845
+ operation: OP6,
12592
12846
  suggestedAction: "Activate the resource or pick an active one."
12593
12847
  });
12594
12848
  }
@@ -12598,11 +12852,11 @@ async function resolveAllocationCode(http, rid, linkage, pins, departments) {
12598
12852
  const pinnedForCode = pins.code !== void 0 ? pins.code : pins.role;
12599
12853
  let code = null;
12600
12854
  const familiesTried = [];
12601
- code = await resolveCode(http, familyOf(linkage), familyLabel(familyOf(linkage)), pinnedForCode, departments, OP3);
12855
+ code = await resolveCode(http, familyOf(linkage), familyLabel(familyOf(linkage)), pinnedForCode, departments, OP6);
12602
12856
  familiesTried.push({ family: familyOf(linkage), label: familyLabel(familyOf(linkage)), filters: [{ field: "useType", op: "eq", value: familyOf(linkage) }, { field: "isActive", op: "eq", value: true }] });
12603
12857
  if (code === null && linkage === "task" && pins.role === void 0) {
12604
12858
  usedLinkage = "internal";
12605
- code = await resolveCode(http, USE_TYPE_INTERNAL, familyLabel(USE_TYPE_INTERNAL), void 0, departments, OP3);
12859
+ code = await resolveCode(http, USE_TYPE_INTERNAL, familyLabel(USE_TYPE_INTERNAL), void 0, departments, OP6);
12606
12860
  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 }] });
12607
12861
  }
12608
12862
  if (code === null) {
@@ -12612,7 +12866,7 @@ async function resolveAllocationCode(http, rid, linkage, pins, departments) {
12612
12866
  families: familiesTried,
12613
12867
  resourceDepartments: [...departments].sort((a, b) => a - b)
12614
12868
  },
12615
- { operation: OP3 }
12869
+ { operation: OP6 }
12616
12870
  );
12617
12871
  }
12618
12872
  const resolvedCode = codeInfo(code);
@@ -12651,7 +12905,7 @@ function createDryRun2(http, rid, body, linkage, usedLinkage, resolvedCode) {
12651
12905
  }
12652
12906
  return buildDryRunResult(
12653
12907
  {
12654
- operation: OP3,
12908
+ operation: OP6,
12655
12909
  method: "POST",
12656
12910
  path: "/V1.0/TimeEntries",
12657
12911
  ids: [rid],
@@ -12693,7 +12947,7 @@ async function createWithAttempts(resource, http, opts, body, defaults, pins, us
12693
12947
  const gate3 = ROLE_REJECTION_PATTERN.test(text);
12694
12948
  if (usedLinkage !== "internal" && gate3) {
12695
12949
  if (!taskRoleFetched && usedLinkage === "task" && pins.task !== void 0) {
12696
- const task = await fetchItem2(http, `/V1.0/Tasks/${pins.task}`, OP3);
12950
+ const task = await fetchItem2(http, `/V1.0/Tasks/${pins.task}`, OP6);
12697
12951
  taskRoleFetched = true;
12698
12952
  if (task !== null) pushLadder(task.assignedResourceRoleID);
12699
12953
  }
@@ -12719,7 +12973,7 @@ async function createForResource(resource, resourceID, params, opts = {}, http)
12719
12973
  const pins = validateCreateParams(params);
12720
12974
  await assertResourceActive(http, rid);
12721
12975
  const linkage = pins.code !== void 0 ? "internal" : pins.ticket !== void 0 ? "ticket" : pins.task !== void 0 ? "task" : "internal";
12722
- const roleRows = await resourceRoleRows(http, rid, OP3);
12976
+ const roleRows = await resourceRoleRows(http, rid, OP6);
12723
12977
  const departments = roleRows.departments;
12724
12978
  const { usedLinkage, resolvedCode } = await resolveAllocationCode(http, rid, linkage, pins, departments);
12725
12979
  const defaults = windowDefaults(params);
@@ -12742,12 +12996,12 @@ function discoveryField(err, body, defaults) {
12742
12996
  }
12743
12997
 
12744
12998
  // src/resources/special/time_entries/move.ts
12745
- var OP4 = "time_entries.moveToTicket";
12999
+ var OP7 = "time_entries.moveToTicket";
12746
13000
  var COPY2 = ["resourceID", "roleID", "billingCodeID", "dateWorked", "startDateTime", "endDateTime", "hoursWorked", "offsetHours", "summaryNotes", "internalNotes", "isNonBillable", "showOnInvoice"];
12747
13001
  var CONTRACT_SCOPED2 = ["contractID", "contractServiceID", "contractServiceBundleID"];
12748
13002
  async function readTimeEntry(http, id, retries) {
12749
13003
  try {
12750
- const res = await http.request({ method: "GET", path: `/V1.0/TimeEntries/${id}`, operation: OP4, maxRetries: retries });
13004
+ const res = await http.request({ method: "GET", path: `/V1.0/TimeEntries/${id}`, operation: OP7, maxRetries: retries });
12751
13005
  return res?.item ?? null;
12752
13006
  } catch (e) {
12753
13007
  if (isAutotaskError(e) && (e.httpStatus === 404 || e.code === "NOT_FOUND")) return null;
@@ -12757,27 +13011,27 @@ async function readTimeEntry(http, id, retries) {
12757
13011
  async function checkSource2({ http, source, sourceId, toTicketId, retries }) {
12758
13012
  const ids2 = { sourceId, toTicketId };
12759
13013
  if (source.taskID !== null && source.taskID !== void 0) {
12760
- throw refuse("MOVE_SOURCE_UNSUPPORTED", `${OP4}: time entry ${sourceId} is on a task \u2014 only ticket time entries are moved`, ids2, OP4);
13014
+ throw refuse("MOVE_SOURCE_UNSUPPORTED", `${OP7}: time entry ${sourceId} is on a task \u2014 only ticket time entries are moved`, ids2, OP7);
12761
13015
  }
12762
13016
  if (source.billingApprovalDateTime || source.billingApprovalResourceID || source.billingApprovalLevelMostRecent) {
12763
- 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" } });
13017
+ throw refuse("MOVE_BLOCKED_APPROVED", `${OP7}: time entry ${sourceId} has billing approval set \u2014 move it in the Autotask UI`, ids2, OP7, { manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "time_entries", sourceId), reason: "approved entries are not moved by the API" } });
12764
13018
  }
12765
13019
  let billing;
12766
13020
  try {
12767
- billing = await queryRows(http, "BillingItems", [{ field: "timeEntryID", op: "eq", value: sourceId }], OP4, retries);
13021
+ billing = await queryRows(http, "BillingItems", [{ field: "timeEntryID", op: "eq", value: sourceId }], OP7, retries);
12768
13022
  } catch {
12769
- 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" } });
13023
+ throw refuse("MOVE_BLOCKED_BILLED", `${OP7}: the billing items of time entry ${sourceId} could not be read \u2014 refused (fails closed)`, ids2, OP7, { manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "time_entries", sourceId), reason: "billing state unreadable" } });
12770
13024
  }
12771
13025
  if (billing.some((b) => b.invoiceID || b.postedDate)) {
12772
- 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" } });
13026
+ throw refuse("MOVE_BLOCKED_BILLED", `${OP7}: time entry ${sourceId} is billed or posted \u2014 move it in the Autotask UI`, ids2, OP7, { manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "time_entries", sourceId), reason: "billed entries are not moved by the API" } });
12773
13027
  }
12774
- const attachments = await queryRows(http, "TimeEntryAttachments", [{ field: "timeEntryID", op: "eq", value: sourceId }], OP4, retries);
13028
+ const attachments = await queryRows(http, "TimeEntryAttachments", [{ field: "timeEntryID", op: "eq", value: sourceId }], OP7, retries);
12775
13029
  if (attachments.length > 0) {
12776
- 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" } });
13030
+ throw refuse("MOVE_BLOCKED_ATTACHMENTS", `${OP7}: time entry ${sourceId} has attachments, which a delete would destroy \u2014 move it in the Autotask UI`, ids2, OP7, { manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "time_entries", sourceId), reason: "entries with attachments are not moved by the API" } });
12777
13031
  }
12778
13032
  }
12779
13033
  var SPEC2 = {
12780
- operation: OP4,
13034
+ operation: OP7,
12781
13035
  entity: "time_entry",
12782
13036
  readSource: readTimeEntry,
12783
13037
  sourceTicketId: (s) => typeof s.ticketID === "number" ? s.ticketID : null,
@@ -12812,7 +13066,7 @@ function moveToTicket2(_resource, sourceId, toTicketId, opts, http) {
12812
13066
  }
12813
13067
 
12814
13068
  // src/resources/special/time_entries/approve.ts
12815
- var OP5 = "time_entries.approve";
13069
+ var OP8 = "time_entries.approve";
12816
13070
  var APPROVAL_LEVELS_PATH = "/V1.0/BillingItemApprovalLevels";
12817
13071
  function toIso(value) {
12818
13072
  if (value instanceof Date) {
@@ -12850,7 +13104,7 @@ async function approve(resource, id, opts = {}, http) {
12850
13104
  if (opts.dryRun === true) {
12851
13105
  return buildDryRunResult(
12852
13106
  {
12853
- operation: OP5,
13107
+ operation: OP8,
12854
13108
  method: "POST",
12855
13109
  path: APPROVAL_LEVELS_PATH,
12856
13110
  ids: [numericId],
@@ -12873,10 +13127,10 @@ async function approve(resource, id, opts = {}, http) {
12873
13127
  method: "POST",
12874
13128
  path: APPROVAL_LEVELS_PATH,
12875
13129
  body,
12876
- operation: OP5,
13130
+ operation: OP8,
12877
13131
  maxRetries: opts.maxRetries
12878
13132
  });
12879
- const levelId = toNumber(parseIdentifier(unwrapOperation(created, OP5).id, "billingItemApprovalLevelID"));
13133
+ const levelId = toNumber(parseIdentifier(unwrapOperation(created, OP8).id, "billingItemApprovalLevelID"));
12880
13134
  await resource.update(
12881
13135
  numericId,
12882
13136
  { billingApprovalDateTime: approvalDateTime, billingApprovalResourceID: approvalResourceID },
@@ -12899,28 +13153,28 @@ async function approve(resource, id, opts = {}, http) {
12899
13153
  }
12900
13154
 
12901
13155
  // src/resources/special/time_entries/recentTickets.ts
12902
- var OP6 = "time_entries.recentTickets";
13156
+ var OP9 = "time_entries.recentTickets";
12903
13157
  var DAY_MS3 = 864e5;
12904
- var TICKET_CHUNK = 100;
12905
- var TICKET_FIELDS2 = ["id", "ticketNumber", "title", "status", "assignedResourceID", "companyID"];
12906
- function positiveId(value, name) {
13158
+ var TICKET_CHUNK2 = 100;
13159
+ var TICKET_FIELDS3 = ["id", "ticketNumber", "title", "status", "assignedResourceID", "companyID"];
13160
+ function positiveId3(value, name) {
12907
13161
  const n = typeof value === "string" && /^\d+$/.test(value.trim()) ? Number(value) : value;
12908
13162
  if (typeof n !== "number" || !Number.isSafeInteger(n) || n < 1) {
12909
- throw new AutotaskConfigError(`${OP6}: ${name} must be a positive integer id (got ${String(value)})`, { operation: OP6 });
13163
+ throw new AutotaskConfigError(`${OP9}: ${name} must be a positive integer id (got ${String(value)})`, { operation: OP9 });
12910
13164
  }
12911
13165
  return n;
12912
13166
  }
12913
13167
  async function recentTickets(_resource, from, to, http, opts) {
12914
13168
  if (opts === void 0 || opts === null || typeof opts !== "object") {
12915
- throw new AutotaskConfigError(`${OP6}: opts { resourceID, companyID?, tz?, limit? } is required`, { operation: OP6 });
13169
+ throw new AutotaskConfigError(`${OP9}: opts { resourceID, companyID?, tz?, limit? } is required`, { operation: OP9 });
12916
13170
  }
12917
- const resourceID = positiveId(opts.resourceID, "resourceID");
12918
- const companyID = opts.companyID === void 0 ? void 0 : positiveId(opts.companyID, "companyID");
12919
- if (opts.tz !== void 0) validateZone(opts.tz, OP6);
12920
- const fromB = customRangeDay(from, "from", void 0, OP6);
12921
- const toB = customRangeDay(to, "to", void 0, OP6);
13171
+ const resourceID = positiveId3(opts.resourceID, "resourceID");
13172
+ const companyID = opts.companyID === void 0 ? void 0 : positiveId3(opts.companyID, "companyID");
13173
+ if (opts.tz !== void 0) validateZone(opts.tz, OP9);
13174
+ const fromB = customRangeDay(from, "from", void 0, OP9);
13175
+ const toB = customRangeDay(to, "to", void 0, OP9);
12922
13176
  if (fromB.ms > toB.ms) {
12923
- throw new AutotaskConfigError(`${OP6}: from (${isoUtc(fromB.ms)}) must not be after to (${isoUtc(toB.ms)})`, { operation: OP6 });
13177
+ throw new AutotaskConfigError(`${OP9}: from (${isoUtc(fromB.ms)}) must not be after to (${isoUtc(toB.ms)})`, { operation: OP9 });
12924
13178
  }
12925
13179
  const windowToMs = toB.ms + DAY_MS3 - 1;
12926
13180
  const { items: entries, hasMore } = await queryTimeEntries(
@@ -12931,7 +13185,7 @@ async function recentTickets(_resource, from, to, http, opts) {
12931
13185
  { field: "resourceID", op: "eq", value: resourceID }
12932
13186
  ],
12933
13187
  clampWindowLimit(opts.limit),
12934
- OP6,
13188
+ OP9,
12935
13189
  http
12936
13190
  );
12937
13191
  const groups = /* @__PURE__ */ new Map();
@@ -12949,17 +13203,17 @@ async function recentTickets(_resource, from, to, http, opts) {
12949
13203
  }
12950
13204
  const ids2 = [...groups.keys()];
12951
13205
  const tickets = /* @__PURE__ */ new Map();
12952
- for (let c = 0; c < ids2.length; c += TICKET_CHUNK) {
12953
- const chunk = ids2.slice(c, c + TICKET_CHUNK);
13206
+ for (let c = 0; c < ids2.length; c += TICKET_CHUNK2) {
13207
+ const chunk = ids2.slice(c, c + TICKET_CHUNK2);
12954
13208
  const filter = [{ field: "id", op: "in", value: chunk }];
12955
13209
  const res = await http.request({
12956
13210
  method: "POST",
12957
13211
  idempotent: true,
12958
13212
  path: "/V1.0/Tickets/query",
12959
- body: { MaxRecords: TICKET_CHUNK, filter, IncludeFields: TICKET_FIELDS2 },
12960
- operation: OP6
13213
+ body: { MaxRecords: TICKET_CHUNK2, filter, IncludeFields: TICKET_FIELDS3 },
13214
+ operation: OP9
12961
13215
  });
12962
- for (const t of unwrapItems(res, OP6).items) if (typeof t.id === "number") tickets.set(t.id, t);
13216
+ for (const t of unwrapItems(res, OP9).items) if (typeof t.id === "number") tickets.set(t.id, t);
12963
13217
  }
12964
13218
  const items = [];
12965
13219
  const unresolvedTicketIDs = [];