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