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.
- package/CHANGELOG.md +15 -0
- package/MCP_TOOL_CATALOG.json +27 -8
- package/MCP_TOOL_MANIFEST.md +14 -13
- package/N8N_MCP_MANIFEST.json +38 -12
- package/N8N_MCP_MANIFEST.md +10 -9
- package/README.md +1 -1
- package/capabilities.json +1448 -1307
- package/dist/capabilities.cjs +2 -2
- package/dist/capabilities.js +1 -1
- package/dist/{chunk-QCB4LYKD.cjs → chunk-3LAZZT7V.cjs} +143 -143
- package/dist/{chunk-IKPFR6K2.cjs → chunk-4QN43TRP.cjs} +73 -48
- package/dist/{chunk-FVHPN7ZR.js → chunk-5DS6F6GF.js} +241 -91
- package/dist/{chunk-T6EDJRLT.js → chunk-7WNE5MPG.js} +2 -2
- package/dist/{chunk-R6HLJCI2.js → chunk-MFDQHFGJ.js} +26 -1
- package/dist/{chunk-FTF2FEQ6.cjs → chunk-OO67MRHN.cjs} +765 -615
- package/dist/{chunk-DCTHO7CQ.cjs → chunk-SP2SJJZX.cjs} +1447 -1306
- package/dist/{chunk-M2EGFPGF.js → chunk-VKE56TTN.js} +1447 -1306
- package/dist/{index-Cw4aNNMo.d.ts → index-BaeMHZPk.d.ts} +87 -1
- package/dist/{index-OkbCWcef.d.cts → index-Cq0VsfSz.d.cts} +87 -1
- package/dist/index.cjs +23 -23
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -5
- package/dist/operations/index.cjs +120 -133
- package/dist/operations/index.d.cts +2 -2
- package/dist/operations/index.d.ts +2 -2
- package/dist/operations/index.js +3 -16
- package/dist/pool.cjs +9 -9
- package/dist/pool.d.cts +1 -1
- package/dist/pool.d.ts +1 -1
- package/dist/pool.js +4 -4
- package/dist/resources/index.cjs +4 -4
- package/dist/resources/index.d.cts +1 -1
- package/dist/resources/index.d.ts +1 -1
- package/dist/resources/index.js +3 -3
- package/docs/API.md +1 -1
- 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-
|
|
44
|
+
} from "./chunk-MFDQHFGJ.js";
|
|
44
45
|
import {
|
|
45
46
|
CAPABILITIES
|
|
46
|
-
} from "./chunk-
|
|
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
|
|
277
|
-
const offset = tzOffsetMs(tz,
|
|
278
|
-
let start =
|
|
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 =
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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(
|
|
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:
|
|
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
|
|
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,
|
|
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,
|
|
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:
|
|
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
|
|
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 }],
|
|
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", `${
|
|
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:
|
|
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
|
|
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:
|
|
11425
|
-
{ name: "resolution", met:
|
|
11426
|
-
{ name: "resolutionPlan", met:
|
|
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
|
|
11594
|
+
const str2 = (v) => typeof v === "string" && v !== "" ? v : null;
|
|
11445
11595
|
return {
|
|
11446
11596
|
ticketId,
|
|
11447
|
-
ticketNumber:
|
|
11448
|
-
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:
|
|
11459
|
-
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
|
|
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(`${
|
|
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:
|
|
12375
|
+
operation: OP6
|
|
12226
12376
|
});
|
|
12227
|
-
const page = unwrapItems(res,
|
|
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(`${
|
|
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(`${
|
|
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(`${
|
|
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(`${
|
|
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(`${
|
|
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:
|
|
12411
|
+
operation: OP6
|
|
12262
12412
|
});
|
|
12263
|
-
const page = unwrapItems(res,
|
|
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
|
|
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(
|
|
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(
|
|
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:
|
|
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
|
|
12666
|
-
if (opts?.tz !== void 0) validateZone(opts.tz,
|
|
12667
|
-
const fromB = customRangeDay(from, "from", void 0,
|
|
12668
|
-
const toB = customRangeDay(to, "to", void 0,
|
|
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(`${
|
|
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),
|
|
12680
|
-
const { states, incomplete } = await postedStateOf(http, items,
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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}`,
|
|
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:
|
|
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:
|
|
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,
|
|
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,
|
|
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:
|
|
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:
|
|
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}`,
|
|
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,
|
|
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
|
|
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:
|
|
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", `${
|
|
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", `${
|
|
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 }],
|
|
13284
|
+
billing = await queryRows(http, "BillingItems", [{ field: "timeEntryID", op: "eq", value: sourceId }], OP9, retries);
|
|
13135
13285
|
} catch {
|
|
13136
|
-
throw refuse("MOVE_BLOCKED_BILLED", `${
|
|
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", `${
|
|
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 }],
|
|
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", `${
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
13393
|
+
operation: OP10,
|
|
13244
13394
|
maxRetries: opts.maxRetries
|
|
13245
13395
|
});
|
|
13246
|
-
const levelId = toNumber(parseIdentifier(unwrapOperation(created,
|
|
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
|
|
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(`${
|
|
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(`${
|
|
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,
|
|
13287
|
-
const fromB = customRangeDay(from, "from", void 0,
|
|
13288
|
-
const toB = customRangeDay(to, "to", void 0,
|
|
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(`${
|
|
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
|
-
|
|
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:
|
|
13477
|
+
operation: OP11
|
|
13328
13478
|
});
|
|
13329
|
-
for (const t of unwrapItems(res,
|
|
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 = [];
|