node-autotask 0.32.0 → 0.33.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 (37) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/MCP_TOOL_CATALOG.json +27 -8
  3. package/MCP_TOOL_MANIFEST.md +14 -13
  4. package/N8N_MCP_MANIFEST.json +38 -12
  5. package/N8N_MCP_MANIFEST.md +10 -9
  6. package/README.md +1 -1
  7. package/capabilities.json +1448 -1307
  8. package/dist/capabilities.cjs +2 -2
  9. package/dist/capabilities.js +1 -1
  10. package/dist/{chunk-QCB4LYKD.cjs → chunk-3LAZZT7V.cjs} +143 -143
  11. package/dist/{chunk-IKPFR6K2.cjs → chunk-4QN43TRP.cjs} +73 -48
  12. package/dist/{chunk-FVHPN7ZR.js → chunk-5DS6F6GF.js} +241 -91
  13. package/dist/{chunk-T6EDJRLT.js → chunk-7WNE5MPG.js} +2 -2
  14. package/dist/{chunk-R6HLJCI2.js → chunk-MFDQHFGJ.js} +26 -1
  15. package/dist/{chunk-FTF2FEQ6.cjs → chunk-OO67MRHN.cjs} +765 -615
  16. package/dist/{chunk-DCTHO7CQ.cjs → chunk-SP2SJJZX.cjs} +1447 -1306
  17. package/dist/{chunk-M2EGFPGF.js → chunk-VKE56TTN.js} +1447 -1306
  18. package/dist/{index-Cw4aNNMo.d.ts → index-BaeMHZPk.d.ts} +87 -1
  19. package/dist/{index-OkbCWcef.d.cts → index-Cq0VsfSz.d.cts} +87 -1
  20. package/dist/index.cjs +23 -23
  21. package/dist/index.d.cts +2 -2
  22. package/dist/index.d.ts +2 -2
  23. package/dist/index.js +5 -5
  24. package/dist/operations/index.cjs +120 -133
  25. package/dist/operations/index.d.cts +2 -2
  26. package/dist/operations/index.d.ts +2 -2
  27. package/dist/operations/index.js +3 -16
  28. package/dist/pool.cjs +9 -9
  29. package/dist/pool.d.cts +1 -1
  30. package/dist/pool.d.ts +1 -1
  31. package/dist/pool.js +4 -4
  32. package/dist/resources/index.cjs +4 -4
  33. package/dist/resources/index.d.cts +1 -1
  34. package/dist/resources/index.d.ts +1 -1
  35. package/dist/resources/index.js +3 -3
  36. package/docs/API.md +1 -1
  37. package/package.json +2 -1
@@ -25,6 +25,7 @@ import {
25
25
  createNamePlan,
26
26
  cursorToPath,
27
27
  decodeFileData,
28
+ effectiveRequiredFields,
28
29
  formatUnknownNameList,
29
30
  formatVendorDetail,
30
31
  isAutotaskError,
@@ -40,10 +41,10 @@ import {
40
41
  unwrapItems,
41
42
  unwrapOperation,
42
43
  vendorId
43
- } from "./chunk-R6HLJCI2.js";
44
+ } from "./chunk-MFDQHFGJ.js";
44
45
  import {
45
46
  CAPABILITIES
46
- } from "./chunk-M2EGFPGF.js";
47
+ } from "./chunk-VKE56TTN.js";
47
48
 
48
49
  // src/resources/appointments.ts
49
50
  var AppointmentsResource = class extends BaseResource {
@@ -273,11 +274,11 @@ function tzOffsetMs(tz, ms) {
273
274
  }
274
275
  function localDayStartMs(y, m, d, tz) {
275
276
  if (tz === void 0) return Date.UTC(y, m - 1, d);
276
- const candidate = Date.UTC(y, m - 1, d);
277
- const offset = tzOffsetMs(tz, candidate);
278
- let start = candidate - offset;
277
+ const candidate2 = Date.UTC(y, m - 1, d);
278
+ const offset = tzOffsetMs(tz, candidate2);
279
+ let start = candidate2 - offset;
279
280
  if (tzOffsetMs(tz, start) !== offset) {
280
- start = candidate - tzOffsetMs(tz, start);
281
+ start = candidate2 - tzOffsetMs(tz, start);
281
282
  }
282
283
  const p = wallParts(tz, start);
283
284
  if (p.d !== d) start += 864e5 - ((p.h * 60 + p.mi) * 60 + p.s) * 1e3;
@@ -1561,6 +1562,151 @@ async function fullProfile(resource, id, opts = {}, http) {
1561
1562
  return result;
1562
1563
  }
1563
1564
 
1565
+ // src/resources/special/companies/prepareCrmWrite.ts
1566
+ var FREEMAIL = /* @__PURE__ */ new Set(["gmail.com", "googlemail.com", "outlook.com", "hotmail.com", "live.com", "yahoo.com", "yahoo.com.au", "icloud.com", "bigpond.com", "optusnet.com.au", "me.com", "proton.me", "protonmail.com"]);
1567
+ var LIMIT = 10;
1568
+ var OP2 = "companies.prepareCrmWrite";
1569
+ async function readRows(http, path, filter) {
1570
+ const res = await http.request({ method: "POST", idempotent: true, path, body: { MaxRecords: LIMIT * 5, filter }, operation: OP2 });
1571
+ return unwrapItems(res).items;
1572
+ }
1573
+ var str = (v) => typeof v === "string" && v !== "" ? v : null;
1574
+ var bool = (v) => typeof v === "boolean" ? v : null;
1575
+ var norm = (s) => s.toLowerCase().replace(/[^a-z0-9]+/g, " ").replace(/\b(pty|ltd|limited|inc|llc)\b/g, "").replace(/\s+/g, " ").trim();
1576
+ function domainOf(input) {
1577
+ if (!input) return null;
1578
+ const host = input.trim().toLowerCase().replace(/^[a-z]+:\/\//, "").replace(/^.*@/, "").replace(/^www\./, "").split(/[/?#:]/)[0] ?? "";
1579
+ return host.includes(".") && !FREEMAIL.has(host) ? host : null;
1580
+ }
1581
+ function candidate(r) {
1582
+ return { id: r.id, companyName: str(r.companyName), webAddress: str(r.webAddress), phone: str(r.phone), isActive: bool(r.isActive) };
1583
+ }
1584
+ function has(v) {
1585
+ return v !== void 0 && v !== null && v !== "";
1586
+ }
1587
+ async function prepareCrmWrite(resource, input, http) {
1588
+ const intents = ["create_company", "add_contact", "update_contact", "update_company"];
1589
+ if (!input || !intents.includes(input.intent)) throw new AutotaskConfigError(`prepareCrmWrite: intent must be one of ${intents.join(", ")}`);
1590
+ const co = input.company ?? {};
1591
+ const ct = input.contact ?? {};
1592
+ const caveats = [
1593
+ "Companies, resources and countries are blind surfaces on some tenants: a miss is not proof a record is absent, and owner / country candidates are lookups, not lists.",
1594
+ "Company name matching is `contains` (exact names ranked first); there is no semantic search in the SDK."
1595
+ ];
1596
+ const needsInput = [];
1597
+ let candidates = [];
1598
+ let matchedBy = null;
1599
+ let searched = false;
1600
+ if (has(co.id)) {
1601
+ searched = true;
1602
+ const rows = await readRows(http, "/V1.0/Companies/query", [{ field: "id", op: "eq", value: co.id }]);
1603
+ candidates = rows.map(candidate);
1604
+ matchedBy = candidates.length ? "id" : null;
1605
+ }
1606
+ const domain = domainOf(co.domain) ?? domainOf(co.website) ?? domainOf(ct.emailAddress);
1607
+ if (candidates.length === 0 && domain) {
1608
+ searched = true;
1609
+ candidates = (await resource.searchByDomain(domain, { limit: LIMIT })).map((c) => candidate(c));
1610
+ matchedBy = candidates.length ? "domain" : null;
1611
+ }
1612
+ if (candidates.length === 0 && has(co.companyName)) {
1613
+ searched = true;
1614
+ const rows = (await resource.searchByIdentity({ companyName: co.companyName }, { limit: LIMIT })).map((c) => candidate(c));
1615
+ const want = norm(co.companyName);
1616
+ const exact = rows.filter((r) => r.companyName !== null && norm(r.companyName) === want);
1617
+ candidates = exact.length ? exact : rows;
1618
+ matchedBy = candidates.length ? exact.length ? "name_exact" : "name_contains" : null;
1619
+ }
1620
+ const status = !searched ? "not_searched" : candidates.length === 0 ? "none" : candidates.length === 1 ? "matched" : "ambiguous";
1621
+ const companyID = status === "matched" ? candidates[0].id : null;
1622
+ const company = { status, matchedBy, candidates };
1623
+ if (status === "ambiguous") {
1624
+ needsInput.push({ field: "company", why: `${candidates.length} companies match; choose one`, source: "policy", lookup: { candidates: candidates.map((c) => ({ id: c.id, companyName: c.companyName })) }, research: "ask" });
1625
+ }
1626
+ if (status === "none" && input.intent !== "create_company") {
1627
+ needsInput.push({ field: "company", why: "no company matched; give a company id, name or domain, or use intent create_company (a miss is not proof of absence)", source: "policy", research: "ask" });
1628
+ }
1629
+ if (status === "not_searched" && input.intent !== "create_company") {
1630
+ needsInput.push({ field: "company", why: "give a company id, name, domain/website or a contact email to search by", source: "policy", research: "ask" });
1631
+ }
1632
+ let locations = null;
1633
+ if (companyID !== null) {
1634
+ const rows = await readRows(http, "/V1.0/CompanyLocations/query", [{ field: "companyID", op: "eq", value: companyID }]);
1635
+ const items = rows.map((r) => ({ id: r.id, name: str(r.name), isPrimary: bool(r.isPrimary), isActive: bool(r.isActive), city: str(r.city), state: str(r.state), postalCode: str(r.postalCode) }));
1636
+ const active = items.filter((l) => l.isActive !== false);
1637
+ const m = active.filter((l) => has(ct.city) && l.city?.toLowerCase() === String(ct.city).toLowerCase() || has(ct.postalCode) && l.postalCode === String(ct.postalCode));
1638
+ const pick = has(ct.companyLocationID) ? { rule: "matched", locationID: ct.companyLocationID } : active.length === 1 ? { rule: "single", locationID: active[0].id } : active.length > 1 && m.length === 1 ? { rule: "matched", locationID: m[0].id } : active.length > 1 ? { rule: "ask", locationID: null } : { rule: "none", locationID: null };
1639
+ locations = { items, pick };
1640
+ if (input.intent === "add_contact" && pick.locationID === null) {
1641
+ needsInput.push({
1642
+ field: "companyLocationID",
1643
+ why: active.length > 1 ? "the company has several active locations; do not guess" : "the company has no active location; create or reactivate one first",
1644
+ source: "policy",
1645
+ lookup: { candidates: active.map((l) => ({ id: l.id, name: l.name, isPrimary: l.isPrimary })) },
1646
+ research: "ask"
1647
+ });
1648
+ }
1649
+ }
1650
+ let contacts = null;
1651
+ if (input.intent !== "create_company" && (has(ct.emailAddress) || has(ct.firstName) && has(ct.lastName) || has(ct.id))) {
1652
+ const pickFields = (r) => ({ id: r.id, firstName: r.firstName, lastName: r.lastName, emailAddress: r.emailAddress, companyID: r.companyID, companyLocationID: r.companyLocationID, isActive: r.isActive });
1653
+ let rows = [];
1654
+ let by = null;
1655
+ if (has(ct.id)) rows = await readRows(http, "/V1.0/Contacts/query", [{ field: "id", op: "eq", value: ct.id }]);
1656
+ else if (has(ct.emailAddress)) {
1657
+ rows = await readRows(http, "/V1.0/Contacts/query", [{ field: "emailAddress", op: "eq", value: ct.emailAddress }]);
1658
+ by = rows.length ? "email" : null;
1659
+ }
1660
+ if (rows.length === 0 && has(ct.firstName) && has(ct.lastName)) {
1661
+ const f = [{ field: "firstName", op: "eq", value: ct.firstName }, { field: "lastName", op: "eq", value: ct.lastName }];
1662
+ if (companyID !== null) f.push({ field: "companyID", op: "eq", value: companyID });
1663
+ rows = await readRows(http, "/V1.0/Contacts/query", f);
1664
+ by = rows.length ? "name" : null;
1665
+ }
1666
+ contacts = { duplicates: rows.slice(0, LIMIT).map(pickFields), matchedBy: by };
1667
+ if (input.intent === "update_contact" && rows.length === 0) {
1668
+ needsInput.push({ field: "contact", why: "no contact matched; give a contact id or email", source: "policy", research: "ask" });
1669
+ }
1670
+ }
1671
+ if (input.intent === "create_company" && status !== "matched") {
1672
+ const eff = effectiveRequiredFields(null, RELATIONSHIP_MAP.companies?.zoneFacts);
1673
+ const research = (f) => ["companyType", "ownerResourceID", "countryID"].includes(f) ? "lookup" : "web_if_available_else_ask";
1674
+ const lookups = {
1675
+ companyType: { via: "operations.fieldCatalog", resource: "companies", field: "companyType", labels: true, note: "map the user wording to one returned label; never hardcode ids" },
1676
+ ownerResourceID: { via: "resources.get / the staff resolver", note: "owners cannot be enumerated on a blind surface: take a known resource id and confirm it with resources.get" },
1677
+ countryID: { via: "countries.query", note: "resolve the country name to an id; never guess" }
1678
+ };
1679
+ const wanted = [
1680
+ ...eff.fields.filter((f) => f !== "isActive").map((f) => [f, "live-proven"]),
1681
+ ...["address1", "city", "state", "postalCode", "countryID"].map((f) => [f, "policy"])
1682
+ ];
1683
+ for (const [f, source] of wanted) {
1684
+ if (has(co[f]) || needsInput.some((n) => n.field === f)) continue;
1685
+ needsInput.push({ field: f, why: source === "policy" ? `CRM policy: a new company needs ${f} (the vendor auto-creates a "Primary Address" location from it)` : `the vendor requires ${f} to create a company`, source, ...lookups[f] ? { lookup: lookups[f] } : {}, research: research(f) });
1686
+ }
1687
+ }
1688
+ if (input.intent === "add_contact") {
1689
+ for (const f of ["firstName", "lastName"]) if (!has(ct[f])) needsInput.push({ field: f, why: `the vendor requires contact ${f}`, source: "live-proven", research: "ask" });
1690
+ if (!has(ct.emailAddress) && !has(ct.phone) && !has(ct.mobilePhone)) needsInput.push({ field: "emailAddress", why: "CRM policy: a contact needs an email or a phone number", source: "policy", research: "ask" });
1691
+ }
1692
+ let next = null;
1693
+ const isInactive = (d) => d.isActive === false || d.isActive === 0;
1694
+ const dups = contacts?.duplicates ?? [];
1695
+ const sameCompany = companyID === null ? dups : dups.filter((d) => d.companyID === companyID);
1696
+ const elsewhere = companyID === null ? [] : dups.filter((d) => d.companyID !== companyID && !isInactive(d));
1697
+ if (input.intent === "add_contact" && sameCompany.length === 0 && elsewhere.length > 0) {
1698
+ needsInput.push({ field: "contact", why: `a contact with this ${contacts?.matchedBy ?? "identity"} is active at another company: update it, move it with contacts.moveToCompany, or confirm a second record`, source: "policy", lookup: { duplicates: elsewhere }, research: "ask" });
1699
+ }
1700
+ if (needsInput.length === 0) {
1701
+ const dup = sameCompany[0];
1702
+ if (input.intent === "create_company") next = status === "matched" ? { operation: "companies.update", note: "the company already exists: update it instead of creating" } : { operation: "companies.create", note: 'then re-run with intent add_contact: the vendor auto-creates a "Primary Address" location (rename it with company_locations.update; locations cannot be deleted via the API)' };
1703
+ else if (input.intent === "add_contact") next = dup ? { operation: "contacts.update", note: `a contact already matches by ${contacts?.matchedBy ?? "id"} (id ${String(dup.id)}): update it, or move it with contacts.moveToCompany if it belongs to another company` } : { operation: "contacts.create", note: `set companyID ${String(companyID)} and companyLocationID ${String(locations?.pick.locationID)}` };
1704
+ else if (input.intent === "update_contact") next = { operation: "contacts.update", note: "send only the changed fields" };
1705
+ else next = { operation: "companies.update", note: "send only the changed fields" };
1706
+ }
1707
+ return { intent: input.intent, company, locations, contacts, needsInput, next, caveats };
1708
+ }
1709
+
1564
1710
  // src/resources/special/companies/unbilledWorkTypes.ts
1565
1711
  var UNBILLED_WORK_NOTE_CHARS_DEFAULT = 400;
1566
1712
  var UNBILLED_WORK_JOIN_SET_CAP = 300;
@@ -1671,7 +1817,7 @@ function validateUnbilledWorkOptions(companyId, opts) {
1671
1817
  }
1672
1818
 
1673
1819
  // src/resources/special/companies/unbilledWork.ts
1674
- var OP2 = "companies.unbilledWork";
1820
+ var OP3 = "companies.unbilledWork";
1675
1821
  function capJoinSet(ids2, cap) {
1676
1822
  const sorted = [...new Set(ids2)].sort((a, b) => a - b);
1677
1823
  if (sorted.length <= cap) return { ids: sorted, truncated: false };
@@ -1708,14 +1854,14 @@ async function readCompanyAndParent(resource, id, includeParent) {
1708
1854
  }
1709
1855
  async function readJoinSets(http, companyIds, v) {
1710
1856
  const ticketsCond = [{ field: "companyID", op: "in", value: companyIds }];
1711
- const ticketsRaw = await boundedPart(http, "/V1.0/Tickets/query", ticketsCond, v.limit, OP2);
1857
+ const ticketsRaw = await boundedPart(http, "/V1.0/Tickets/query", ticketsCond, v.limit, OP3);
1712
1858
  const projectsCond = [{ field: "companyID", op: "in", value: companyIds }];
1713
- const projectsRaw = await boundedPart(http, "/V1.0/Projects/query", projectsCond, v.limit, OP2);
1859
+ const projectsRaw = await boundedPart(http, "/V1.0/Projects/query", projectsCond, v.limit, OP3);
1714
1860
  const projectIds = projectsRaw.items.map((p) => num(p.id)).filter((n) => n > 0);
1715
1861
  let taskIds = [];
1716
1862
  let tasksHasMore = false;
1717
1863
  if (projectIds.length > 0) {
1718
- const tasksRaw = await boundedPart(http, "/V1.0/Tasks/query", [{ field: "projectID", op: "in", value: projectIds }], v.limit, OP2);
1864
+ const tasksRaw = await boundedPart(http, "/V1.0/Tasks/query", [{ field: "projectID", op: "in", value: projectIds }], v.limit, OP3);
1719
1865
  taskIds = tasksRaw.items.map((t) => num(t.id)).filter((n) => n > 0);
1720
1866
  tasksHasMore = tasksRaw.hasMore;
1721
1867
  }
@@ -1739,7 +1885,7 @@ async function readBillingPart(http, v, companyIds) {
1739
1885
  }
1740
1886
  const billingFields = resolveFieldsPreset("billing_join", "billing_items");
1741
1887
  const billingIncludeFields = [...billingFields, "id", ...v.includeNotes ? ["description"] : []];
1742
- const billingRaw = await boundedPart(http, "/V1.0/BillingItems/query", billingConditions, v.limit, OP2, billingIncludeFields);
1888
+ const billingRaw = await boundedPart(http, "/V1.0/BillingItems/query", billingConditions, v.limit, OP3, billingIncludeFields);
1743
1889
  let billingItems = billingRaw.items;
1744
1890
  let billingNoteCharsTruncated = 0;
1745
1891
  if (v.includeNotes) {
@@ -1783,7 +1929,7 @@ async function readTimePart(http, v, join) {
1783
1929
  if (v.dateFrom !== void 0) timeConditions.push({ field: "dateWorked", op: "gte", value: v.dateFrom });
1784
1930
  if (v.dateTo !== void 0) timeConditions.push({ field: "dateWorked", op: "lte", value: v.dateTo });
1785
1931
  const timeFields = ["taskID", "ticketID", "hoursWorked", "dateWorked", "id", ...v.includeNotes ? ["summaryNotes"] : []];
1786
- const raw = await boundedPart(http, "/V1.0/TimeEntries/query", timeConditions, v.limit, OP2, timeFields);
1932
+ const raw = await boundedPart(http, "/V1.0/TimeEntries/query", timeConditions, v.limit, OP3, timeFields);
1787
1933
  entries = raw.items;
1788
1934
  let cappedItems = entries;
1789
1935
  let noteCharsTruncated = 0;
@@ -2007,6 +2153,10 @@ var CompaniesResource = class extends BaseResource {
2007
2153
  unbilledWork(id, opts) {
2008
2154
  return unbilledWork(this, id, opts, this.http);
2009
2155
  }
2156
+ /** Node-parity special op (hand-written in src/resources/special/companies.ts). */
2157
+ prepareCrmWrite(input) {
2158
+ return prepareCrmWrite(this, input, this.http);
2159
+ }
2010
2160
  };
2011
2161
 
2012
2162
  // src/resources/company_alerts.ts
@@ -4834,11 +4984,11 @@ var ContractServiceUnitsResource = class extends BaseResource {
4834
4984
  };
4835
4985
 
4836
4986
  // src/resources/special/contracts/lineOfBusiness.ts
4837
- var OP3 = "contracts.lineOfBusiness";
4987
+ var OP4 = "contracts.lineOfBusiness";
4838
4988
  function positiveId(value, name) {
4839
4989
  const n = typeof value === "string" && /^[0-9]+$/.test(value.trim()) ? Number(value) : value;
4840
4990
  if (typeof n !== "number" || !Number.isSafeInteger(n) || n < 1) {
4841
- throw new AutotaskConfigError(OP3 + ": " + name + " must be a positive integer id (got " + String(value) + ")", { operation: OP3 });
4991
+ throw new AutotaskConfigError(OP4 + ": " + name + " must be a positive integer id (got " + String(value) + ")", { operation: OP4 });
4842
4992
  }
4843
4993
  return n;
4844
4994
  }
@@ -4864,7 +5014,7 @@ async function lineOfBusiness(resource, contractIdIn, http, associationIdIn) {
4864
5014
  markers: { association: "missing", organizationalLevel2: "missing", organizationalLevel1: "missing" }
4865
5015
  };
4866
5016
  }
4867
- const chain = await resolveLineOfBusinessChain(http, associationId, { budget: 3, operation: OP3, onError: "propagate" });
5017
+ const chain = await resolveLineOfBusinessChain(http, associationId, { budget: 3, operation: OP4, onError: "propagate" });
4868
5018
  const { association, organizationalLevel2, organizationalLevel1, markers } = chain.lineOfBusiness;
4869
5019
  return { contractId, associationSource, association, organizationalLevel2, organizationalLevel1, markers };
4870
5020
  }
@@ -5338,7 +5488,7 @@ function appendWiresNote(wires, note) {
5338
5488
  wires.note = (wires.note ? wires.note + " | " : "") + note;
5339
5489
  }
5340
5490
  function makeFetchCtx(rawHttp, v, contractId, budget) {
5341
- const OP11 = PROFITABILITY_OP;
5491
+ const OP12 = PROFITABILITY_OP;
5342
5492
  const id = parseIdentifier(contractId, "contractId");
5343
5493
  const idNum = typeof id === "number" ? id : Number(id);
5344
5494
  const wires = { issued: 0, requests: 0, budget };
@@ -5350,19 +5500,19 @@ function makeFetchCtx(rawHttp, v, contractId, budget) {
5350
5500
  } };
5351
5501
  const readPart = (path, conditions, fields) => {
5352
5502
  wires.issued += 1;
5353
- return degradable(() => boundedPart(http, path, conditions, v.limit, OP11, fields));
5503
+ return degradable(() => boundedPart(http, path, conditions, v.limit, OP12, fields));
5354
5504
  };
5355
5505
  const truncatedMonths = {};
5356
5506
  const windowedPart = async (name, key, path, cond, fields, dateField) => {
5357
5507
  const first = await readPart(path, cond, fields);
5358
5508
  if (first.absent !== void 0 || !first.hasMore) return { part: stampPart(key, first, cond), rows: first.items };
5359
- const wp = await degradable(() => expandByMonth(http, path, cond, v.limit, OP11, fields, dateField, v.dateFrom, v.dateTo, wires));
5509
+ const wp = await degradable(() => expandByMonth(http, path, cond, v.limit, OP12, fields, dateField, v.dateFrom, v.dateTo, wires));
5360
5510
  if (!("truncatedMonths" in wp)) return { part: stampPart(key, wp, cond), rows: [] };
5361
5511
  const { truncatedMonths: tm, ...part } = wp;
5362
5512
  truncatedMonths[name] = tm;
5363
5513
  return { part, rows: wp.items };
5364
5514
  };
5365
- return { v, OP: OP11, id, idNum, rawHttp, http, wires, windowOn, readPart, windowedPart, truncatedMonths };
5515
+ return { v, OP: OP12, id, idNum, rawHttp, http, wires, windowOn, readPart, windowedPart, truncatedMonths };
5366
5516
  }
5367
5517
 
5368
5518
  // src/resources/special/contracts/profitability.stages.ts
@@ -10734,22 +10884,22 @@ function uiLinkFor(http, entity, id) {
10734
10884
  }
10735
10885
 
10736
10886
  // src/resources/special/ticket_charges.ts
10737
- var OP4 = "ticket_charges.moveToTicket";
10887
+ var OP5 = "ticket_charges.moveToTicket";
10738
10888
  var COPY = ["name", "description", "chargeType", "billingCodeID", "productID", "unitQuantity", "unitCost", "unitPrice", "datePurchased", "isBillableToCompany", "notes", "purchaseOrderNumber", "internalPurchaseOrderNumber"];
10739
10889
  var CONTRACT_SCOPED = ["contractServiceID", "contractServiceBundleID"];
10740
10890
  async function readCharge(http, id, retries) {
10741
- const rows = await queryRows(http, "TicketCharges", [{ field: "id", op: "eq", value: id }], OP4, retries);
10891
+ const rows = await queryRows(http, "TicketCharges", [{ field: "id", op: "eq", value: id }], OP5, retries);
10742
10892
  return rows[0] ?? null;
10743
10893
  }
10744
10894
  async function checkSource({ http, source, sourceId, sourceTicketId, toTicketId }) {
10745
10895
  if (source.isBilled === true) {
10746
- throw refuse("MOVE_BLOCKED_BILLED", `${OP4}: ticket charge ${sourceId} is billed \u2014 move it in the Autotask UI`, { sourceId, toTicketId }, OP4, {
10896
+ throw refuse("MOVE_BLOCKED_BILLED", `${OP5}: ticket charge ${sourceId} is billed \u2014 move it in the Autotask UI`, { sourceId, toTicketId }, OP5, {
10747
10897
  manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "tickets", sourceTicketId), reason: "billed charges are not moved by the API (the ticket link opens its charges)" }
10748
10898
  });
10749
10899
  }
10750
10900
  }
10751
10901
  var SPEC = {
10752
- operation: OP4,
10902
+ operation: OP5,
10753
10903
  entity: "ticket_charge",
10754
10904
  readSource: readCharge,
10755
10905
  sourceTicketId: (s) => typeof s.ticketID === "number" ? s.ticketID : null,
@@ -11418,12 +11568,12 @@ async function slaHealthCheck(_resource, id, opts = {}, http) {
11418
11568
  "tickets.slaHealthCheck"
11419
11569
  );
11420
11570
  const slas = rows.map((row) => {
11421
- const bool = (v) => typeof v === "boolean" ? v : null;
11571
+ const bool2 = (v) => typeof v === "boolean" ? v : null;
11422
11572
  const num2 = (v) => typeof v === "number" ? v : null;
11423
11573
  const milestones = [
11424
- { name: "firstResponse", met: bool(row.isFirstResponseMet), elapsedHours: num2(row.firstResponseElapsedHours) },
11425
- { name: "resolution", met: bool(row.isResolutionMet), elapsedHours: num2(row.resolutionElapsedHours) },
11426
- { name: "resolutionPlan", met: bool(row.isResolutionPlanMet), elapsedHours: num2(row.resolutionPlanElapsedHours) }
11574
+ { name: "firstResponse", met: bool2(row.isFirstResponseMet), elapsedHours: num2(row.firstResponseElapsedHours) },
11575
+ { name: "resolution", met: bool2(row.isResolutionMet), elapsedHours: num2(row.resolutionElapsedHours) },
11576
+ { name: "resolutionPlan", met: bool2(row.isResolutionPlanMet), elapsedHours: num2(row.resolutionPlanElapsedHours) }
11427
11577
  ];
11428
11578
  return { id: Number(row.id), name: row.serviceLevelAgreementName ?? null, milestones };
11429
11579
  });
@@ -11441,11 +11591,11 @@ async function summary(_resource, id, opts = {}, http) {
11441
11591
  const now = opts.now ?? /* @__PURE__ */ new Date();
11442
11592
  const ageDays = typeof ticket.createDate === "string" && ticket.createDate !== "" ? Math.max(0, Math.floor((now.getTime() - Date.parse(ticket.createDate)) / MS)) : null;
11443
11593
  const pick = (v) => typeof v === "number" ? v : null;
11444
- const str = (v) => typeof v === "string" && v !== "" ? v : null;
11594
+ const str2 = (v) => typeof v === "string" && v !== "" ? v : null;
11445
11595
  return {
11446
11596
  ticketId,
11447
- ticketNumber: str(ticket.ticketNumber),
11448
- title: str(ticket.title),
11597
+ ticketNumber: str2(ticket.ticketNumber),
11598
+ title: str2(ticket.title),
11449
11599
  description: desc.value,
11450
11600
  descriptionLength: desc.length,
11451
11601
  descriptionTruncated: desc.truncated,
@@ -11455,8 +11605,8 @@ async function summary(_resource, id, opts = {}, http) {
11455
11605
  contactID: pick(ticket.contactID),
11456
11606
  assignedResourceID: pick(ticket.assignedResourceID),
11457
11607
  projectID: pick(ticket.projectID),
11458
- createDate: str(ticket.createDate),
11459
- dueDateTime: str(ticket.dueDateTime),
11608
+ createDate: str2(ticket.createDate),
11609
+ dueDateTime: str2(ticket.dueDateTime),
11460
11610
  ageDays
11461
11611
  };
11462
11612
  }
@@ -12203,7 +12353,7 @@ async function fullContext(resource, id, opts = {}, http) {
12203
12353
  }
12204
12354
 
12205
12355
  // src/resources/special/tickets/forResource.ts
12206
- var OP5 = "tickets.forResource";
12356
+ var OP6 = "tickets.forResource";
12207
12357
  var TICKET_CHUNK = 100;
12208
12358
  var LIMIT_DEFAULT = 100;
12209
12359
  var LIMIT_MAX = 500;
@@ -12212,7 +12362,7 @@ var TICKET_FIELDS2 = ["id", "ticketNumber", "title", "status", "assignedResource
12212
12362
  function positiveId2(value, name) {
12213
12363
  const n = typeof value === "string" && /^\d+$/.test(value.trim()) ? Number(value) : value;
12214
12364
  if (typeof n !== "number" || !Number.isSafeInteger(n) || n < 1) {
12215
- throw new AutotaskConfigError(`${OP5}: ${name} must be a positive integer id (got ${String(value)})`, { operation: OP5 });
12365
+ throw new AutotaskConfigError(`${OP6}: ${name} must be a positive integer id (got ${String(value)})`, { operation: OP6 });
12216
12366
  }
12217
12367
  return n;
12218
12368
  }
@@ -12222,28 +12372,28 @@ async function ticketPage(http, filter, maxRecords, fields) {
12222
12372
  idempotent: true,
12223
12373
  path: "/V1.0/Tickets/query",
12224
12374
  body: { MaxRecords: maxRecords, filter, IncludeFields: fields },
12225
- operation: OP5
12375
+ operation: OP6
12226
12376
  });
12227
- const page = unwrapItems(res, OP5);
12377
+ const page = unwrapItems(res, OP6);
12228
12378
  return { rows: page.items, more: page.pageDetails.nextPageUrl !== null };
12229
12379
  }
12230
12380
  async function forResource(_resource, resourceIDIn, http, opts = {}) {
12231
- if (opts === null || typeof opts !== "object") throw new AutotaskConfigError(`${OP5}: opts must be an object`, { operation: OP5 });
12381
+ if (opts === null || typeof opts !== "object") throw new AutotaskConfigError(`${OP6}: opts must be an object`, { operation: OP6 });
12232
12382
  const resourceID = positiveId2(resourceIDIn, "resourceID");
12233
12383
  const scope = opts.scope ?? "both";
12234
12384
  if (scope !== "primary" && scope !== "secondary" && scope !== "both") {
12235
- throw new AutotaskConfigError(`${OP5}: scope must be 'primary', 'secondary' or 'both' (got ${JSON.stringify(opts.scope)})`, { operation: OP5 });
12385
+ throw new AutotaskConfigError(`${OP6}: scope must be 'primary', 'secondary' or 'both' (got ${JSON.stringify(opts.scope)})`, { operation: OP6 });
12236
12386
  }
12237
12387
  if (opts.openOnly !== void 0 && typeof opts.openOnly !== "boolean") {
12238
- throw new AutotaskConfigError(`${OP5}: openOnly must be a boolean`, { operation: OP5 });
12388
+ throw new AutotaskConfigError(`${OP6}: openOnly must be a boolean`, { operation: OP6 });
12239
12389
  }
12240
12390
  const limit = opts.limit === void 0 ? LIMIT_DEFAULT : opts.limit;
12241
12391
  if (!Number.isInteger(limit) || limit < 1 || limit > LIMIT_MAX) {
12242
- throw new AutotaskConfigError(`${OP5}: limit must be an integer 1-${LIMIT_MAX} (got ${String(opts.limit)})`, { operation: OP5 });
12392
+ throw new AutotaskConfigError(`${OP6}: limit must be an integer 1-${LIMIT_MAX} (got ${String(opts.limit)})`, { operation: OP6 });
12243
12393
  }
12244
12394
  const includeFields = opts.includeFields ?? [];
12245
12395
  if (!Array.isArray(includeFields) || includeFields.some((f) => typeof f !== "string" || f.length === 0)) {
12246
- throw new AutotaskConfigError(`${OP5}: includeFields must be a list of field names`, { operation: OP5 });
12396
+ throw new AutotaskConfigError(`${OP6}: includeFields must be a list of field names`, { operation: OP6 });
12247
12397
  }
12248
12398
  const openOnly = opts.openOnly === true;
12249
12399
  const fields = [.../* @__PURE__ */ new Set([...TICKET_FIELDS2, ...includeFields])];
@@ -12258,9 +12408,9 @@ async function forResource(_resource, resourceIDIn, http, opts = {}) {
12258
12408
  idempotent: true,
12259
12409
  path: "/V1.0/TicketSecondaryResources/query",
12260
12410
  body: { MaxRecords: LINK_READ_CAP, filter: [{ field: "resourceID", op: "eq", value: resourceID }], IncludeFields: ["ticketID"] },
12261
- operation: OP5
12411
+ operation: OP6
12262
12412
  });
12263
- const page = unwrapItems(res, OP5);
12413
+ const page = unwrapItems(res, OP6);
12264
12414
  for (const row of page.items) if (typeof row.ticketID === "number" && !linkIds.includes(row.ticketID)) linkIds.push(row.ticketID);
12265
12415
  if (page.items.length >= LINK_READ_CAP || page.pageDetails.nextPageUrl !== null) capped = true;
12266
12416
  }
@@ -12313,11 +12463,11 @@ async function forResource(_resource, resourceIDIn, http, opts = {}) {
12313
12463
  }
12314
12464
 
12315
12465
  // src/resources/special/tickets/lineOfBusiness.ts
12316
- var OP6 = "tickets.lineOfBusiness";
12466
+ var OP7 = "tickets.lineOfBusiness";
12317
12467
  function positiveId3(value, name) {
12318
12468
  const n = typeof value === "string" && /^[0-9]+$/.test(value.trim()) ? Number(value) : value;
12319
12469
  if (typeof n !== "number" || !Number.isSafeInteger(n) || n < 1) {
12320
- throw new AutotaskConfigError(OP6 + ": " + name + " must be a positive integer id (got " + String(value) + ")", { operation: OP6 });
12470
+ throw new AutotaskConfigError(OP7 + ": " + name + " must be a positive integer id (got " + String(value) + ")", { operation: OP7 });
12321
12471
  }
12322
12472
  return n;
12323
12473
  }
@@ -12340,7 +12490,7 @@ async function lineOfBusiness2(resource, ticketIdIn, http, associationIdIn) {
12340
12490
  if (contractRef !== void 0) {
12341
12491
  const client = http.client;
12342
12492
  if (client === void 0) {
12343
- throw new AutotaskConfigError(OP6 + ": the transport is not wired to an AutotaskClient (the composite must run through the client)", { operation: OP6 });
12493
+ throw new AutotaskConfigError(OP7 + ": the transport is not wired to an AutotaskClient (the composite must run through the client)", { operation: OP7 });
12344
12494
  }
12345
12495
  try {
12346
12496
  const contract = await client.contracts.get(contractRef);
@@ -12371,7 +12521,7 @@ async function lineOfBusiness2(resource, ticketIdIn, http, associationIdIn) {
12371
12521
  markers: { association: contractUnread ? "unresolved" : "missing", organizationalLevel2: "missing", organizationalLevel1: "missing" }
12372
12522
  };
12373
12523
  }
12374
- const chain = await resolveLineOfBusinessChain(http, associationId, { budget: 3, operation: OP6, onError: "propagate" });
12524
+ const chain = await resolveLineOfBusinessChain(http, associationId, { budget: 3, operation: OP7, onError: "propagate" });
12375
12525
  const { association, organizationalLevel2, organizationalLevel1, markers } = chain.lineOfBusiness;
12376
12526
  return { ticketId, associationSource, contractId, association, organizationalLevel2, organizationalLevel1, markers };
12377
12527
  }
@@ -12662,12 +12812,12 @@ async function postedStateQuery(_resource, mode, http, opts) {
12662
12812
  return { items, hasMore: read.hasMore || incomplete, window, approvedCount, unapprovedCount: items.length - approvedCount };
12663
12813
  }
12664
12814
  async function postedWithin(_resource, from, to, http, opts, opLabel = "time_entries.postedWithin") {
12665
- const OP11 = opLabel;
12666
- if (opts?.tz !== void 0) validateZone(opts.tz, OP11);
12667
- const fromB = customRangeDay(from, "from", void 0, OP11);
12668
- const toB = customRangeDay(to, "to", void 0, OP11);
12815
+ const OP12 = opLabel;
12816
+ if (opts?.tz !== void 0) validateZone(opts.tz, OP12);
12817
+ const fromB = customRangeDay(from, "from", void 0, OP12);
12818
+ const toB = customRangeDay(to, "to", void 0, OP12);
12669
12819
  if (fromB.ms > toB.ms) {
12670
- throw new AutotaskConfigError(`${OP11}: from (${iso2(fromB.ms)}) must not be after to (${iso2(toB.ms)})`, { operation: OP11 });
12820
+ throw new AutotaskConfigError(`${OP12}: from (${iso2(fromB.ms)}) must not be after to (${iso2(toB.ms)})`, { operation: OP12 });
12671
12821
  }
12672
12822
  const windowToMs = toB.ms + DAY_MS2 - 1;
12673
12823
  const filter = [
@@ -12676,8 +12826,8 @@ async function postedWithin(_resource, from, to, http, opts, opLabel = "time_ent
12676
12826
  ];
12677
12827
  if (opts?.ticketID !== void 0) filter.push({ field: "ticketID", op: "eq", value: opts.ticketID });
12678
12828
  if (opts?.resourceID !== void 0) filter.push({ field: "resourceID", op: "eq", value: opts.resourceID });
12679
- const { items, hasMore } = await queryTimeEntries(_resource, filter, clampWindowLimit(opts?.limit), OP11, http);
12680
- const { states, incomplete } = await postedStateOf(http, items, OP11);
12829
+ const { items, hasMore } = await queryTimeEntries(_resource, filter, clampWindowLimit(opts?.limit), OP12, http);
12830
+ const { states, incomplete } = await postedStateOf(http, items, OP12);
12681
12831
  let postedCount = 0;
12682
12832
  let unpostedCount = 0;
12683
12833
  for (const state of states.values()) {
@@ -12887,7 +13037,7 @@ async function resolveCode(http, family, familyLabel2, pinnedId, departments, op
12887
13037
  }
12888
13038
 
12889
13039
  // src/resources/special/time_entries/createForResource.ts
12890
- var OP7 = "time_entries.createForResource";
13040
+ var OP8 = "time_entries.createForResource";
12891
13041
  var PARAM_KEYS = /* @__PURE__ */ new Set([
12892
13042
  "internallAllocationCodeID",
12893
13043
  "ticketID",
@@ -12923,30 +13073,30 @@ function validateCreateParams(params) {
12923
13073
  if (pinCount > 1) {
12924
13074
  throw new AutotaskConfigError(
12925
13075
  "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)",
12926
- { operation: OP7 }
13076
+ { operation: OP8 }
12927
13077
  );
12928
13078
  }
12929
13079
  if (pins.role !== void 0 && pins.code !== void 0) {
12930
13080
  throw new AutotaskConfigError(
12931
13081
  "createForResource: a pinned roleID is only meaningful with the task/ticket linkage (internallAllocationCodeID pins the internal linkage and owns the code) \u2014 drop one",
12932
- { operation: OP7 }
13082
+ { operation: OP8 }
12933
13083
  );
12934
13084
  }
12935
13085
  if (params.hoursWorked !== void 0 && (typeof params.hoursWorked !== "number" || !Number.isFinite(params.hoursWorked) || params.hoursWorked <= 0)) {
12936
13086
  throw new AutotaskConfigError(
12937
13087
  `createForResource: hoursWorked must be a positive finite number (got ${String(params.hoursWorked)})`,
12938
- { operation: OP7 }
13088
+ { operation: OP8 }
12939
13089
  );
12940
13090
  }
12941
13091
  return pins;
12942
13092
  }
12943
13093
  async function assertResourceActive(http, rid) {
12944
- const rec = await fetchItem2(http, `/V1.0/Resources/${rid}`, OP7);
13094
+ const rec = await fetchItem2(http, `/V1.0/Resources/${rid}`, OP8);
12945
13095
  if (rec === null) {
12946
13096
  throw new ResourceInvalidError(`Resource ${rid} was not found \u2014 a time entry cannot attach to a missing resource`, {
12947
13097
  resourceIds: [rid],
12948
13098
  details: { idRefs: [{ entity: "resource", id: rid }] },
12949
- operation: OP7,
13099
+ operation: OP8,
12950
13100
  category: "not_found",
12951
13101
  suggestedAction: "Resolve the resource id (resources.get / resources.search) and retry."
12952
13102
  });
@@ -12955,7 +13105,7 @@ async function assertResourceActive(http, rid) {
12955
13105
  throw new ResourceInvalidError(`Resource ${rid} is not active (isActive !== true) \u2014 the vendor would reject a time entry for an inactive resource`, {
12956
13106
  resourceIds: [rid],
12957
13107
  details: { idRefs: [{ entity: "resource", id: rid }] },
12958
- operation: OP7,
13108
+ operation: OP8,
12959
13109
  suggestedAction: "Activate the resource or pick an active one."
12960
13110
  });
12961
13111
  }
@@ -12965,11 +13115,11 @@ async function resolveAllocationCode(http, rid, linkage, pins, departments) {
12965
13115
  const pinnedForCode = pins.code !== void 0 ? pins.code : pins.role;
12966
13116
  let code = null;
12967
13117
  const familiesTried = [];
12968
- code = await resolveCode(http, familyOf(linkage), familyLabel(familyOf(linkage)), pinnedForCode, departments, OP7);
13118
+ code = await resolveCode(http, familyOf(linkage), familyLabel(familyOf(linkage)), pinnedForCode, departments, OP8);
12969
13119
  familiesTried.push({ family: familyOf(linkage), label: familyLabel(familyOf(linkage)), filters: [{ field: "useType", op: "eq", value: familyOf(linkage) }, { field: "isActive", op: "eq", value: true }] });
12970
13120
  if (code === null && linkage === "task" && pins.role === void 0) {
12971
13121
  usedLinkage = "internal";
12972
- code = await resolveCode(http, USE_TYPE_INTERNAL, familyLabel(USE_TYPE_INTERNAL), void 0, departments, OP7);
13122
+ code = await resolveCode(http, USE_TYPE_INTERNAL, familyLabel(USE_TYPE_INTERNAL), void 0, departments, OP8);
12973
13123
  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 }] });
12974
13124
  }
12975
13125
  if (code === null) {
@@ -12979,7 +13129,7 @@ async function resolveAllocationCode(http, rid, linkage, pins, departments) {
12979
13129
  families: familiesTried,
12980
13130
  resourceDepartments: [...departments].sort((a, b) => a - b)
12981
13131
  },
12982
- { operation: OP7 }
13132
+ { operation: OP8 }
12983
13133
  );
12984
13134
  }
12985
13135
  const resolvedCode = codeInfo(code);
@@ -13018,7 +13168,7 @@ function createDryRun2(http, rid, body, linkage, usedLinkage, resolvedCode) {
13018
13168
  }
13019
13169
  return buildDryRunResult(
13020
13170
  {
13021
- operation: OP7,
13171
+ operation: OP8,
13022
13172
  method: "POST",
13023
13173
  path: "/V1.0/TimeEntries",
13024
13174
  ids: [rid],
@@ -13060,7 +13210,7 @@ async function createWithAttempts(resource, http, opts, body, defaults, pins, us
13060
13210
  const gate3 = ROLE_REJECTION_PATTERN.test(text);
13061
13211
  if (usedLinkage !== "internal" && gate3) {
13062
13212
  if (!taskRoleFetched && usedLinkage === "task" && pins.task !== void 0) {
13063
- const task = await fetchItem2(http, `/V1.0/Tasks/${pins.task}`, OP7);
13213
+ const task = await fetchItem2(http, `/V1.0/Tasks/${pins.task}`, OP8);
13064
13214
  taskRoleFetched = true;
13065
13215
  if (task !== null) pushLadder(task.assignedResourceRoleID);
13066
13216
  }
@@ -13086,7 +13236,7 @@ async function createForResource(resource, resourceID, params, opts = {}, http)
13086
13236
  const pins = validateCreateParams(params);
13087
13237
  await assertResourceActive(http, rid);
13088
13238
  const linkage = pins.code !== void 0 ? "internal" : pins.ticket !== void 0 ? "ticket" : pins.task !== void 0 ? "task" : "internal";
13089
- const roleRows = await resourceRoleRows(http, rid, OP7);
13239
+ const roleRows = await resourceRoleRows(http, rid, OP8);
13090
13240
  const departments = roleRows.departments;
13091
13241
  const { usedLinkage, resolvedCode } = await resolveAllocationCode(http, rid, linkage, pins, departments);
13092
13242
  const defaults = windowDefaults(params);
@@ -13109,12 +13259,12 @@ function discoveryField(err, body, defaults) {
13109
13259
  }
13110
13260
 
13111
13261
  // src/resources/special/time_entries/move.ts
13112
- var OP8 = "time_entries.moveToTicket";
13262
+ var OP9 = "time_entries.moveToTicket";
13113
13263
  var COPY2 = ["resourceID", "roleID", "billingCodeID", "dateWorked", "startDateTime", "endDateTime", "hoursWorked", "offsetHours", "summaryNotes", "internalNotes", "isNonBillable", "showOnInvoice"];
13114
13264
  var CONTRACT_SCOPED2 = ["contractID", "contractServiceID", "contractServiceBundleID"];
13115
13265
  async function readTimeEntry(http, id, retries) {
13116
13266
  try {
13117
- const res = await http.request({ method: "GET", path: `/V1.0/TimeEntries/${id}`, operation: OP8, maxRetries: retries });
13267
+ const res = await http.request({ method: "GET", path: `/V1.0/TimeEntries/${id}`, operation: OP9, maxRetries: retries });
13118
13268
  return res?.item ?? null;
13119
13269
  } catch (e) {
13120
13270
  if (isAutotaskError(e) && (e.httpStatus === 404 || e.code === "NOT_FOUND")) return null;
@@ -13124,27 +13274,27 @@ async function readTimeEntry(http, id, retries) {
13124
13274
  async function checkSource2({ http, source, sourceId, toTicketId, retries }) {
13125
13275
  const ids2 = { sourceId, toTicketId };
13126
13276
  if (source.taskID !== null && source.taskID !== void 0) {
13127
- throw refuse("MOVE_SOURCE_UNSUPPORTED", `${OP8}: time entry ${sourceId} is on a task \u2014 only ticket time entries are moved`, ids2, OP8);
13277
+ throw refuse("MOVE_SOURCE_UNSUPPORTED", `${OP9}: time entry ${sourceId} is on a task \u2014 only ticket time entries are moved`, ids2, OP9);
13128
13278
  }
13129
13279
  if (source.billingApprovalDateTime || source.billingApprovalResourceID || source.billingApprovalLevelMostRecent) {
13130
- throw refuse("MOVE_BLOCKED_APPROVED", `${OP8}: time entry ${sourceId} has billing approval set \u2014 move it in the Autotask UI`, ids2, OP8, { manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "time_entries", sourceId), reason: "approved entries are not moved by the API" } });
13280
+ throw refuse("MOVE_BLOCKED_APPROVED", `${OP9}: time entry ${sourceId} has billing approval set \u2014 move it in the Autotask UI`, ids2, OP9, { manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "time_entries", sourceId), reason: "approved entries are not moved by the API" } });
13131
13281
  }
13132
13282
  let billing;
13133
13283
  try {
13134
- billing = await queryRows(http, "BillingItems", [{ field: "timeEntryID", op: "eq", value: sourceId }], OP8, retries);
13284
+ billing = await queryRows(http, "BillingItems", [{ field: "timeEntryID", op: "eq", value: sourceId }], OP9, retries);
13135
13285
  } catch {
13136
- throw refuse("MOVE_BLOCKED_BILLED", `${OP8}: the billing items of time entry ${sourceId} could not be read \u2014 refused (fails closed)`, ids2, OP8, { manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "time_entries", sourceId), reason: "billing state unreadable" } });
13286
+ throw refuse("MOVE_BLOCKED_BILLED", `${OP9}: the billing items of time entry ${sourceId} could not be read \u2014 refused (fails closed)`, ids2, OP9, { manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "time_entries", sourceId), reason: "billing state unreadable" } });
13137
13287
  }
13138
13288
  if (billing.some((b) => b.invoiceID || b.postedDate)) {
13139
- throw refuse("MOVE_BLOCKED_BILLED", `${OP8}: time entry ${sourceId} is billed or posted \u2014 move it in the Autotask UI`, ids2, OP8, { manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "time_entries", sourceId), reason: "billed entries are not moved by the API" } });
13289
+ throw refuse("MOVE_BLOCKED_BILLED", `${OP9}: time entry ${sourceId} is billed or posted \u2014 move it in the Autotask UI`, ids2, OP9, { manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "time_entries", sourceId), reason: "billed entries are not moved by the API" } });
13140
13290
  }
13141
- const attachments = await queryRows(http, "TimeEntryAttachments", [{ field: "timeEntryID", op: "eq", value: sourceId }], OP8, retries);
13291
+ const attachments = await queryRows(http, "TimeEntryAttachments", [{ field: "timeEntryID", op: "eq", value: sourceId }], OP9, retries);
13142
13292
  if (attachments.length > 0) {
13143
- throw refuse("MOVE_BLOCKED_ATTACHMENTS", `${OP8}: time entry ${sourceId} has attachments, which a delete would destroy \u2014 move it in the Autotask UI`, ids2, OP8, { manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "time_entries", sourceId), reason: "entries with attachments are not moved by the API" } });
13293
+ throw refuse("MOVE_BLOCKED_ATTACHMENTS", `${OP9}: time entry ${sourceId} has attachments, which a delete would destroy \u2014 move it in the Autotask UI`, ids2, OP9, { manualAction: { action: "delete_source", uiLink: uiLinkFor(http, "time_entries", sourceId), reason: "entries with attachments are not moved by the API" } });
13144
13294
  }
13145
13295
  }
13146
13296
  var SPEC2 = {
13147
- operation: OP8,
13297
+ operation: OP9,
13148
13298
  entity: "time_entry",
13149
13299
  readSource: readTimeEntry,
13150
13300
  sourceTicketId: (s) => typeof s.ticketID === "number" ? s.ticketID : null,
@@ -13179,7 +13329,7 @@ function moveToTicket2(_resource, sourceId, toTicketId, opts, http) {
13179
13329
  }
13180
13330
 
13181
13331
  // src/resources/special/time_entries/approve.ts
13182
- var OP9 = "time_entries.approve";
13332
+ var OP10 = "time_entries.approve";
13183
13333
  var APPROVAL_LEVELS_PATH = "/V1.0/BillingItemApprovalLevels";
13184
13334
  function toIso(value) {
13185
13335
  if (value instanceof Date) {
@@ -13217,7 +13367,7 @@ async function approve(resource, id, opts = {}, http) {
13217
13367
  if (opts.dryRun === true) {
13218
13368
  return buildDryRunResult(
13219
13369
  {
13220
- operation: OP9,
13370
+ operation: OP10,
13221
13371
  method: "POST",
13222
13372
  path: APPROVAL_LEVELS_PATH,
13223
13373
  ids: [numericId],
@@ -13240,10 +13390,10 @@ async function approve(resource, id, opts = {}, http) {
13240
13390
  method: "POST",
13241
13391
  path: APPROVAL_LEVELS_PATH,
13242
13392
  body,
13243
- operation: OP9,
13393
+ operation: OP10,
13244
13394
  maxRetries: opts.maxRetries
13245
13395
  });
13246
- const levelId = toNumber(parseIdentifier(unwrapOperation(created, OP9).id, "billingItemApprovalLevelID"));
13396
+ const levelId = toNumber(parseIdentifier(unwrapOperation(created, OP10).id, "billingItemApprovalLevelID"));
13247
13397
  await resource.update(
13248
13398
  numericId,
13249
13399
  { billingApprovalDateTime: approvalDateTime, billingApprovalResourceID: approvalResourceID },
@@ -13266,28 +13416,28 @@ async function approve(resource, id, opts = {}, http) {
13266
13416
  }
13267
13417
 
13268
13418
  // src/resources/special/time_entries/recentTickets.ts
13269
- var OP10 = "time_entries.recentTickets";
13419
+ var OP11 = "time_entries.recentTickets";
13270
13420
  var DAY_MS3 = 864e5;
13271
13421
  var TICKET_CHUNK2 = 100;
13272
13422
  var TICKET_FIELDS3 = ["id", "ticketNumber", "title", "status", "assignedResourceID", "companyID"];
13273
13423
  function positiveId4(value, name) {
13274
13424
  const n = typeof value === "string" && /^\d+$/.test(value.trim()) ? Number(value) : value;
13275
13425
  if (typeof n !== "number" || !Number.isSafeInteger(n) || n < 1) {
13276
- throw new AutotaskConfigError(`${OP10}: ${name} must be a positive integer id (got ${String(value)})`, { operation: OP10 });
13426
+ throw new AutotaskConfigError(`${OP11}: ${name} must be a positive integer id (got ${String(value)})`, { operation: OP11 });
13277
13427
  }
13278
13428
  return n;
13279
13429
  }
13280
13430
  async function recentTickets(_resource, from, to, http, opts) {
13281
13431
  if (opts === void 0 || opts === null || typeof opts !== "object") {
13282
- throw new AutotaskConfigError(`${OP10}: opts { resourceID, companyID?, tz?, limit? } is required`, { operation: OP10 });
13432
+ throw new AutotaskConfigError(`${OP11}: opts { resourceID, companyID?, tz?, limit? } is required`, { operation: OP11 });
13283
13433
  }
13284
13434
  const resourceID = positiveId4(opts.resourceID, "resourceID");
13285
13435
  const companyID = opts.companyID === void 0 ? void 0 : positiveId4(opts.companyID, "companyID");
13286
- if (opts.tz !== void 0) validateZone(opts.tz, OP10);
13287
- const fromB = customRangeDay(from, "from", void 0, OP10);
13288
- const toB = customRangeDay(to, "to", void 0, OP10);
13436
+ if (opts.tz !== void 0) validateZone(opts.tz, OP11);
13437
+ const fromB = customRangeDay(from, "from", void 0, OP11);
13438
+ const toB = customRangeDay(to, "to", void 0, OP11);
13289
13439
  if (fromB.ms > toB.ms) {
13290
- throw new AutotaskConfigError(`${OP10}: from (${isoUtc(fromB.ms)}) must not be after to (${isoUtc(toB.ms)})`, { operation: OP10 });
13440
+ throw new AutotaskConfigError(`${OP11}: from (${isoUtc(fromB.ms)}) must not be after to (${isoUtc(toB.ms)})`, { operation: OP11 });
13291
13441
  }
13292
13442
  const windowToMs = toB.ms + DAY_MS3 - 1;
13293
13443
  const { items: entries, hasMore } = await queryTimeEntries(
@@ -13298,7 +13448,7 @@ async function recentTickets(_resource, from, to, http, opts) {
13298
13448
  { field: "resourceID", op: "eq", value: resourceID }
13299
13449
  ],
13300
13450
  clampWindowLimit(opts.limit),
13301
- OP10,
13451
+ OP11,
13302
13452
  http
13303
13453
  );
13304
13454
  const groups = /* @__PURE__ */ new Map();
@@ -13324,9 +13474,9 @@ async function recentTickets(_resource, from, to, http, opts) {
13324
13474
  idempotent: true,
13325
13475
  path: "/V1.0/Tickets/query",
13326
13476
  body: { MaxRecords: TICKET_CHUNK2, filter, IncludeFields: TICKET_FIELDS3 },
13327
- operation: OP10
13477
+ operation: OP11
13328
13478
  });
13329
- for (const t of unwrapItems(res, OP10).items) if (typeof t.id === "number") tickets.set(t.id, t);
13479
+ for (const t of unwrapItems(res, OP11).items) if (typeof t.id === "number") tickets.set(t.id, t);
13330
13480
  }
13331
13481
  const items = [];
13332
13482
  const unresolvedTicketIDs = [];