node-autotask 0.33.0 → 0.37.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 +82 -1
- package/MCP_TOOL_CATALOG.json +26 -9
- package/MCP_TOOL_MANIFEST.md +14 -13
- package/N8N_MCP_MANIFEST.json +5 -5
- package/N8N_MCP_MANIFEST.md +4 -4
- package/README.md +12 -5
- package/capabilities.json +5850 -2557
- package/capabilities.schema.json +36 -0
- package/dist/capabilities.cjs +2 -2
- package/dist/capabilities.d.cts +15 -1
- package/dist/capabilities.d.ts +15 -1
- package/dist/capabilities.js +1 -1
- package/dist/{chunk-7WNE5MPG.js → chunk-2PJCQGXZ.js} +12 -12
- package/dist/{chunk-OO67MRHN.cjs → chunk-4GHBN2AW.cjs} +1737 -6620
- package/dist/{chunk-5DS6F6GF.js → chunk-75WJS26D.js} +3594 -8477
- package/dist/{chunk-3LAZZT7V.cjs → chunk-7WR5WUQT.cjs} +143 -143
- package/dist/{chunk-MFDQHFGJ.js → chunk-CH2WXTYD.js} +9445 -3678
- package/dist/{chunk-4QN43TRP.cjs → chunk-FYF46OT3.cjs} +9470 -3703
- package/dist/{chunk-SP2SJJZX.cjs → chunk-HBYKTXN3.cjs} +5849 -2556
- package/dist/{chunk-VKE56TTN.js → chunk-KQ6HMBB4.js} +5849 -2556
- package/dist/{index-BaeMHZPk.d.ts → index-DAFg_68I.d.ts} +584 -207
- package/dist/{index-Cq0VsfSz.d.cts → index-g8J6Qiu1.d.cts} +584 -207
- package/dist/index.cjs +22 -22
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +14 -14
- package/dist/operations/index.cjs +867 -135
- package/dist/operations/index.d.cts +182 -28
- package/dist/operations/index.d.ts +182 -28
- package/dist/operations/index.js +742 -10
- 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 +5 -4
- package/dist/resources/index.d.cts +1 -1
- package/dist/resources/index.d.ts +1 -1
- package/dist/resources/index.js +14 -13
- package/docs/API.md +481 -46
- package/docs/LIVE-TESTING.md +11 -11
- package/package.json +1 -1
|
@@ -44,29 +44,49 @@
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
|
|
47
|
-
var _chunk4QN43TRPcjs = require('../chunk-4QN43TRP.cjs');
|
|
48
47
|
|
|
49
48
|
|
|
50
|
-
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
var _chunkFYF46OT3cjs = require('../chunk-FYF46OT3.cjs');
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
var _chunkHBYKTXN3cjs = require('../chunk-HBYKTXN3.cjs');
|
|
51
71
|
|
|
52
72
|
// src/operations/searchAcrossResources.ts
|
|
53
73
|
var DEFAULT_LIMIT = 25;
|
|
54
74
|
function resourceFor(client, key) {
|
|
55
|
-
return client[
|
|
75
|
+
return client[_chunkFYF46OT3cjs.camelKey.call(void 0, key)];
|
|
56
76
|
}
|
|
57
77
|
async function searchAcrossResources(client, query, opts = {}) {
|
|
58
78
|
const text = query.trim();
|
|
59
79
|
if (text === "") {
|
|
60
|
-
throw new (0,
|
|
80
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)("searchAcrossResources: query must be a non-empty string");
|
|
61
81
|
}
|
|
62
|
-
const keys = _nullishCoalesce(opts.resources, () => (
|
|
82
|
+
const keys = _nullishCoalesce(opts.resources, () => ( _chunkFYF46OT3cjs.WORKFLOW_RESOURCES));
|
|
63
83
|
if (keys.length === 0) {
|
|
64
|
-
throw new (0,
|
|
84
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)("searchAcrossResources: no resources requested");
|
|
65
85
|
}
|
|
66
|
-
const limit =
|
|
86
|
+
const limit = _chunkFYF46OT3cjs.clampPageSize.call(void 0, _nullishCoalesce(opts.limit, () => ( DEFAULT_LIMIT)));
|
|
67
87
|
const totalLimit = opts.totalLimit;
|
|
68
88
|
if (totalLimit !== void 0 && !(Number.isInteger(totalLimit) && totalLimit >= 1)) {
|
|
69
|
-
throw new (0,
|
|
89
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)("searchAcrossResources: totalLimit must be a positive integer");
|
|
70
90
|
}
|
|
71
91
|
const results = [];
|
|
72
92
|
const skipped = [];
|
|
@@ -97,7 +117,7 @@ async function searchAcrossResources(client, query, opts = {}) {
|
|
|
97
117
|
|
|
98
118
|
// src/operations/resolveAny.ts
|
|
99
119
|
function resourceFor2(client, key) {
|
|
100
|
-
return client[
|
|
120
|
+
return client[_chunkFYF46OT3cjs.camelKey.call(void 0, key)];
|
|
101
121
|
}
|
|
102
122
|
var FAULT_CATEGORIES = /* @__PURE__ */ new Set(["auth", "rate_limit", "server", "network", "timeout", "policy"]);
|
|
103
123
|
function missCost(identifier) {
|
|
@@ -106,13 +126,13 @@ function missCost(identifier) {
|
|
|
106
126
|
}
|
|
107
127
|
async function resolveAny(client, identifier, opts = {}) {
|
|
108
128
|
if (typeof identifier === "number") {
|
|
109
|
-
|
|
129
|
+
_chunkFYF46OT3cjs.parseIdentifier.call(void 0, identifier, "identifier");
|
|
110
130
|
} else if (identifier.trim() === "") {
|
|
111
|
-
throw new (0,
|
|
131
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)("resolveAny: identifier must be non-empty");
|
|
112
132
|
}
|
|
113
|
-
const keys = _nullishCoalesce(opts.resources, () => (
|
|
133
|
+
const keys = _nullishCoalesce(opts.resources, () => ( _chunkFYF46OT3cjs.WORKFLOW_RESOURCES));
|
|
114
134
|
if (keys.length === 0) {
|
|
115
|
-
throw new (0,
|
|
135
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)("resolveAny: no resources requested");
|
|
116
136
|
}
|
|
117
137
|
const results = [];
|
|
118
138
|
for (const key of keys) {
|
|
@@ -142,9 +162,9 @@ async function resolveAny(client, identifier, opts = {}) {
|
|
|
142
162
|
} catch (err) {
|
|
143
163
|
let code;
|
|
144
164
|
let candidates = [];
|
|
145
|
-
if (err instanceof
|
|
165
|
+
if (err instanceof _chunkFYF46OT3cjs.AutotaskConfigError) {
|
|
146
166
|
code = "CONFIG_ERROR";
|
|
147
|
-
} else if (
|
|
167
|
+
} else if (_chunkFYF46OT3cjs.isAutotaskError.call(void 0, err)) {
|
|
148
168
|
if (FAULT_CATEGORIES.has(err.category)) throw err;
|
|
149
169
|
code = err.code;
|
|
150
170
|
if (code === "RESOLUTION_AMBIGUOUS" && err.resourceIds !== void 0) {
|
|
@@ -171,7 +191,7 @@ async function resolveAny(client, identifier, opts = {}) {
|
|
|
171
191
|
// src/operations/aggregate.ts
|
|
172
192
|
var AGGREGATE_TOP_MAX = 100;
|
|
173
193
|
var AGGREGATE_DEFAULT_TOP = 25;
|
|
174
|
-
var AGGREGATE_DEFAULT_LIMIT =
|
|
194
|
+
var AGGREGATE_DEFAULT_LIMIT = _chunkFYF46OT3cjs.BOUNDED_QUERY_MAX_LIMIT;
|
|
175
195
|
var AGGREGATE_DATE_BUCKETS = ["month", "week", "year"];
|
|
176
196
|
var DATE_ONLY_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
177
197
|
var DATETIME_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(\.\d{1,9})?(Z|[+-]\d{2}:?\d{2})?$/;
|
|
@@ -228,12 +248,12 @@ function dateBucketLabel(value, bucket) {
|
|
|
228
248
|
}
|
|
229
249
|
var QUERY_RESOURCE_KEYS = (() => {
|
|
230
250
|
const s = /* @__PURE__ */ new Set();
|
|
231
|
-
for (const rec of
|
|
251
|
+
for (const rec of _chunkHBYKTXN3cjs.CAPABILITIES) if (rec.operation === "query") s.add(rec.resource);
|
|
232
252
|
return s;
|
|
233
253
|
})();
|
|
234
254
|
var AGGREGATE_OPT_KEYS = ["groupBy", "sum", "top", "limit"];
|
|
235
255
|
var OP_RE = /^([a-z0-9_]+)\.query$/;
|
|
236
|
-
function
|
|
256
|
+
function num2(v) {
|
|
237
257
|
return typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
238
258
|
}
|
|
239
259
|
function parseOperationKey(operation, problems) {
|
|
@@ -264,15 +284,15 @@ function parseFilterConditions(filter, problems) {
|
|
|
264
284
|
return [];
|
|
265
285
|
}
|
|
266
286
|
try {
|
|
267
|
-
return
|
|
287
|
+
return _chunkFYF46OT3cjs.buildFilter.call(void 0, conds).filter;
|
|
268
288
|
} catch (err) {
|
|
269
|
-
problems.push(err instanceof
|
|
289
|
+
problems.push(err instanceof _chunkFYF46OT3cjs.AutotaskConfigError ? err.message : `filter must be the query-bag filter (13 operators; caps 500 OR-conditions / 1 UDF / 8000 chars); ${String(err)}`);
|
|
270
290
|
return [];
|
|
271
291
|
}
|
|
272
292
|
}
|
|
273
293
|
function checkOptsBag(opts, problems) {
|
|
274
294
|
if (opts !== void 0 && (typeof opts !== "object" || opts === null || Array.isArray(opts))) {
|
|
275
|
-
throw new (0,
|
|
295
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
276
296
|
`aggregate: opts must be the options object (got ${Array.isArray(opts) ? "an array" : typeof opts})`,
|
|
277
297
|
{ operation: "operations.aggregate" }
|
|
278
298
|
);
|
|
@@ -352,7 +372,7 @@ function parseTopAndLimit(opts, problems) {
|
|
|
352
372
|
if (!Number.isInteger(opts.limit)) {
|
|
353
373
|
problems.push(`limit must be an integer (got ${String(opts.limit)})`);
|
|
354
374
|
} else {
|
|
355
|
-
limit = Math.min(Math.max(opts.limit, 1),
|
|
375
|
+
limit = Math.min(Math.max(opts.limit, 1), _chunkFYF46OT3cjs.BOUNDED_QUERY_MAX_LIMIT);
|
|
356
376
|
}
|
|
357
377
|
}
|
|
358
378
|
return { top, limit };
|
|
@@ -364,14 +384,14 @@ function validateAggregate(operation, filter, opts) {
|
|
|
364
384
|
checkOptsBag(opts, problems);
|
|
365
385
|
const oracles = {
|
|
366
386
|
resourceKey,
|
|
367
|
-
fieldSets: resourceKey !== void 0 ?
|
|
368
|
-
dateSets: resourceKey !== void 0 ?
|
|
387
|
+
fieldSets: resourceKey !== void 0 ? _chunkFYF46OT3cjs.MODEL_FIELD_SETS[resourceKey] : void 0,
|
|
388
|
+
dateSets: resourceKey !== void 0 ? _chunkFYF46OT3cjs.MODEL_DATE_FIELDS[resourceKey] : void 0
|
|
369
389
|
};
|
|
370
390
|
const groupBy = parseGroupBy(opts, oracles, problems);
|
|
371
391
|
const sum = checkSumFields("sum", opts.sum, oracles, problems);
|
|
372
392
|
const { top, limit } = parseTopAndLimit(opts, problems);
|
|
373
393
|
if (problems.length > 0) {
|
|
374
|
-
throw new (0,
|
|
394
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
375
395
|
`aggregate: ${problems.length} problem(s) found pre-request (zero wire was issued)
|
|
376
396
|
- ` + problems.join("\n- "),
|
|
377
397
|
{ operation: "operations.aggregate" }
|
|
@@ -398,16 +418,16 @@ function foldGroups(items, v) {
|
|
|
398
418
|
}
|
|
399
419
|
g.count += 1;
|
|
400
420
|
if (g.sums !== void 0) {
|
|
401
|
-
for (const f of v.sum) g.sums[f] = (_nullishCoalesce(g.sums[f], () => ( 0))) +
|
|
421
|
+
for (const f of v.sum) g.sums[f] = (_nullishCoalesce(g.sums[f], () => ( 0))) + num2(item[f]);
|
|
402
422
|
}
|
|
403
423
|
}
|
|
404
424
|
return [...groups.values()].sort((a, b) => b.count - a.count || (a.keyOrder < b.keyOrder ? -1 : a.keyOrder > b.keyOrder ? 1 : 0));
|
|
405
425
|
}
|
|
406
426
|
async function aggregate(client, operation, filter, opts = {}) {
|
|
407
427
|
const v = validateAggregate(operation, filter, opts);
|
|
408
|
-
const holder = client[
|
|
428
|
+
const holder = client[_chunkFYF46OT3cjs.camelKey.call(void 0, v.resourceKey)];
|
|
409
429
|
if (holder === void 0) {
|
|
410
|
-
throw new (0,
|
|
430
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(`aggregate: the client exposes no accessor for resource "${v.resourceKey}"`, {
|
|
411
431
|
operation: "operations.aggregate",
|
|
412
432
|
suggestedAction: "Rebuild the client from the SDK build that ships this registry (the resource set is pinned by the plan)."
|
|
413
433
|
});
|
|
@@ -437,24 +457,692 @@ async function aggregate(client, operation, filter, opts = {}) {
|
|
|
437
457
|
return result;
|
|
438
458
|
}
|
|
439
459
|
|
|
460
|
+
// src/operations/revenueRollup.ts
|
|
461
|
+
var OP = "operations.revenueRollup";
|
|
462
|
+
var PAGE = 500;
|
|
463
|
+
var CONCURRENCY = 3;
|
|
464
|
+
var DEFAULT_BUDGET = 90;
|
|
465
|
+
var DEFAULT_BUDGET_NON_CHARGEABLE = 150;
|
|
466
|
+
var BUDGET_CAP = 200;
|
|
467
|
+
var TOP_MAX = 100;
|
|
468
|
+
var SCOPE_IDS_MAX = 100;
|
|
469
|
+
var TENANT_HOURLY_LIMIT = 1e4;
|
|
470
|
+
var DEFAULT_GROUP_BY = ["vertical", "contractType"];
|
|
471
|
+
var DEFAULT_CONTRACT_TYPE_RE = /^\s*(recurring|fixed)\b/i;
|
|
472
|
+
var CONTRACTS_PATH = "/V1.0/Contracts";
|
|
473
|
+
var COMPANIES_PATH = "/V1.0/Companies";
|
|
474
|
+
var BILLING_ITEMS_PATH = "/V1.0/BillingItems";
|
|
475
|
+
var LINE_TYPE_FIELDS = ["billingItemType", "timeEntryID", "expenseItemID", ..._chunkFYF46OT3cjs.CHARGE_LINE_FKS, ..._chunkFYF46OT3cjs.SERVICE_LINE_FKS];
|
|
476
|
+
var isLabour = (line) => _chunkFYF46OT3cjs.billingLineType.call(void 0, line) === "labour";
|
|
477
|
+
function picklistKey(c, dim, path, field, raw, missing) {
|
|
478
|
+
const id = _chunkFYF46OT3cjs.num.call(void 0, raw);
|
|
479
|
+
if (id === 0) return { id: null, label: missing };
|
|
480
|
+
const label = _optionalChain([c, 'access', _2 => _2.labels, 'access', _3 => _3[path], 'optionalAccess', _4 => _4.get, 'call', _5 => _5(field), 'optionalAccess', _6 => _6.get, 'call', _7 => _7(id)]);
|
|
481
|
+
if (label === void 0) {
|
|
482
|
+
c.unlabelled.add(`${dim}: ${field} value ${id} has no label on the live ${path} picklist (id-only)`);
|
|
483
|
+
return { id };
|
|
484
|
+
}
|
|
485
|
+
return { id, label };
|
|
486
|
+
}
|
|
487
|
+
var DIMENSIONS = {
|
|
488
|
+
vertical: {
|
|
489
|
+
joins: { companies: ["marketSegmentID"] },
|
|
490
|
+
picklist: COMPANIES_PATH,
|
|
491
|
+
key: (c) => picklistKey(c, "vertical", COMPANIES_PATH, "marketSegmentID", _optionalChain([c, 'access', _8 => _8.company, 'optionalAccess', _9 => _9["marketSegmentID"]]), "unassigned")
|
|
492
|
+
},
|
|
493
|
+
contractType: {
|
|
494
|
+
key: (c) => picklistKey(c, "contractType", CONTRACTS_PATH, "contractType", c.contract["contractType"], "none")
|
|
495
|
+
},
|
|
496
|
+
contractCategory: {
|
|
497
|
+
key: (c) => picklistKey(c, "contractCategory", CONTRACTS_PATH, "contractCategory", c.contract["contractCategory"], "uncategorised")
|
|
498
|
+
},
|
|
499
|
+
company: {
|
|
500
|
+
joins: { companies: ["companyName"] },
|
|
501
|
+
key: (c) => ({ id: _chunkFYF46OT3cjs.num.call(void 0, c.line["companyID"]), name: String(_nullishCoalesce(_optionalChain([c, 'access', _10 => _10.company, 'optionalAccess', _11 => _11["companyName"]]), () => ( ""))) })
|
|
502
|
+
},
|
|
503
|
+
month: {
|
|
504
|
+
key: (c) => ({ id: c.invoice.month })
|
|
505
|
+
},
|
|
506
|
+
billingItemKind: {
|
|
507
|
+
lineFields: LINE_TYPE_FIELDS,
|
|
508
|
+
picklist: BILLING_ITEMS_PATH,
|
|
509
|
+
key: (c) => _chunkFYF46OT3cjs.num.call(void 0, c.line["billingItemType"]) === 0 ? { id: _chunkFYF46OT3cjs.billingLineType.call(void 0, c.line), label: _chunkFYF46OT3cjs.billingLineType.call(void 0, c.line) } : picklistKey(c, "billingItemKind", BILLING_ITEMS_PATH, "billingItemType", c.line["billingItemType"], "other")
|
|
510
|
+
},
|
|
511
|
+
contract: {
|
|
512
|
+
joins: { contracts: ["contractName"] },
|
|
513
|
+
key: (c) => ({ id: _chunkFYF46OT3cjs.num.call(void 0, c.line["contractID"]), name: String(_nullishCoalesce(c.contract["contractName"], () => ( ""))) })
|
|
514
|
+
},
|
|
515
|
+
// Per line (no contract-pointer fallback): association → level2 (the Line of Business, `name`) + level1 (the division).
|
|
516
|
+
lineOfBusiness: {
|
|
517
|
+
lineFields: ["organizationalLevelAssociationID"],
|
|
518
|
+
joins: { organizationalLevelAssociations: [], organizationalLevel1s: [], organizationalLevel2s: [] },
|
|
519
|
+
unresolved: "unresolvedAssociations",
|
|
520
|
+
key: (c) => {
|
|
521
|
+
const id = _chunkFYF46OT3cjs.num.call(void 0, c.line["organizationalLevelAssociationID"]);
|
|
522
|
+
if (id === 0) return { id: null, label: "no LOB assigned" };
|
|
523
|
+
const a = c.joined.organizationalLevelAssociations.get(id);
|
|
524
|
+
const l1 = c.joined.organizationalLevel1s.get(_chunkFYF46OT3cjs.num.call(void 0, _optionalChain([a, 'optionalAccess', _12 => _12["organizationalLevel1ID"]])));
|
|
525
|
+
const l2 = c.joined.organizationalLevel2s.get(_chunkFYF46OT3cjs.num.call(void 0, _optionalChain([a, 'optionalAccess', _13 => _13["organizationalLevel2ID"]])));
|
|
526
|
+
if (l1 === void 0 || l2 === void 0) return "unresolvedAssociations";
|
|
527
|
+
return { id, name: String(_nullishCoalesce(l2["name"], () => ( ""))), division: String(_nullishCoalesce(l1["name"], () => ( ""))) };
|
|
528
|
+
}
|
|
529
|
+
},
|
|
530
|
+
// Labour lines only; a non-labour line (or a labour line with no time entry, e.g. an adjustment) is "n/a".
|
|
531
|
+
resource: {
|
|
532
|
+
lineFields: LINE_TYPE_FIELDS,
|
|
533
|
+
joins: { timeEntries: [], resources: [] },
|
|
534
|
+
unresolved: "unresolvedTimeEntries",
|
|
535
|
+
key: (c) => {
|
|
536
|
+
const te = _chunkFYF46OT3cjs.num.call(void 0, c.line["timeEntryID"]);
|
|
537
|
+
if (!isLabour(c.line) || te === 0) return { id: null, label: "n/a" };
|
|
538
|
+
const id = _chunkFYF46OT3cjs.num.call(void 0, _optionalChain([c, 'access', _14 => _14.joined, 'access', _15 => _15.timeEntries, 'access', _16 => _16.get, 'call', _17 => _17(te), 'optionalAccess', _18 => _18["resourceID"]]));
|
|
539
|
+
const r = c.joined.resources.get(id);
|
|
540
|
+
if (r === void 0) return "unresolvedTimeEntries";
|
|
541
|
+
return { id, name: `${String(_nullishCoalesce(r["firstName"], () => ( "")))} ${String(_nullishCoalesce(r["lastName"], () => ( "")))}`.trim() };
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
billingCode: {
|
|
545
|
+
lineFields: ["billingCodeID"],
|
|
546
|
+
joins: { billingCodes: [] },
|
|
547
|
+
unresolved: "unresolvedBillingCodes",
|
|
548
|
+
key: (c) => {
|
|
549
|
+
const id = _chunkFYF46OT3cjs.num.call(void 0, c.line["billingCodeID"]);
|
|
550
|
+
if (id === 0) return { id: null, label: "none" };
|
|
551
|
+
const b = c.joined.billingCodes.get(id);
|
|
552
|
+
return b === void 0 ? "unresolvedBillingCodes" : { id, name: String(_nullishCoalesce(b["name"], () => ( ""))) };
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
var fk = (rows, field) => [...rows].map((r) => _chunkFYF46OT3cjs.num.call(void 0, r[field]));
|
|
557
|
+
var ONE_CHUNK = (idChunks) => Math.min(1, idChunks);
|
|
558
|
+
var JOINS = {
|
|
559
|
+
contracts: {
|
|
560
|
+
make: (http) => new (0, _chunkFYF46OT3cjs.ContractsResource)(http, PAGE),
|
|
561
|
+
fields: ["id", "contractType", "contractCategory"],
|
|
562
|
+
ids: (lines) => fk(lines, "contractID")
|
|
563
|
+
},
|
|
564
|
+
companies: {
|
|
565
|
+
make: (http) => new (0, _chunkFYF46OT3cjs.CompaniesResource)(http, PAGE),
|
|
566
|
+
fields: ["id"],
|
|
567
|
+
ids: (lines) => fk(lines, "companyID")
|
|
568
|
+
},
|
|
569
|
+
organizationalLevelAssociations: {
|
|
570
|
+
make: (http) => new (0, _chunkFYF46OT3cjs.OrganizationalLevelAssociationsResource)(http, PAGE),
|
|
571
|
+
fields: ["id", "organizationalLevel1ID", "organizationalLevel2ID"],
|
|
572
|
+
ids: (lines) => fk(lines, "organizationalLevelAssociationID")
|
|
573
|
+
},
|
|
574
|
+
organizationalLevel1s: {
|
|
575
|
+
make: (http) => new (0, _chunkFYF46OT3cjs.OrganizationalLevel1sResource)(http, PAGE),
|
|
576
|
+
fields: ["id", "name"],
|
|
577
|
+
ids: (_, j) => fk(j["organizationalLevelAssociations"].values(), "organizationalLevel1ID"),
|
|
578
|
+
chunks: ONE_CHUNK
|
|
579
|
+
},
|
|
580
|
+
organizationalLevel2s: {
|
|
581
|
+
make: (http) => new (0, _chunkFYF46OT3cjs.OrganizationalLevel2sResource)(http, PAGE),
|
|
582
|
+
fields: ["id", "name"],
|
|
583
|
+
ids: (_, j) => fk(j["organizationalLevelAssociations"].values(), "organizationalLevel2ID"),
|
|
584
|
+
chunks: ONE_CHUNK
|
|
585
|
+
},
|
|
586
|
+
timeEntries: {
|
|
587
|
+
make: (http) => new (0, _chunkFYF46OT3cjs.TimeEntriesResource)(http, PAGE),
|
|
588
|
+
fields: ["id", "resourceID"],
|
|
589
|
+
ids: (lines) => fk(lines.filter(isLabour), "timeEntryID"),
|
|
590
|
+
// One time entry per labour line: the line count is the upper bound (labour share unknown pre-walk).
|
|
591
|
+
chunks: (_, lines) => pages(lines)
|
|
592
|
+
},
|
|
593
|
+
resources: {
|
|
594
|
+
make: (http, client) => new (0, _chunkFYF46OT3cjs.ResourcesResource)(http, client.config, PAGE),
|
|
595
|
+
fields: ["id", "firstName", "lastName"],
|
|
596
|
+
ids: (_, j) => fk(j["timeEntries"].values(), "resourceID"),
|
|
597
|
+
chunks: ONE_CHUNK
|
|
598
|
+
},
|
|
599
|
+
billingCodes: {
|
|
600
|
+
make: (http) => new (0, _chunkFYF46OT3cjs.BillingCodesResource)(http, PAGE),
|
|
601
|
+
fields: ["id", "name"],
|
|
602
|
+
ids: (lines) => fk(lines, "billingCodeID")
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
var LINE_FIELDS = ["id", "invoiceID", "companyID", "contractID", "totalAmount", "internalCurrencyTotalAmount", "extendedPrice", "internalCurrencyExtendedPrice"];
|
|
606
|
+
var OPT_KEYS = ["companyIDs", "ownerResourceID", "contractIDs", "tenantWide", "dateFrom", "dateTo", "contractTypes", "contractCategories", "groupBy", "top", "maxVendorCalls", "includeNonChargeable"];
|
|
607
|
+
var NC_LINE_FIELDS = ["nonBillable", "quantity", ...LINE_TYPE_FIELDS];
|
|
608
|
+
var SCOPE_KEYS = ["companyIDs", "ownerResourceID", "contractIDs", "tenantWide"];
|
|
609
|
+
var isPosInt = (v) => Number.isSafeInteger(v) && v > 0;
|
|
610
|
+
function checkIdList(name, v, problems) {
|
|
611
|
+
if (!Array.isArray(v) || v.length === 0 || v.length > SCOPE_IDS_MAX || !v.every(isPosInt)) {
|
|
612
|
+
problems.push(`${name} must be a non-empty array of at most ${SCOPE_IDS_MAX} positive integer ids`);
|
|
613
|
+
return void 0;
|
|
614
|
+
}
|
|
615
|
+
return [...new Set(v)].sort((a, b) => a - b);
|
|
616
|
+
}
|
|
617
|
+
function checkSelector(name, v, problems) {
|
|
618
|
+
if (v === void 0) return void 0;
|
|
619
|
+
if (!Array.isArray(v) || v.length === 0 || !v.every((x) => isPosInt(x) || typeof x === "string" && x.trim() !== "")) {
|
|
620
|
+
problems.push(`${name} must be a non-empty array of picklist ids or labels`);
|
|
621
|
+
return void 0;
|
|
622
|
+
}
|
|
623
|
+
return v;
|
|
624
|
+
}
|
|
625
|
+
function checkInt(name, v, def, max, problems) {
|
|
626
|
+
if (v === void 0) return def;
|
|
627
|
+
if (!Number.isInteger(v) || v < 1 || v > max) {
|
|
628
|
+
problems.push(`${name} must be an integer 1..${max} (got ${String(v)})`);
|
|
629
|
+
return def;
|
|
630
|
+
}
|
|
631
|
+
return v;
|
|
632
|
+
}
|
|
633
|
+
function validate(opts) {
|
|
634
|
+
if (typeof opts !== "object" || opts === null || Array.isArray(opts)) {
|
|
635
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)("revenueRollup: opts must be the options object { <scope>, dateFrom, dateTo, ... } (zero wire was issued)", { operation: OP });
|
|
636
|
+
}
|
|
637
|
+
const o = opts;
|
|
638
|
+
const problems = [];
|
|
639
|
+
for (const k of Object.keys(o)) if (!OPT_KEYS.includes(k)) problems.push(`unknown opts key "${k}" \u2014 the bag is closed: ${OPT_KEYS.join(", ")}`);
|
|
640
|
+
const given = SCOPE_KEYS.filter((k) => o[k] !== void 0);
|
|
641
|
+
if (given.length !== 1) {
|
|
642
|
+
problems.push(given.length === 0 ? "a scope is required: exactly one of companyIDs, ownerResourceID, contractIDs, or tenantWide: true" : `exactly one scope key is allowed (got ${given.join(", ")})`);
|
|
643
|
+
}
|
|
644
|
+
const scopeKind = _nullishCoalesce(given[0], () => ( "tenantWide"));
|
|
645
|
+
let scopeIds;
|
|
646
|
+
if (given.length === 1) {
|
|
647
|
+
if (scopeKind === "companyIDs" || scopeKind === "contractIDs") scopeIds = checkIdList(scopeKind, o[scopeKind], problems);
|
|
648
|
+
if (scopeKind === "ownerResourceID" && !isPosInt(o.ownerResourceID)) problems.push("ownerResourceID must be a positive integer resource id");
|
|
649
|
+
if (scopeKind === "tenantWide" && o.tenantWide !== true) problems.push("tenantWide must be exactly true");
|
|
650
|
+
}
|
|
651
|
+
const { dateFrom, dateTo } = o;
|
|
652
|
+
if (!_chunkFYF46OT3cjs.isDateYMD.call(void 0, dateFrom) || !_chunkFYF46OT3cjs.isDateYMD.call(void 0, dateTo)) {
|
|
653
|
+
problems.push("dateFrom and dateTo are both required as YYYY-MM-DD (the window is inclusive, whole UTC days)");
|
|
654
|
+
} else if (dateFrom > dateTo) {
|
|
655
|
+
problems.push(`dateFrom ${dateFrom} is after dateTo ${dateTo}`);
|
|
656
|
+
} else if (_chunkFYF46OT3cjs.monthsInWindow.call(void 0, dateFrom, dateTo) > _chunkFYF46OT3cjs.PROFITABILITY_MAX_WINDOW_MONTHS) {
|
|
657
|
+
problems.push(`the window spans ${_chunkFYF46OT3cjs.monthsInWindow.call(void 0, dateFrom, dateTo)} months; the cap is ${_chunkFYF46OT3cjs.PROFITABILITY_MAX_WINDOW_MONTHS}`);
|
|
658
|
+
}
|
|
659
|
+
let groupBy = [...DEFAULT_GROUP_BY];
|
|
660
|
+
if (o.groupBy !== void 0) {
|
|
661
|
+
const g = o.groupBy;
|
|
662
|
+
if (!Array.isArray(g) || g.length === 0 || new Set(g).size !== g.length) {
|
|
663
|
+
problems.push(`groupBy must be a non-empty array of distinct dimensions: ${Object.keys(DIMENSIONS).join(", ")}`);
|
|
664
|
+
} else {
|
|
665
|
+
for (const d of g) if (!Object.hasOwn(DIMENSIONS, d)) problems.push(`unknown groupBy dimension "${String(d)}" \u2014 the dimensions are: ${Object.keys(DIMENSIONS).join(", ")}`);
|
|
666
|
+
groupBy = g;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
const v = {
|
|
670
|
+
dateFrom,
|
|
671
|
+
dateTo,
|
|
672
|
+
scopeKind,
|
|
673
|
+
scopeIds,
|
|
674
|
+
ownerResourceID: scopeKind === "ownerResourceID" ? o.ownerResourceID : void 0,
|
|
675
|
+
contractTypes: checkSelector("contractTypes", o.contractTypes, problems),
|
|
676
|
+
contractCategories: checkSelector("contractCategories", o.contractCategories, problems),
|
|
677
|
+
groupBy,
|
|
678
|
+
top: checkInt("top", o.top, TOP_MAX, TOP_MAX, problems),
|
|
679
|
+
budget: checkInt("maxVendorCalls", o.maxVendorCalls, o.includeNonChargeable === true ? DEFAULT_BUDGET_NON_CHARGEABLE : DEFAULT_BUDGET, BUDGET_CAP, problems),
|
|
680
|
+
nonChargeable: o.includeNonChargeable === true
|
|
681
|
+
};
|
|
682
|
+
if (o.includeNonChargeable !== void 0 && typeof o.includeNonChargeable !== "boolean") problems.push("includeNonChargeable must be a boolean");
|
|
683
|
+
if (problems.length > 0) {
|
|
684
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(`revenueRollup: ${problems.length} problem(s) found pre-request (zero wire was issued)
|
|
685
|
+
- ${problems.join("\n- ")}`, { operation: OP });
|
|
686
|
+
}
|
|
687
|
+
return v;
|
|
688
|
+
}
|
|
689
|
+
function resolveSelector(name, sel, values) {
|
|
690
|
+
const out = /* @__PURE__ */ new Map();
|
|
691
|
+
const misses = [];
|
|
692
|
+
for (const s of sel) {
|
|
693
|
+
const hits = typeof s === "number" ? values.has(s) ? [s] : [] : [...values].filter(([, l]) => l.trim().toLowerCase() === s.trim().toLowerCase()).map(([id]) => id);
|
|
694
|
+
if (hits.length === 0) misses.push(JSON.stringify(s));
|
|
695
|
+
for (const h of hits) out.set(h, values.get(h));
|
|
696
|
+
}
|
|
697
|
+
if (misses.length > 0) throw unmatched(name, `${misses.join(", ")} matched no live value`, values);
|
|
698
|
+
return out;
|
|
699
|
+
}
|
|
700
|
+
function unmatched(name, what, values) {
|
|
701
|
+
const live = [...values].map(([id, l]) => `${id} ${l}`).join("; ") || "(the picklist serves no values)";
|
|
702
|
+
return new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(`revenueRollup: ${name}: ${what}. Live values (id label): ${live}`, {
|
|
703
|
+
operation: OP,
|
|
704
|
+
suggestedAction: `Pass ${name} as ids or exact labels from the live list.`
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
var sortedRecord = (m) => Object.fromEntries([...m].sort((a, b) => a[0] - b[0]).map(([id, l]) => [String(id), l]));
|
|
708
|
+
var chunk = (a) => {
|
|
709
|
+
const out = [];
|
|
710
|
+
for (let i = 0; i < a.length; i += PAGE) out.push(a.slice(i, i + PAGE));
|
|
711
|
+
return out;
|
|
712
|
+
};
|
|
713
|
+
var byId = (rows) => [...new Map(rows.map((r) => [_chunkFYF46OT3cjs.num.call(void 0, r["id"]), r])).values()];
|
|
714
|
+
var cents = (v) => Math.round(_chunkFYF46OT3cjs.num.call(void 0, v) * 100);
|
|
715
|
+
var revC = (line) => cents(_chunkFYF46OT3cjs.internalAmount.call(void 0, line, "internalCurrencyTotalAmount", "totalAmount"));
|
|
716
|
+
var extC = (line) => cents(_chunkFYF46OT3cjs.internalAmount.call(void 0, line, "internalCurrencyExtendedPrice", "extendedPrice"));
|
|
717
|
+
var INTERNAL_FIELDS = ["internalCurrencyTotalAmount", "internalCurrencyExtendedPrice"];
|
|
718
|
+
var lacksInternal = (line) => INTERNAL_FIELDS.some((k) => !Number.isFinite(line[k]));
|
|
719
|
+
var money = (c) => c / 100;
|
|
720
|
+
var pages = (n) => Math.ceil(n / PAGE);
|
|
721
|
+
async function collect(res, filter, includeFields) {
|
|
722
|
+
const out = [];
|
|
723
|
+
for await (const r of res.query({ filter, includeFields, maxRecords: PAGE })) out.push(r);
|
|
724
|
+
return out;
|
|
725
|
+
}
|
|
726
|
+
function blindCount() {
|
|
727
|
+
return new (0, _chunkFYF46OT3cjs.AutotaskError)("revenueRollup: a gate count came back blind (unknown, not zero); the roll-up cannot be sized", {
|
|
728
|
+
code: "INVALID_RESPONSE",
|
|
729
|
+
category: "server",
|
|
730
|
+
operation: OP,
|
|
731
|
+
details: { reason: "blind_count" },
|
|
732
|
+
suggestedAction: "Check the API user can see invoices and billing items (operations.blindSurfaces), then retry."
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
function countMismatch(what, streamed, counted, rows) {
|
|
736
|
+
const rowNote = rows !== void 0 && rows !== streamed ? ` (${rows} rows)` : "";
|
|
737
|
+
return new (0, _chunkFYF46OT3cjs.AutotaskError)(`revenueRollup: the ${what} stream returned ${streamed} distinct ids${rowNote} but the gate count was ${counted} (the data changed mid-walk or a page dropped rows); the fold was discarded`, {
|
|
738
|
+
code: "INVALID_RESPONSE",
|
|
739
|
+
category: "server",
|
|
740
|
+
operation: OP,
|
|
741
|
+
details: { reason: "count_mismatch", stream: what, streamed, counted },
|
|
742
|
+
suggestedAction: "Retry the call."
|
|
743
|
+
});
|
|
744
|
+
}
|
|
745
|
+
function budgetError(message, details, suggestedAction) {
|
|
746
|
+
return new (0, _chunkFYF46OT3cjs.AutotaskError)(message, { code: "BUDGET_EXCEEDED", category: "policy", operation: OP, details, suggestedAction });
|
|
747
|
+
}
|
|
748
|
+
function lastMonthsFrom(dateTo, months) {
|
|
749
|
+
return new Date(Date.UTC(Number(dateTo.slice(0, 4)), Number(dateTo.slice(5, 7)) - months, 1)).toISOString().slice(0, 10);
|
|
750
|
+
}
|
|
751
|
+
async function revenueRollup(client, opts) {
|
|
752
|
+
const v = validate(opts);
|
|
753
|
+
const raw = client.http;
|
|
754
|
+
let calls = 0;
|
|
755
|
+
let estimate = 0;
|
|
756
|
+
const sizingMin = [.../* @__PURE__ */ new Set([CONTRACTS_PATH, ...v.groupBy.flatMap((d) => DIMENSIONS[d].picklist ? [DIMENSIONS[d].picklist] : [])])].length + (v.scopeKind === "ownerResourceID" ? 1 : 0) + 2 + (v.nonChargeable ? 1 : 0);
|
|
757
|
+
const http = Object.create(raw);
|
|
758
|
+
http.request = (o) => {
|
|
759
|
+
if (calls >= v.budget && estimate === 0) {
|
|
760
|
+
throw budgetError(
|
|
761
|
+
`revenueRollup: the vendor-call budget of ${v.budget} is below the sizing overhead (labels, scope and count calls: at least ${sizingMin} for this call); the roll-up could not be sized and no stream was read. Retry with a higher maxVendorCalls (cap ${BUDGET_CAP}); the refusal then names the full estimate.`,
|
|
762
|
+
{ phase: "estimate", estimate: sizingMin, budget: v.budget, vendorCalls: calls, suggestions: [{ maxVendorCalls: sizingMin }] },
|
|
763
|
+
`Pass maxVendorCalls >= ${sizingMin} (cap ${BUDGET_CAP}).`
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
if (calls >= v.budget) {
|
|
767
|
+
throw budgetError(
|
|
768
|
+
`revenueRollup: the vendor-call budget of ${v.budget} was exhausted mid-walk (pre-walk estimate ${estimate}); the partial fold was discarded. Retry with a higher maxVendorCalls (cap ${BUDGET_CAP}) or a shorter window / a scope.`,
|
|
769
|
+
{ phase: "walk", budget: v.budget, vendorCalls: calls, estimate },
|
|
770
|
+
`Pass maxVendorCalls > ${v.budget} (cap ${BUDGET_CAP}), a later dateFrom, or a scope.`
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
calls += 1;
|
|
774
|
+
return raw.request(o);
|
|
775
|
+
};
|
|
776
|
+
const groupDims = v.groupBy.map((d) => DIMENSIONS[d]);
|
|
777
|
+
const paths = [.../* @__PURE__ */ new Set([CONTRACTS_PATH, ...groupDims.flatMap((d) => d.picklist ? [d.picklist] : [])])];
|
|
778
|
+
const labels = {};
|
|
779
|
+
for (const p of paths) labels[p] = await _chunkFYF46OT3cjs.readPicklists.call(void 0, http, p, OP);
|
|
780
|
+
const contractLabels = labels[CONTRACTS_PATH];
|
|
781
|
+
const typeValues = _nullishCoalesce(contractLabels.get("contractType"), () => ( /* @__PURE__ */ new Map()));
|
|
782
|
+
const inTypes = v.contractTypes ? resolveSelector("contractTypes", v.contractTypes, typeValues) : new Map([...typeValues].filter(([, l]) => DEFAULT_CONTRACT_TYPE_RE.test(l)));
|
|
783
|
+
if (inTypes.size === 0) throw unmatched("contractTypes", "no live contract type label starts with 'Recurring' or 'Fixed' (the default)", typeValues);
|
|
784
|
+
const inCategories = v.contractCategories ? resolveSelector("contractCategories", v.contractCategories, _nullishCoalesce(contractLabels.get("contractCategory"), () => ( /* @__PURE__ */ new Map()))) : void 0;
|
|
785
|
+
let scopeIds = v.scopeIds;
|
|
786
|
+
if (v.scopeKind === "ownerResourceID") {
|
|
787
|
+
const owned = /* @__PURE__ */ new Set();
|
|
788
|
+
for await (const c of new (0, _chunkFYF46OT3cjs.CompaniesResource)(http, PAGE).query({ filter: [{ field: "ownerResourceID", op: "eq", value: v.ownerResourceID }], includeFields: ["id"], maxRecords: PAGE })) {
|
|
789
|
+
const id = _chunkFYF46OT3cjs.num.call(void 0, c.id);
|
|
790
|
+
if (id > 0) owned.add(id);
|
|
791
|
+
if (owned.size > SCOPE_IDS_MAX) break;
|
|
792
|
+
}
|
|
793
|
+
scopeIds = [...owned].sort((a, b) => a - b);
|
|
794
|
+
if (scopeIds.length === 0 || scopeIds.length > SCOPE_IDS_MAX) {
|
|
795
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
796
|
+
`revenueRollup: ownerResourceID ${v.ownerResourceID} resolves to ${scopeIds.length > SCOPE_IDS_MAX ? `more than ${SCOPE_IDS_MAX}` : 0} companies (companies.query; 1..${SCOPE_IDS_MAX} supported)`,
|
|
797
|
+
{ operation: OP, suggestedAction: "Pass companyIDs explicitly, or tenantWide: true." }
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
const scopeCond = scopeIds === void 0 ? [] : [{ field: v.scopeKind === "contractIDs" ? "contractID" : "companyID", op: "in", value: scopeIds }];
|
|
802
|
+
const invoices = new (0, _chunkFYF46OT3cjs.InvoicesResource)(http, PAGE);
|
|
803
|
+
const billingItems = new (0, _chunkFYF46OT3cjs.BillingItemsResource)(http, PAGE);
|
|
804
|
+
const windowOn = (field) => [{ field, op: "gte", value: v.dateFrom }, { field, op: "lt", value: _chunkFYF46OT3cjs.dayAfter.call(void 0, v.dateTo) }];
|
|
805
|
+
const invoiceFilter = (voided2) => [...windowOn("invoiceDateTime"), { field: "isVoided", op: "eq", value: voided2 }];
|
|
806
|
+
const withVoided = v.scopeKind !== "contractIDs";
|
|
807
|
+
const voidedFilter = [...invoiceFilter(true), ...scopeIds !== void 0 && withVoided ? [{ field: "companyID", op: "in", value: scopeIds }] : []];
|
|
808
|
+
const nInvoices = await invoices.count({ filter: invoiceFilter(false) });
|
|
809
|
+
const nLines = await billingItems.count({ filter: [...windowOn("itemDate"), { field: "invoiceID", op: "exist" }, ...scopeCond] });
|
|
810
|
+
const uninvoicedFilter = [{ field: "invoiceID", op: "notExist" }, ...windowOn("itemDate"), ...scopeCond];
|
|
811
|
+
const nUninvoiced = v.nonChargeable ? await billingItems.count({ filter: uninvoicedFilter }) : 0;
|
|
812
|
+
if (typeof nInvoices !== "number" || typeof nLines !== "number" || typeof nUninvoiced !== "number") throw blindCount();
|
|
813
|
+
const joinsNeeded = Object.keys(JOINS).filter((j) => j === "contracts" || groupDims.some((d) => _optionalChain([d, 'access', _19 => _19.joins, 'optionalAccess', _20 => _20[j]])));
|
|
814
|
+
const chunks = pages(nInvoices);
|
|
815
|
+
const linePages = chunks + Math.max(0, pages(nLines) - 1) + chunks;
|
|
816
|
+
const uninvoicedPages = v.nonChargeable ? Math.max(1, pages(nUninvoiced)) : 0;
|
|
817
|
+
const idChunks = pages(Math.min(nLines + nUninvoiced, nInvoices));
|
|
818
|
+
const joinChunks = joinsNeeded.reduce((s, j) => {
|
|
819
|
+
const spec = JOINS[j];
|
|
820
|
+
return s + ("chunks" in spec ? spec.chunks(idChunks, nLines + nUninvoiced) : idChunks);
|
|
821
|
+
}, 0);
|
|
822
|
+
const fixed = calls + (withVoided ? 1 : 0);
|
|
823
|
+
estimate = fixed + Math.max(1, chunks) + linePages + uninvoicedPages + joinChunks;
|
|
824
|
+
if (estimate > v.budget) throw refusal(v, estimate, fixed, scopeIds, joinsNeeded.length);
|
|
825
|
+
const invoiceRows = await collect(invoices, invoiceFilter(false), ["id", "invoiceTotal", "invoiceDateTime", "companyID"]);
|
|
826
|
+
const invoiceById = /* @__PURE__ */ new Map();
|
|
827
|
+
for (const r of invoiceRows) {
|
|
828
|
+
invoiceById.set(_chunkFYF46OT3cjs.num.call(void 0, r["id"]), { total: cents(r["invoiceTotal"]), month: String(_nullishCoalesce(r["invoiceDateTime"], () => ( ""))).slice(0, 7), companyID: _chunkFYF46OT3cjs.num.call(void 0, r["companyID"]) });
|
|
829
|
+
}
|
|
830
|
+
if (invoiceById.size !== nInvoices) throw countMismatch("invoice", invoiceById.size, nInvoices, invoiceRows.length);
|
|
831
|
+
const voided = withVoided ? byId(await collect(invoices, voidedFilter, ["id", "invoiceTotal"])) : void 0;
|
|
832
|
+
const lineFields = [.../* @__PURE__ */ new Set([...LINE_FIELDS, ...groupDims.flatMap((d) => _nullishCoalesce(d.lineFields, () => ( []))), ...v.nonChargeable ? NC_LINE_FIELDS : []])];
|
|
833
|
+
const ids = [...invoiceById.keys()].sort((a, b) => a - b);
|
|
834
|
+
const lines = (await client.batch(async (c) => {
|
|
835
|
+
const filter = [{ field: "invoiceID", op: "in", value: c }, ...scopeCond];
|
|
836
|
+
const n = await billingItems.count({ filter });
|
|
837
|
+
if (typeof n !== "number") throw blindCount();
|
|
838
|
+
const rows = byId(await collect(billingItems, filter, lineFields));
|
|
839
|
+
if (rows.length !== n) throw countMismatch("line", rows.length, n);
|
|
840
|
+
return rows;
|
|
841
|
+
}, chunk(ids), { concurrency: CONCURRENCY })).flat();
|
|
842
|
+
const orphan = lines.find((l) => !invoiceById.has(_chunkFYF46OT3cjs.num.call(void 0, l["invoiceID"])));
|
|
843
|
+
if (orphan !== void 0) {
|
|
844
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskError)(`revenueRollup: billing line ${_chunkFYF46OT3cjs.num.call(void 0, orphan["id"])} belongs to invoice ${_chunkFYF46OT3cjs.num.call(void 0, orphan["invoiceID"])}, which is not among the ${invoiceById.size} streamed invoices; the fold was discarded`, {
|
|
845
|
+
code: "INVALID_RESPONSE",
|
|
846
|
+
category: "server",
|
|
847
|
+
operation: OP,
|
|
848
|
+
details: { reason: "orphan_line", lineID: _chunkFYF46OT3cjs.num.call(void 0, orphan["id"]), invoiceID: _chunkFYF46OT3cjs.num.call(void 0, orphan["invoiceID"]) },
|
|
849
|
+
suggestedAction: "Retry the call."
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
const uninvoiced = v.nonChargeable ? byId(await collect(billingItems, uninvoicedFilter, [...lineFields, "itemDate"])) : [];
|
|
853
|
+
if (v.nonChargeable && uninvoiced.length !== nUninvoiced) throw countMismatch("uninvoiced line", uninvoiced.length, nUninvoiced);
|
|
854
|
+
const all = uninvoiced.length > 0 ? [...lines, ...uninvoiced] : lines;
|
|
855
|
+
const joined = Object.fromEntries(Object.keys(JOINS).map((j) => [j, /* @__PURE__ */ new Map()]));
|
|
856
|
+
for (const j of joinsNeeded) {
|
|
857
|
+
const spec = JOINS[j];
|
|
858
|
+
const res = spec.make(http, client);
|
|
859
|
+
const fields = [.../* @__PURE__ */ new Set([...spec.fields, ...groupDims.flatMap((d) => _nullishCoalesce(_optionalChain([d, 'access', _21 => _21.joins, 'optionalAccess', _22 => _22[j]]), () => ( [])))])];
|
|
860
|
+
const want = [...new Set(spec.ids(all, joined))].filter((id) => id > 0).sort((a, b) => a - b);
|
|
861
|
+
const rows = await client.batch((c) => collect(res, [{ field: "id", op: "in", value: c }], fields), chunk(want), { concurrency: CONCURRENCY });
|
|
862
|
+
for (const r of rows.flat()) joined[j].set(_chunkFYF46OT3cjs.num.call(void 0, r["id"]), r);
|
|
863
|
+
}
|
|
864
|
+
return fold(v, { invoiceById, voided, lines, uninvoiced, joined, labels, inTypes, inCategories, scopeIds, calls, estimate });
|
|
865
|
+
}
|
|
866
|
+
function refusal(v, estimate, fixed, scopeIds, joinCount) {
|
|
867
|
+
const months = _chunkFYF46OT3cjs.monthsInWindow.call(void 0, v.dateFrom, v.dateTo);
|
|
868
|
+
const variable = estimate - fixed;
|
|
869
|
+
const fitMonths = Math.floor((v.budget - fixed) * months / variable);
|
|
870
|
+
const suggestions = [];
|
|
871
|
+
const text = [];
|
|
872
|
+
let unestimated = false;
|
|
873
|
+
const suggest = (s, t, estimated = false) => {
|
|
874
|
+
suggestions.push(s);
|
|
875
|
+
text.push(t);
|
|
876
|
+
if (!estimated) unestimated = true;
|
|
877
|
+
};
|
|
878
|
+
if (estimate <= BUDGET_CAP) suggest({ maxVendorCalls: estimate }, `maxVendorCalls: ${estimate} (cap ${BUDGET_CAP})`, true);
|
|
879
|
+
if (fitMonths >= 1 && fitMonths < months) {
|
|
880
|
+
const dateFrom = lastMonthsFrom(v.dateTo, fitMonths);
|
|
881
|
+
const est = fixed + Math.ceil(variable * fitMonths / months);
|
|
882
|
+
suggest({ dateFrom, estimate: est }, `dateFrom: '${dateFrom}' (est. ${est})`, true);
|
|
883
|
+
}
|
|
884
|
+
if (v.scopeKind === "tenantWide") suggest({ companyIDs: "[...]" }, "companyIDs: [...] (or ownerResourceID)");
|
|
885
|
+
if (estimate > BUDGET_CAP) {
|
|
886
|
+
if (v.nonChargeable) suggest({ includeNonChargeable: false }, "includeNonChargeable: false");
|
|
887
|
+
if (joinCount > 1) suggest({ groupBy: ["contractType"] }, "groupBy: ['contractType'] (fewer dimensions, fewer id-fetch calls)");
|
|
888
|
+
if (v.scopeKind === "companyIDs" || v.scopeKind === "ownerResourceID") suggest({ contractIDs: "[...]" }, "contractIDs: [...] (one call per group of contracts)");
|
|
889
|
+
const n = (_nullishCoalesce(scopeIds, () => ( []))).length;
|
|
890
|
+
if (v.scopeKind === "companyIDs" && n > 1) suggest({ companyIDs: "[...]" }, "a shorter companyIDs list (one call per part)");
|
|
891
|
+
if (v.scopeKind === "ownerResourceID" && n > 1) suggest({ companyIDs: "[...]" }, "companyIDs: a subset of the owner's companies (one call per part)");
|
|
892
|
+
if (v.scopeKind === "contractIDs" && n > 1) suggest({ contractIDs: "[...]" }, "a shorter contractIDs list (one call per part)");
|
|
893
|
+
}
|
|
894
|
+
if (suggestions.length === 0) suggest({ maxVendorCalls: BUDGET_CAP }, `maxVendorCalls: ${BUDGET_CAP} (the cap; the estimate ${estimate} is above it, so this call will be refused at this estimate even at the cap; no narrower shape exists for this scope and window)`, true);
|
|
895
|
+
const caveat = unestimated ? ". Narrowings without an estimate may still be refused: the invoice stream is tenant-wide in every scope, and the line stream costs at least one page and one count per chunk of 500 of those invoices, so a narrower scope or shape does not reduce that part" : "";
|
|
896
|
+
const scope = v.scopeKind === "tenantWide" ? "tenantWide" : `${v.scopeKind} ${(_nullishCoalesce(scopeIds, () => ( []))).length} id(s)`;
|
|
897
|
+
return budgetError(
|
|
898
|
+
`revenueRollup: estimated ${estimate} vendor calls exceeds maxVendorCalls ${v.budget} (window ${v.dateFrom}..${v.dateTo}, ${scope}). Narrow with one of: ${text.join("; ")}${caveat}`,
|
|
899
|
+
{ phase: "estimate", estimate, budget: v.budget, suggestions },
|
|
900
|
+
`Narrow with one of: ${text.join("; ")}${caveat}`
|
|
901
|
+
);
|
|
902
|
+
}
|
|
903
|
+
var amt = () => ({ amount: 0, lines: 0 });
|
|
904
|
+
var nc = () => ({ contractCovered: { value: 0, hours: 0, lines: 0 }, nonBillable: { value: 0, hours: 0, lines: 0 } });
|
|
905
|
+
var add = (a, c) => {
|
|
906
|
+
a.amount += c;
|
|
907
|
+
a.lines += 1;
|
|
908
|
+
};
|
|
909
|
+
var outAmt = (a) => ({ amount: money(a.amount), lines: a.lines });
|
|
910
|
+
var outNc = (n) => {
|
|
911
|
+
const o = (k) => ({ value: money(n[k].value), hours: n[k].hours / 100, lines: n[k].lines });
|
|
912
|
+
return { contractCovered: o("contractCovered"), nonBillable: o("nonBillable") };
|
|
913
|
+
};
|
|
914
|
+
var outRecord = (r) => Object.fromEntries([...r].sort((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0).map(([k, a]) => [k, outAmt(a)]));
|
|
915
|
+
function ncClass(line) {
|
|
916
|
+
if (line["nonBillable"] === true || line["nonBillable"] === 1) return "nonBillable";
|
|
917
|
+
return extC(line) > 0 && revC(line) === 0 ? "contractCovered" : void 0;
|
|
918
|
+
}
|
|
919
|
+
function ncAdd(n, k, line) {
|
|
920
|
+
n[k].value += extC(line);
|
|
921
|
+
if (isLabour(line)) n[k].hours += cents(line["quantity"]);
|
|
922
|
+
n[k].lines += 1;
|
|
923
|
+
}
|
|
924
|
+
function compareIds(a, b) {
|
|
925
|
+
for (let i = 0; i < a.length; i++) {
|
|
926
|
+
const x = _nullishCoalesce(a[i], () => ( null));
|
|
927
|
+
const y = _nullishCoalesce(b[i], () => ( null));
|
|
928
|
+
if (x === y) continue;
|
|
929
|
+
if (x === null) return -1;
|
|
930
|
+
if (y === null) return 1;
|
|
931
|
+
if (typeof x === "number" && typeof y === "number") return x - y;
|
|
932
|
+
return String(x) < String(y) ? -1 : 1;
|
|
933
|
+
}
|
|
934
|
+
return 0;
|
|
935
|
+
}
|
|
936
|
+
function fold(v, f) {
|
|
937
|
+
const contractLabels = f.labels[CONTRACTS_PATH];
|
|
938
|
+
const bucketLabel = (field, fallback) => (t) => {
|
|
939
|
+
const values = contractLabels.get(field);
|
|
940
|
+
const l = _optionalChain([values, 'optionalAccess', _23 => _23.get, 'call', _24 => _24(t)]);
|
|
941
|
+
if (l === void 0) return `${fallback} ${t}`;
|
|
942
|
+
return [...values.values()].filter((x) => x === l).length > 1 ? `${l} (${t})` : l;
|
|
943
|
+
};
|
|
944
|
+
const typeLabel = bucketLabel("contractType", "type");
|
|
945
|
+
const catLabel = bucketLabel("contractCategory", "category");
|
|
946
|
+
const needCompany = v.groupBy.some((d) => _optionalChain([DIMENSIONS, 'access', _25 => _25[d], 'access', _26 => _26.joins, 'optionalAccess', _27 => _27.companies]));
|
|
947
|
+
const byLob = v.groupBy.includes("lineOfBusiness");
|
|
948
|
+
const unlabelled = /* @__PURE__ */ new Set();
|
|
949
|
+
const uncontracted = amt();
|
|
950
|
+
const unresolvedContracts = amt();
|
|
951
|
+
const unresolvedCompanies = amt();
|
|
952
|
+
const unresolvedJoins = new Map(v.groupBy.flatMap((d) => DIMENSIONS[d].unresolved ? [[DIMENSIONS[d].unresolved, amt()]] : []));
|
|
953
|
+
const unresolvedNc = amt();
|
|
954
|
+
const noPointer = amt();
|
|
955
|
+
const otherTypes = /* @__PURE__ */ new Map();
|
|
956
|
+
const otherCats = /* @__PURE__ */ new Map();
|
|
957
|
+
const cells = /* @__PURE__ */ new Map();
|
|
958
|
+
const allContracts = /* @__PURE__ */ new Set();
|
|
959
|
+
const lineTotals = /* @__PURE__ */ new Map();
|
|
960
|
+
const zeroTotal = amt();
|
|
961
|
+
const zeroIds = /* @__PURE__ */ new Set();
|
|
962
|
+
const locate = (line, invoice) => {
|
|
963
|
+
const contractID = _chunkFYF46OT3cjs.num.call(void 0, line["contractID"]);
|
|
964
|
+
if (contractID === 0) return { bucket: uncontracted, unresolved: false };
|
|
965
|
+
const contract = f.joined.contracts.get(contractID);
|
|
966
|
+
if (contract === void 0) return { bucket: unresolvedContracts, unresolved: true };
|
|
967
|
+
const type = _chunkFYF46OT3cjs.num.call(void 0, contract["contractType"]);
|
|
968
|
+
if (!f.inTypes.has(type)) return { bucket: slot(otherTypes, typeLabel(type)), unresolved: false };
|
|
969
|
+
const cat = _chunkFYF46OT3cjs.num.call(void 0, contract["contractCategory"]);
|
|
970
|
+
if (f.inCategories !== void 0 && !f.inCategories.has(cat)) return { bucket: slot(otherCats, cat === 0 ? "uncategorised" : catLabel(cat)), unresolved: false };
|
|
971
|
+
const company = f.joined.companies.get(_chunkFYF46OT3cjs.num.call(void 0, line["companyID"]));
|
|
972
|
+
if (needCompany && company === void 0) return { bucket: unresolvedCompanies, unresolved: true };
|
|
973
|
+
const ctx = { line, invoice, contract, company, joined: f.joined, labels: f.labels, unlabelled };
|
|
974
|
+
const keys = {};
|
|
975
|
+
const ids = [];
|
|
976
|
+
for (const d of v.groupBy) {
|
|
977
|
+
const k = DIMENSIONS[d].key(ctx);
|
|
978
|
+
if (typeof k === "string") return { bucket: unresolvedJoins.get(k), unresolved: true };
|
|
979
|
+
keys[d] = k;
|
|
980
|
+
ids.push(k.id);
|
|
981
|
+
}
|
|
982
|
+
const id = JSON.stringify(ids);
|
|
983
|
+
let cell = cells.get(id);
|
|
984
|
+
if (cell === void 0) {
|
|
985
|
+
cell = { keys, ids, revenue: 0, credits: 0, creditLines: 0, included: 0, contracts: /* @__PURE__ */ new Set(), items: 0, nc: nc() };
|
|
986
|
+
cells.set(id, cell);
|
|
987
|
+
}
|
|
988
|
+
return { cell };
|
|
989
|
+
};
|
|
990
|
+
for (const line of f.lines) {
|
|
991
|
+
const invoiceID = _chunkFYF46OT3cjs.num.call(void 0, line["invoiceID"]);
|
|
992
|
+
const invoice = f.invoiceById.get(invoiceID);
|
|
993
|
+
lineTotals.set(invoiceID, (_nullishCoalesce(lineTotals.get(invoiceID), () => ( 0))) + cents(line["totalAmount"]));
|
|
994
|
+
const c = revC(line);
|
|
995
|
+
if (invoice.total === 0) {
|
|
996
|
+
add(zeroTotal, c);
|
|
997
|
+
zeroIds.add(invoiceID);
|
|
998
|
+
}
|
|
999
|
+
const at = locate(line, invoice);
|
|
1000
|
+
if (!("cell" in at)) {
|
|
1001
|
+
add(at.bucket, c);
|
|
1002
|
+
if (at.unresolved && v.nonChargeable && ncClass(line)) add(unresolvedNc, extC(line));
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
1005
|
+
const cell = at.cell;
|
|
1006
|
+
cell.revenue += c;
|
|
1007
|
+
if (c < 0) {
|
|
1008
|
+
cell.credits += c;
|
|
1009
|
+
cell.creditLines += 1;
|
|
1010
|
+
}
|
|
1011
|
+
cell.included += extC(line) - c;
|
|
1012
|
+
cell.contracts.add(_chunkFYF46OT3cjs.num.call(void 0, line["contractID"]));
|
|
1013
|
+
cell.items += 1;
|
|
1014
|
+
allContracts.add(_chunkFYF46OT3cjs.num.call(void 0, line["contractID"]));
|
|
1015
|
+
if (byLob && cell.keys.lineOfBusiness.id === null) add(noPointer, c);
|
|
1016
|
+
const k = v.nonChargeable ? ncClass(line) : void 0;
|
|
1017
|
+
if (k) ncAdd(cell.nc, k, line);
|
|
1018
|
+
}
|
|
1019
|
+
for (const line of f.uninvoiced) {
|
|
1020
|
+
const k = ncClass(line);
|
|
1021
|
+
if (!k) continue;
|
|
1022
|
+
const at = locate(line, { total: 0, month: String(_nullishCoalesce(line["itemDate"], () => ( ""))).slice(0, 7), companyID: _chunkFYF46OT3cjs.num.call(void 0, line["companyID"]) });
|
|
1023
|
+
if ("cell" in at) ncAdd(at.cell.nc, k, line);
|
|
1024
|
+
else if (at.unresolved) add(unresolvedNc, extC(line));
|
|
1025
|
+
}
|
|
1026
|
+
const sorted = [...cells.values()].sort((a, b) => b.revenue - a.revenue || compareIds(a.ids, b.ids));
|
|
1027
|
+
const kept = sorted.slice(0, v.top);
|
|
1028
|
+
const cut = sorted.slice(v.top);
|
|
1029
|
+
const sum = (k) => sorted.reduce((s, x) => s + x[k], 0);
|
|
1030
|
+
const ncTotal = nc();
|
|
1031
|
+
for (const x of sorted) for (const k of ["contractCovered", "nonBillable"]) for (const m of ["value", "hours", "lines"]) ncTotal[k][m] += x.nc[k][m];
|
|
1032
|
+
const reconciled = v.scopeKind === "contractIDs" ? [] : [...f.invoiceById].filter(([, inv]) => v.scopeKind === "tenantWide" || f.scopeIds.includes(inv.companyID));
|
|
1033
|
+
let headerTotal = 0;
|
|
1034
|
+
let lineTotal = 0;
|
|
1035
|
+
let mismatched = 0;
|
|
1036
|
+
for (const [id, inv] of reconciled) {
|
|
1037
|
+
const lt = _nullishCoalesce(lineTotals.get(id), () => ( 0));
|
|
1038
|
+
headerTotal += inv.total;
|
|
1039
|
+
lineTotal += lt;
|
|
1040
|
+
if (lt !== inv.total) mismatched += 1;
|
|
1041
|
+
}
|
|
1042
|
+
const blindSurfaces2 = [...unlabelled].sort();
|
|
1043
|
+
const noInternal = [...f.lines, ...f.uninvoiced].filter(lacksInternal).length;
|
|
1044
|
+
if (noInternal > 0) blindSurfaces2.push(`billing_items: ${noInternal} line(s) without internalCurrencyTotalAmount / internalCurrencyExtendedPrice; their local-currency totalAmount / extendedPrice was used instead`);
|
|
1045
|
+
if (unresolvedCompanies.lines > 0) blindSurfaces2.push("companies.query: some line companies were not returned by the id-in fetch (the companies list surface can be blind to companies the API user does not own); their lines are in coverage.excluded.unresolvedCompanies");
|
|
1046
|
+
if (v.scopeKind === "ownerResourceID") blindSurfaces2.push("companies.query: ownerResourceID was resolved through the companies list surface, which can be blind to companies the API user does not own; a company it hides is missing from the scope and its revenue is not counted (scope.ids lists the companies that were resolved)");
|
|
1047
|
+
const notes = [
|
|
1048
|
+
"basis: invoice date (invoiceDateTime) as whole UTC days; voided invoices excluded; revenue = internalCurrencyTotalAmount (ex-tax, internal currency); includedValue is informational and never in revenue",
|
|
1049
|
+
"prepaid block-hour / retainer contracts: revenue is the billed purchase"
|
|
1050
|
+
];
|
|
1051
|
+
notes.push("coverage.zeroTotalInvoices is informational: its lines are also folded into rows / the excluded buckets, so it is not part of coverage.excluded (revenue + the excluded line buckets reconcile to the whole)");
|
|
1052
|
+
if (v.scopeKind === "contractIDs") {
|
|
1053
|
+
notes.push("reconciliation is not run for a contractIDs scope (only the scoped contracts' lines are fetched)");
|
|
1054
|
+
notes.push("coverage.excluded.voidedInvoices is omitted for a contractIDs scope: an invoice header cannot be attributed to a contract (zeroTotalInvoices counts the zero-total invoices carrying a scoped line)");
|
|
1055
|
+
} else if (v.scopeKind !== "tenantWide") {
|
|
1056
|
+
notes.push("scope applies to the line's companyID; reconciliation, voidedInvoices and the zero-total count cover the invoices whose header company is in scope (zero-total also counts an invoice carrying an in-scope line; a mismatch can mean a line billed to another company)");
|
|
1057
|
+
}
|
|
1058
|
+
if (v.nonChargeable) {
|
|
1059
|
+
notes.push("nonChargeable is never added to revenue: contractCovered = billable lines with internalCurrencyExtendedPrice > 0 and internalCurrencyTotalAmount = 0, nonBillable = nonBillable lines; value = \u03A3 internalCurrencyExtendedPrice, hours = \u03A3 quantity on labour lines (Labor and Labor Adjustment). Invoiced lines are keyed on the invoice date; uninvoiced lines (invoiceID notExist) have no invoice date, so they are windowed and month-keyed on itemDate (whole UTC days). Uninvoiced lines outside the contract-type/category scope or uncontracted are not counted.");
|
|
1060
|
+
}
|
|
1061
|
+
for (const [id, inv] of f.invoiceById) {
|
|
1062
|
+
if (inv.total === 0 && (v.scopeKind === "tenantWide" || v.scopeKind !== "contractIDs" && f.scopeIds.includes(inv.companyID))) zeroIds.add(id);
|
|
1063
|
+
}
|
|
1064
|
+
const result = {
|
|
1065
|
+
window: { dateFrom: v.dateFrom, dateTo: v.dateTo, basis: "invoiceDate" },
|
|
1066
|
+
scope: {
|
|
1067
|
+
kind: v.scopeKind,
|
|
1068
|
+
...v.ownerResourceID !== void 0 ? { ownerResourceID: v.ownerResourceID } : {},
|
|
1069
|
+
...f.scopeIds !== void 0 ? { ids: f.scopeIds } : {}
|
|
1070
|
+
},
|
|
1071
|
+
groupBy: v.groupBy,
|
|
1072
|
+
top: v.top,
|
|
1073
|
+
rows: kept.map((x) => ({
|
|
1074
|
+
keys: x.keys,
|
|
1075
|
+
revenue: money(x.revenue),
|
|
1076
|
+
credits: { amount: money(x.credits), lines: x.creditLines },
|
|
1077
|
+
includedValue: money(x.included),
|
|
1078
|
+
contracts: x.contracts.size,
|
|
1079
|
+
items: x.items,
|
|
1080
|
+
...v.nonChargeable ? { nonChargeable: outNc(x.nc) } : {}
|
|
1081
|
+
})),
|
|
1082
|
+
...cut.length > 0 ? { rowsTruncated: { rows: cut.length, revenue: money(cut.reduce((s, x) => s + x.revenue, 0)) } } : {},
|
|
1083
|
+
totals: {
|
|
1084
|
+
revenue: money(sum("revenue")),
|
|
1085
|
+
credits: money(sum("credits")),
|
|
1086
|
+
includedValue: money(sum("included")),
|
|
1087
|
+
contracts: allContracts.size,
|
|
1088
|
+
items: sum("items"),
|
|
1089
|
+
...v.nonChargeable ? { nonChargeable: outNc(ncTotal) } : {}
|
|
1090
|
+
},
|
|
1091
|
+
coverage: {
|
|
1092
|
+
vendorCalls: f.calls,
|
|
1093
|
+
budget: v.budget,
|
|
1094
|
+
estimate: f.estimate,
|
|
1095
|
+
tenantHourlyShare: `${(f.calls / TENANT_HOURLY_LIMIT * 100).toFixed(2)}% of ${TENANT_HOURLY_LIMIT.toLocaleString("en-US")}/h`,
|
|
1096
|
+
contractTypeMap: sortedRecord(f.inTypes),
|
|
1097
|
+
...f.inCategories !== void 0 ? { contractCategoryMap: sortedRecord(f.inCategories) } : {},
|
|
1098
|
+
reconciliation: { headerTotal: money(headerTotal), lineTotal: money(lineTotal), delta: money(lineTotal - headerTotal), invoicesChecked: reconciled.length, invoicesMismatched: mismatched },
|
|
1099
|
+
zeroTotalInvoices: { count: zeroIds.size, ...outAmt(zeroTotal) },
|
|
1100
|
+
excluded: {
|
|
1101
|
+
...f.voided !== void 0 ? { voidedInvoices: { count: f.voided.length, headerAmount: money(f.voided.reduce((s, r) => s + cents(r["invoiceTotal"]), 0)) } } : {},
|
|
1102
|
+
uncontracted: outAmt(uncontracted),
|
|
1103
|
+
otherContractTypes: outRecord(otherTypes),
|
|
1104
|
+
otherContractCategories: outRecord(otherCats),
|
|
1105
|
+
unresolvedContracts: outAmt(unresolvedContracts),
|
|
1106
|
+
unresolvedCompanies: outAmt(unresolvedCompanies),
|
|
1107
|
+
...Object.fromEntries([...unresolvedJoins].map(([k, a]) => [k, outAmt(a)])),
|
|
1108
|
+
...v.nonChargeable ? { unresolvedNonChargeable: outAmt(unresolvedNc) } : {}
|
|
1109
|
+
},
|
|
1110
|
+
...byLob ? {
|
|
1111
|
+
lineOfBusiness: {
|
|
1112
|
+
noPointer: outAmt(noPointer),
|
|
1113
|
+
note: `LOB figures are floors: lines without a pointer are shown separately (the "no LOB assigned" row, id null). LOB is per billing line (no contract fallback), so a row's contracts = contracts with at least one line in that LOB, and per-LOB contract counts can sum to more than totals.contracts.`
|
|
1114
|
+
}
|
|
1115
|
+
} : {},
|
|
1116
|
+
blindSurfaces: blindSurfaces2,
|
|
1117
|
+
notes
|
|
1118
|
+
}
|
|
1119
|
+
};
|
|
1120
|
+
return result;
|
|
1121
|
+
}
|
|
1122
|
+
function slot(m, k) {
|
|
1123
|
+
const a = _nullishCoalesce(m.get(k), () => ( amt()));
|
|
1124
|
+
m.set(k, a);
|
|
1125
|
+
return a;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
440
1128
|
// src/operations/relationshipMap.ts
|
|
441
1129
|
async function relationshipMap(client, resourceKey) {
|
|
442
1130
|
void client;
|
|
443
1131
|
if (typeof resourceKey !== "string" || resourceKey.trim() === "") {
|
|
444
|
-
throw new (0,
|
|
1132
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
445
1133
|
`relationshipMap: resourceKey must be a non-empty plan resource key string (e.g. 'tickets'; got ${typeof resourceKey === "string" ? "a blank string" : typeof resourceKey})`,
|
|
446
1134
|
{ suggestedAction: "Pass the exact plan resource key (snake_case) from autotask_catalog \u2014 e.g. tickets, companies, configuration_items, ticket_notes." }
|
|
447
1135
|
);
|
|
448
1136
|
}
|
|
449
1137
|
const key = resourceKey.trim();
|
|
450
|
-
const entry =
|
|
1138
|
+
const entry = _chunkFYF46OT3cjs.RELATIONSHIP_MAP[key];
|
|
451
1139
|
if (entry === void 0) {
|
|
452
|
-
throw new (0,
|
|
453
|
-
`relationshipMap: unknown resource "${key}" \u2014 the plan has ${Object.keys(
|
|
1140
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskError)(
|
|
1141
|
+
`relationshipMap: unknown resource "${key}" \u2014 the plan has ${Object.keys(_chunkFYF46OT3cjs.RELATIONSHIP_MAP).length} resources`,
|
|
454
1142
|
{
|
|
455
1143
|
code: "RESOURCE_UNKNOWN",
|
|
456
1144
|
category: "validation",
|
|
457
|
-
suggestedAction: `Pass the exact plan resource key (snake_case) from autotask_catalog (e.g. tickets, companies, configuration_items). Zone facts served are ${
|
|
1145
|
+
suggestedAction: `Pass the exact plan resource key (snake_case) from autotask_catalog (e.g. tickets, companies, configuration_items). Zone facts served are ${_chunkFYF46OT3cjs.RELATIONSHIP_ZONE.label} (${_chunkFYF46OT3cjs.RELATIONSHIP_ZONE.zone})-verified.`
|
|
458
1146
|
}
|
|
459
1147
|
);
|
|
460
1148
|
}
|
|
@@ -471,24 +1159,24 @@ var BLIND_SURFACE_KINDS = [
|
|
|
471
1159
|
async function blindSurfaces(client, opts) {
|
|
472
1160
|
void client;
|
|
473
1161
|
if (opts !== void 0 && (typeof opts !== "object" || opts === null || Array.isArray(opts))) {
|
|
474
|
-
throw new (0,
|
|
1162
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)("blindSurfaces: opts must be an object { resource?: string, kind?: BlindSurfaceKind }");
|
|
475
1163
|
}
|
|
476
|
-
const inv =
|
|
1164
|
+
const inv = _chunkFYF46OT3cjs.BLIND_SURFACE_INVENTORY;
|
|
477
1165
|
if (inv === null) {
|
|
478
|
-
throw new (0,
|
|
1166
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)("blindSurfaces: the reachability inventory is absent from the generated artifact \u2014 re-run the build");
|
|
479
1167
|
}
|
|
480
1168
|
let surfaces = inv.surfaces;
|
|
481
|
-
if (_optionalChain([opts, 'optionalAccess',
|
|
1169
|
+
if (_optionalChain([opts, 'optionalAccess', _28 => _28.resource]) !== void 0) {
|
|
482
1170
|
const key = typeof opts.resource === "string" ? opts.resource.trim() : "";
|
|
483
1171
|
if (key === "") {
|
|
484
|
-
throw new (0,
|
|
1172
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
485
1173
|
"blindSurfaces: opts.resource must be a non-empty plan resource key string (e.g. 'tickets')",
|
|
486
1174
|
{ suggestedAction: "Pass the exact plan resource key (snake_case) from autotask_catalog \u2014 e.g. tickets, companies, configuration_items." }
|
|
487
1175
|
);
|
|
488
1176
|
}
|
|
489
1177
|
if (!inv.surfaces.some((row) => row.resource === key)) {
|
|
490
|
-
throw new (0,
|
|
491
|
-
`blindSurfaces: unknown resource "${key}" \u2014 the plan has ${Object.keys(
|
|
1178
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskError)(
|
|
1179
|
+
`blindSurfaces: unknown resource "${key}" \u2014 the plan has ${Object.keys(_chunkFYF46OT3cjs.RELATIONSHIP_MAP).length} resources`,
|
|
492
1180
|
{
|
|
493
1181
|
code: "RESOURCE_UNKNOWN",
|
|
494
1182
|
category: "validation",
|
|
@@ -498,9 +1186,9 @@ async function blindSurfaces(client, opts) {
|
|
|
498
1186
|
}
|
|
499
1187
|
surfaces = surfaces.filter((row) => row.resource === key);
|
|
500
1188
|
}
|
|
501
|
-
if (_optionalChain([opts, 'optionalAccess',
|
|
1189
|
+
if (_optionalChain([opts, 'optionalAccess', _29 => _29.kind]) !== void 0) {
|
|
502
1190
|
if (!BLIND_SURFACE_KINDS.includes(opts.kind)) {
|
|
503
|
-
throw new (0,
|
|
1191
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
504
1192
|
`blindSurfaces: unknown kind "${String(opts.kind)}" \u2014 the valid classes are: ${BLIND_SURFACE_KINDS.join(", ")}`,
|
|
505
1193
|
{ suggestedAction: `Filter with one of: ${BLIND_SURFACE_KINDS.join(", ")} (or omit kind for the full 116-surface inventory).` }
|
|
506
1194
|
);
|
|
@@ -513,51 +1201,51 @@ async function blindSurfaces(client, opts) {
|
|
|
513
1201
|
// src/operations/probeReachability.ts
|
|
514
1202
|
async function probeReachability(client, opts) {
|
|
515
1203
|
if (opts !== void 0 && (typeof opts !== "object" || opts === null || Array.isArray(opts))) {
|
|
516
|
-
throw new (0,
|
|
1204
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)("probeReachability: opts must be an object { resources?: string[], limit?: 1|2, maxWireCalls?: number, confirmProbe?: true }");
|
|
517
1205
|
}
|
|
518
|
-
const defs =
|
|
519
|
-
if (_optionalChain([opts, 'optionalAccess',
|
|
520
|
-
throw new (0,
|
|
1206
|
+
const defs = _chunkFYF46OT3cjs.REACHABILITY_PROBE_DEFAULTS;
|
|
1207
|
+
if (_optionalChain([opts, 'optionalAccess', _30 => _30.confirmProbe]) !== true) {
|
|
1208
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
521
1209
|
"probeReachability: confirmProbe: true is required \u2014 the op deliberately fans out read wire across the tenant (one bounded query per surface); pass confirmProbe: true to run it",
|
|
522
1210
|
{ suggestedAction: `Re-call with { confirmProbe: true } (plus resources/limit/maxWireCalls as needed). For the ZERO-wire pinned inventory use operations.blindSurfaces.` }
|
|
523
1211
|
);
|
|
524
1212
|
}
|
|
525
|
-
const limit = _nullishCoalesce(_optionalChain([opts, 'optionalAccess',
|
|
1213
|
+
const limit = _nullishCoalesce(_optionalChain([opts, 'optionalAccess', _31 => _31.limit]), () => ( defs.limitDefault));
|
|
526
1214
|
if (!Number.isInteger(limit) || limit < 1 || limit > defs.limitMax) {
|
|
527
|
-
throw new (0,
|
|
1215
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
528
1216
|
`probeReachability: limit must be an integer 1..${defs.limitMax} (got ${String(limit)}) \u2014 the rows-per-surface bound for the discovery read`
|
|
529
1217
|
);
|
|
530
1218
|
}
|
|
531
|
-
const cap = _nullishCoalesce(_optionalChain([opts, 'optionalAccess',
|
|
1219
|
+
const cap = _nullishCoalesce(_optionalChain([opts, 'optionalAccess', _32 => _32.maxWireCalls]), () => ( defs.defaultCap));
|
|
532
1220
|
if (!Number.isInteger(cap) || cap < 1) {
|
|
533
|
-
throw new (0,
|
|
1221
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(`probeReachability: maxWireCalls must be a positive integer (got ${String(cap)})`);
|
|
534
1222
|
}
|
|
535
1223
|
if (cap > defs.hardCap) {
|
|
536
|
-
throw new (0,
|
|
1224
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
537
1225
|
`probeReachability: maxWireCalls ${cap} exceeds the hard wire cap of ${defs.hardCap} \u2014 the sweep is refused above it (never capped silently)`,
|
|
538
1226
|
{ suggestedAction: `Pass maxWireCalls \u2264 ${defs.hardCap} and a resources set no larger than the cap (the refused set is never truncated).` }
|
|
539
1227
|
);
|
|
540
1228
|
}
|
|
541
|
-
const inv =
|
|
1229
|
+
const inv = _chunkFYF46OT3cjs.BLIND_SURFACE_INVENTORY;
|
|
542
1230
|
if (inv === null) {
|
|
543
|
-
throw new (0,
|
|
1231
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)("probeReachability: the reachability inventory is absent from the generated artifact \u2014 re-run the build");
|
|
544
1232
|
}
|
|
545
1233
|
let targets;
|
|
546
|
-
if (_optionalChain([opts, 'optionalAccess',
|
|
1234
|
+
if (_optionalChain([opts, 'optionalAccess', _33 => _33.resources]) === void 0) {
|
|
547
1235
|
targets = [...defs.defaultTargets];
|
|
548
1236
|
} else {
|
|
549
1237
|
if (!Array.isArray(opts.resources) || opts.resources.length === 0) {
|
|
550
|
-
throw new (0,
|
|
1238
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)("probeReachability: opts.resources must be a non-empty string[] of plan resource keys (or omit it for the default non-data-full set)");
|
|
551
1239
|
}
|
|
552
1240
|
const seen = /* @__PURE__ */ new Set();
|
|
553
1241
|
for (const r of opts.resources) {
|
|
554
1242
|
if (typeof r !== "string" || r.trim() === "") {
|
|
555
|
-
throw new (0,
|
|
1243
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(`probeReachability: opts.resources must contain non-empty plan resource key strings (got ${typeof r === "string" && r.trim() === "" ? "a blank string" : typeof r})`);
|
|
556
1244
|
}
|
|
557
1245
|
const key = r.trim();
|
|
558
1246
|
if (!inv.surfaces.some((row) => row.resource === key)) {
|
|
559
1247
|
const surfaceCount = new Set(inv.surfaces.map((s) => s.resource)).size;
|
|
560
|
-
throw new (0,
|
|
1248
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskError)(
|
|
561
1249
|
`probeReachability: unknown resource "${key}" \u2014 the inventory has ${surfaceCount} surfaces`,
|
|
562
1250
|
{
|
|
563
1251
|
code: "RESOURCE_UNKNOWN",
|
|
@@ -571,7 +1259,7 @@ async function probeReachability(client, opts) {
|
|
|
571
1259
|
targets = [...seen];
|
|
572
1260
|
}
|
|
573
1261
|
if (targets.length > cap) {
|
|
574
|
-
throw new (0,
|
|
1262
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
575
1263
|
`probeReachability: ${targets.length} requested surface(s) exceed the wire cap of ${cap} \u2014 the sweep is refused above its cap (never truncated)`,
|
|
576
1264
|
{ suggestedAction: `Raise maxWireCalls (\u2264 ${defs.hardCap}) to cover the set, or pass a smaller resources list.` }
|
|
577
1265
|
);
|
|
@@ -581,8 +1269,8 @@ async function probeReachability(client, opts) {
|
|
|
581
1269
|
for (const resource of targets.sort()) {
|
|
582
1270
|
const row = byResource.get(resource);
|
|
583
1271
|
if (row === void 0) continue;
|
|
584
|
-
const holder = client[
|
|
585
|
-
const probeFn = _optionalChain([holder, 'optionalAccess',
|
|
1272
|
+
const holder = client[_chunkFYF46OT3cjs.camelKey.call(void 0, resource)];
|
|
1273
|
+
const probeFn = _optionalChain([holder, 'optionalAccess', _34 => _34.queryBounded]);
|
|
586
1274
|
if (typeof probeFn !== "function") {
|
|
587
1275
|
results.push({
|
|
588
1276
|
...row,
|
|
@@ -593,14 +1281,14 @@ async function probeReachability(client, opts) {
|
|
|
593
1281
|
}
|
|
594
1282
|
try {
|
|
595
1283
|
const res = await probeFn.call(holder, { limit });
|
|
596
|
-
const items = Array.isArray(_optionalChain([res, 'optionalAccess',
|
|
1284
|
+
const items = Array.isArray(_optionalChain([res, 'optionalAccess', _35 => _35.items])) ? res.items : [];
|
|
597
1285
|
results.push({
|
|
598
1286
|
...row,
|
|
599
1287
|
unfilteredRows: items.length,
|
|
600
1288
|
probe: { ok: true, wire: 1, error: null }
|
|
601
1289
|
});
|
|
602
1290
|
} catch (err) {
|
|
603
|
-
const code =
|
|
1291
|
+
const code = _chunkFYF46OT3cjs.isAutotaskError.call(void 0, err) ? err.code : "NETWORK_ERROR";
|
|
604
1292
|
const message = err instanceof Error ? err.message : String(err);
|
|
605
1293
|
results.push({
|
|
606
1294
|
...row,
|
|
@@ -628,30 +1316,30 @@ var CREATE_BLOCKER_KINDS = [
|
|
|
628
1316
|
async function createGuidance(client, opts) {
|
|
629
1317
|
void client;
|
|
630
1318
|
if (opts !== void 0 && (typeof opts !== "object" || opts === null || Array.isArray(opts))) {
|
|
631
|
-
throw new (0,
|
|
1319
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
632
1320
|
"createGuidance: opts must be an object { resources?: string[], kind?: CreateBlockerKind }"
|
|
633
1321
|
);
|
|
634
1322
|
}
|
|
635
|
-
let servedRows =
|
|
1323
|
+
let servedRows = _chunkFYF46OT3cjs.CREATE_GUIDANCE.rows.map((row) => ({
|
|
636
1324
|
...row,
|
|
637
1325
|
// Issue #219 N4: one precedence rule, stated on every row (live-proven >
|
|
638
1326
|
// zone > spec). The spec set is not available here (it is a LIVE read) —
|
|
639
1327
|
// the helper is handed the plan's two shapes and names the source.
|
|
640
|
-
requiredFields:
|
|
1328
|
+
requiredFields: _chunkFYF46OT3cjs.effectiveRequiredFields.call(void 0, void 0, _nullishCoalesce(_optionalChain([_chunkFYF46OT3cjs.RELATIONSHIP_MAP, 'access', _36 => _36[row.resource], 'optionalAccess', _37 => _37.zoneFacts]), () => ( null)))
|
|
641
1329
|
}));
|
|
642
|
-
if (_optionalChain([opts, 'optionalAccess',
|
|
1330
|
+
if (_optionalChain([opts, 'optionalAccess', _38 => _38.resources]) !== void 0) {
|
|
643
1331
|
if (!Array.isArray(opts.resources) || opts.resources.length === 0) {
|
|
644
|
-
throw new (0,
|
|
1332
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)("createGuidance: opts.resources must be a non-empty string[] of plan resource keys");
|
|
645
1333
|
}
|
|
646
1334
|
const wanted = /* @__PURE__ */ new Set();
|
|
647
1335
|
for (const r of opts.resources) {
|
|
648
1336
|
if (typeof r !== "string" || r.trim() === "") {
|
|
649
|
-
throw new (0,
|
|
1337
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)("createGuidance: opts.resources must contain non-empty plan resource key strings");
|
|
650
1338
|
}
|
|
651
1339
|
const key = r.trim();
|
|
652
|
-
if (!
|
|
653
|
-
throw new (0,
|
|
654
|
-
`createGuidance: unknown resource "${key}" \u2014 the catalog has ${
|
|
1340
|
+
if (!_chunkFYF46OT3cjs.CREATE_GUIDANCE.rows.some((row) => row.resource === key)) {
|
|
1341
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskError)(
|
|
1342
|
+
`createGuidance: unknown resource "${key}" \u2014 the catalog has ${_chunkFYF46OT3cjs.CREATE_GUIDANCE.rows.length} create entities`,
|
|
655
1343
|
{
|
|
656
1344
|
code: "RESOURCE_UNKNOWN",
|
|
657
1345
|
category: "validation",
|
|
@@ -663,20 +1351,20 @@ async function createGuidance(client, opts) {
|
|
|
663
1351
|
}
|
|
664
1352
|
servedRows = servedRows.filter((row) => wanted.has(row.resource));
|
|
665
1353
|
}
|
|
666
|
-
if (_optionalChain([opts, 'optionalAccess',
|
|
1354
|
+
if (_optionalChain([opts, 'optionalAccess', _39 => _39.kind]) !== void 0) {
|
|
667
1355
|
if (!CREATE_BLOCKER_KINDS.includes(opts.kind)) {
|
|
668
|
-
throw new (0,
|
|
1356
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
669
1357
|
`createGuidance: unknown kind "${String(opts.kind)}" \u2014 the valid kinds are: ${CREATE_BLOCKER_KINDS.join(", ")}`
|
|
670
1358
|
);
|
|
671
1359
|
}
|
|
672
|
-
servedRows = servedRows.filter((row) => _optionalChain([row, 'access',
|
|
1360
|
+
servedRows = servedRows.filter((row) => _optionalChain([row, 'access', _40 => _40.blocker, 'optionalAccess', _41 => _41.kind]) === opts.kind);
|
|
673
1361
|
}
|
|
674
1362
|
return {
|
|
675
|
-
zone:
|
|
676
|
-
label:
|
|
677
|
-
verifiedAt:
|
|
1363
|
+
zone: _chunkFYF46OT3cjs.CREATE_GUIDANCE.zone,
|
|
1364
|
+
label: _chunkFYF46OT3cjs.CREATE_GUIDANCE.label,
|
|
1365
|
+
verifiedAt: _chunkFYF46OT3cjs.CREATE_GUIDANCE.verifiedAt,
|
|
678
1366
|
rows: servedRows,
|
|
679
|
-
counts:
|
|
1367
|
+
counts: _chunkFYF46OT3cjs.CREATE_GUIDANCE.counts
|
|
680
1368
|
};
|
|
681
1369
|
}
|
|
682
1370
|
|
|
@@ -685,7 +1373,7 @@ var RECOVERY_CONFIRM = "operations.recovery";
|
|
|
685
1373
|
var RECOVERY_DEFAULT_LIMIT = 25;
|
|
686
1374
|
var RECOVERY_MAX_LIMIT = 100;
|
|
687
1375
|
function configError(message, operation, suggestedAction) {
|
|
688
|
-
throw new (0,
|
|
1376
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(message, { operation, suggestedAction });
|
|
689
1377
|
}
|
|
690
1378
|
function parseMatchId(record) {
|
|
691
1379
|
const raw = record.id;
|
|
@@ -701,7 +1389,7 @@ function parseMatchId(record) {
|
|
|
701
1389
|
return { id: null, unsafe: true };
|
|
702
1390
|
}
|
|
703
1391
|
function validateRecoveryInput(input, operation) {
|
|
704
|
-
if (!
|
|
1392
|
+
if (!_chunkFYF46OT3cjs.isRecord.call(void 0, input)) {
|
|
705
1393
|
configError(
|
|
706
1394
|
`recovery: input must be an object { resource, marker, ... } (got ${typeof input})`,
|
|
707
1395
|
operation,
|
|
@@ -735,10 +1423,10 @@ function validateRecoveryInput(input, operation) {
|
|
|
735
1423
|
return { resource, marker, action };
|
|
736
1424
|
}
|
|
737
1425
|
function resolveRecoveryTarget(client, resource, operation) {
|
|
738
|
-
const entry =
|
|
1426
|
+
const entry = _chunkFYF46OT3cjs.RELATIONSHIP_MAP[resource];
|
|
739
1427
|
if (entry === void 0) {
|
|
740
|
-
throw new (0,
|
|
741
|
-
`recovery: unknown resource "${resource}" \u2014 the plan has ${Object.keys(
|
|
1428
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskError)(
|
|
1429
|
+
`recovery: unknown resource "${resource}" \u2014 the plan has ${Object.keys(_chunkFYF46OT3cjs.RELATIONSHIP_MAP).length} resources`,
|
|
742
1430
|
{
|
|
743
1431
|
code: "RESOURCE_UNKNOWN",
|
|
744
1432
|
category: "validation",
|
|
@@ -747,7 +1435,7 @@ function resolveRecoveryTarget(client, resource, operation) {
|
|
|
747
1435
|
}
|
|
748
1436
|
);
|
|
749
1437
|
}
|
|
750
|
-
const holder = client[
|
|
1438
|
+
const holder = client[_chunkFYF46OT3cjs.camelKey.call(void 0, resource)];
|
|
751
1439
|
if (!holder || typeof holder.queryBounded !== "function") {
|
|
752
1440
|
configError(
|
|
753
1441
|
`recovery: ${resource} exposes no bounded query surface \u2014 the marker search cannot run`,
|
|
@@ -758,7 +1446,7 @@ function resolveRecoveryTarget(client, resource, operation) {
|
|
|
758
1446
|
return { entry, holder };
|
|
759
1447
|
}
|
|
760
1448
|
function resolveMarkerField(input, resource, operation) {
|
|
761
|
-
const field = input.field !== void 0 ? String(input.field) :
|
|
1449
|
+
const field = input.field !== void 0 ? String(input.field) : _chunkFYF46OT3cjs.MARKER_FIELDS[resource];
|
|
762
1450
|
if (typeof field !== "string" || field.trim() === "") {
|
|
763
1451
|
configError(
|
|
764
1452
|
`recovery: ${resource} has no plan markerField and no input.field was passed \u2014 the marker search needs a name-like field to filter on`,
|
|
@@ -797,7 +1485,7 @@ function resolveExtraFilter(input, operation) {
|
|
|
797
1485
|
);
|
|
798
1486
|
}
|
|
799
1487
|
for (const c of input.filter) {
|
|
800
|
-
if (!
|
|
1488
|
+
if (!_chunkFYF46OT3cjs.isRecord.call(void 0, c) || typeof c.op !== "string" || c.op === "and" || c.op === "or") {
|
|
801
1489
|
configError(
|
|
802
1490
|
"recovery: input.filter accepts only plain leaf conditions ({ field, op, value }) \u2014 no grouping",
|
|
803
1491
|
operation,
|
|
@@ -822,7 +1510,7 @@ function resolveLimit(input, operation) {
|
|
|
822
1510
|
async function applyRecoveryDeletes(holder, matches, parentId, resource, operation) {
|
|
823
1511
|
const deleteMethod = holder.delete;
|
|
824
1512
|
if (deleteMethod === void 0) {
|
|
825
|
-
throw new (0,
|
|
1513
|
+
throw new (0, _chunkFYF46OT3cjs.DeleteBoundaryError)(
|
|
826
1514
|
`recovery: ${resource} has no delete method on the client \u2014 the boundary is marked available in the plan but the typed delete surface is missing`,
|
|
827
1515
|
{
|
|
828
1516
|
operation,
|
|
@@ -851,14 +1539,14 @@ async function applyRecoveryDeletes(holder, matches, parentId, resource, operati
|
|
|
851
1539
|
verify = "still-present";
|
|
852
1540
|
}
|
|
853
1541
|
} catch (err) {
|
|
854
|
-
if (err instanceof
|
|
1542
|
+
if (err instanceof _chunkFYF46OT3cjs.NotFoundError) verify = "not-found";
|
|
855
1543
|
else {
|
|
856
1544
|
verify = "error";
|
|
857
|
-
code =
|
|
1545
|
+
code = _chunkFYF46OT3cjs.isAutotaskError.call(void 0, err) ? err.code : "SERVER_ERROR";
|
|
858
1546
|
}
|
|
859
1547
|
}
|
|
860
1548
|
} catch (err) {
|
|
861
|
-
code =
|
|
1549
|
+
code = _chunkFYF46OT3cjs.isAutotaskError.call(void 0, err) ? err.code : "SERVER_ERROR";
|
|
862
1550
|
}
|
|
863
1551
|
deleted.push({ id, verified: verify === "not-found", code });
|
|
864
1552
|
}
|
|
@@ -886,7 +1574,7 @@ async function recovery(client, input, opts = {}) {
|
|
|
886
1574
|
const parentId = resolveParentId(input, entry, resource, operation);
|
|
887
1575
|
const extra = resolveExtraFilter(input, operation);
|
|
888
1576
|
const limit = resolveLimit(input, operation);
|
|
889
|
-
const boundaryNote =
|
|
1577
|
+
const boundaryNote = _chunkFYF46OT3cjs.DELETE_BOUNDARY_NOTES[resource];
|
|
890
1578
|
const boundary = {
|
|
891
1579
|
available: boundaryNote === void 0,
|
|
892
1580
|
reason: _nullishCoalesce(boundaryNote, () => ( null)),
|
|
@@ -898,7 +1586,7 @@ async function recovery(client, input, opts = {}) {
|
|
|
898
1586
|
};
|
|
899
1587
|
const isDelete = action === "delete";
|
|
900
1588
|
if (isDelete && boundaryNote !== void 0) {
|
|
901
|
-
throw new (0,
|
|
1589
|
+
throw new (0, _chunkFYF46OT3cjs.DeleteBoundaryError)(
|
|
902
1590
|
`recovery: the vendor defines no DELETE for ${resource} \u2014 the delete cannot be issued through the API (zero wire was issued)`,
|
|
903
1591
|
{
|
|
904
1592
|
operation,
|
|
@@ -907,10 +1595,10 @@ async function recovery(client, input, opts = {}) {
|
|
|
907
1595
|
}
|
|
908
1596
|
);
|
|
909
1597
|
}
|
|
910
|
-
const dryRun = _optionalChain([opts, 'optionalAccess',
|
|
911
|
-
const confirm = _nullishCoalesce(_optionalChain([opts, 'optionalAccess',
|
|
1598
|
+
const dryRun = _optionalChain([opts, 'optionalAccess', _42 => _42.dryRun]) !== false;
|
|
1599
|
+
const confirm = _nullishCoalesce(_optionalChain([opts, 'optionalAccess', _43 => _43.confirm]), () => ( input.confirm));
|
|
912
1600
|
if (isDelete && !dryRun && confirm !== RECOVERY_CONFIRM) {
|
|
913
|
-
throw new (0,
|
|
1601
|
+
throw new (0, _chunkFYF46OT3cjs.ConfirmationRequiredError)(
|
|
914
1602
|
`recovery: action 'delete' apply is destructive and requires its confirmation flag: pass confirm: "${RECOVERY_CONFIRM}"`,
|
|
915
1603
|
{
|
|
916
1604
|
operation: RECOVERY_CONFIRM,
|
|
@@ -926,7 +1614,7 @@ async function recovery(client, input, opts = {}) {
|
|
|
926
1614
|
const query = await holder.queryBounded({ filter: conditions, limit });
|
|
927
1615
|
if (query.hasMore) {
|
|
928
1616
|
const wire = isDelete ? "DELETE wire" : "mutating wire";
|
|
929
|
-
throw new (0,
|
|
1617
|
+
throw new (0, _chunkFYF46OT3cjs.RecoveryTruncatedError)(
|
|
930
1618
|
`recovery: the marker search on ${resource} TRIPPED its match cap (${limit}) \u2014 more matches exist than were returned (zero ${wire} was issued)`,
|
|
931
1619
|
{
|
|
932
1620
|
operation,
|
|
@@ -955,7 +1643,7 @@ async function recovery(client, input, opts = {}) {
|
|
|
955
1643
|
return { ...base, simulated: true, dryRun: true };
|
|
956
1644
|
}
|
|
957
1645
|
if (unsafeId) {
|
|
958
|
-
throw new (0,
|
|
1646
|
+
throw new (0, _chunkFYF46OT3cjs.MarkerUnsafeIdError)(
|
|
959
1647
|
`recovery: ${unsafeIds.length} of ${matches.length} match(es) on ${resource} have ids beyond Number.MAX_SAFE_INTEGER \u2014 they CANNOT be addressed through the SDK's typed delete surface (zero DELETE wire was issued)`,
|
|
960
1648
|
{
|
|
961
1649
|
operation,
|
|
@@ -973,7 +1661,7 @@ function needsConfirmation(record) {
|
|
|
973
1661
|
return record.effect === "destructive" || (_nullishCoalesce(record.flags, () => ( []))).includes("requiresApproval");
|
|
974
1662
|
}
|
|
975
1663
|
function refuse(message, operation, suggestedAction) {
|
|
976
|
-
throw new (0,
|
|
1664
|
+
throw new (0, _chunkFYF46OT3cjs.ConfirmationRequiredError)(message, { operation, suggestedAction });
|
|
977
1665
|
}
|
|
978
1666
|
var MODE_BY_EFFECT = {
|
|
979
1667
|
read: "read",
|
|
@@ -1019,7 +1707,7 @@ function looseTypeOk(type, value) {
|
|
|
1019
1707
|
case "string[]":
|
|
1020
1708
|
return Array.isArray(value) && value.every((v) => typeof v === "string");
|
|
1021
1709
|
case "object":
|
|
1022
|
-
return
|
|
1710
|
+
return _chunkFYF46OT3cjs.isRecord.call(void 0, value);
|
|
1023
1711
|
default:
|
|
1024
1712
|
return true;
|
|
1025
1713
|
}
|
|
@@ -1033,12 +1721,12 @@ function declaredArgList(record) {
|
|
|
1033
1721
|
function resolveDispatchArgs(record, argsIn) {
|
|
1034
1722
|
const operation = record.id;
|
|
1035
1723
|
const bad = (message) => {
|
|
1036
|
-
throw new (0,
|
|
1724
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(`invoke: "${operation}" ${message}`, { operation });
|
|
1037
1725
|
};
|
|
1038
1726
|
if (argsIn === void 0 || argsIn === null) return [];
|
|
1039
1727
|
if (Array.isArray(argsIn)) return argsIn;
|
|
1040
1728
|
const byName = argsIn;
|
|
1041
|
-
if (!
|
|
1729
|
+
if (!_chunkFYF46OT3cjs.isRecord.call(void 0, byName) || ![Object.prototype, null].includes(Object.getPrototypeOf(byName))) {
|
|
1042
1730
|
return bad(`args must be a positional array or a plain object of named inputs (declared: ${declaredArgList(record)})`);
|
|
1043
1731
|
}
|
|
1044
1732
|
const schema = _nullishCoalesce(record.inputSchema, () => ( {}));
|
|
@@ -1059,7 +1747,7 @@ function validateDispatchArgs(record, argsIn) {
|
|
|
1059
1747
|
const bagName = names.find((n) => schema[n].properties);
|
|
1060
1748
|
const surplus = args[names.length];
|
|
1061
1749
|
const bagHint = bagName && surplus !== null && typeof surplus === "object" && !Array.isArray(surplus) ? ` \u2014 options go inside the single ${bagName} object` : "";
|
|
1062
|
-
throw new (0,
|
|
1750
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
1063
1751
|
`invoke: "${operation}" declares ${names.length} positional arg(s) (${declaredArgList(record)}) but ${args.length} were passed \u2014 extra args are refused${bagHint}`,
|
|
1064
1752
|
{
|
|
1065
1753
|
operation,
|
|
@@ -1069,7 +1757,7 @@ function validateDispatchArgs(record, argsIn) {
|
|
|
1069
1757
|
}
|
|
1070
1758
|
const required = names.filter((n) => schema[n].required);
|
|
1071
1759
|
if (args.length < required.length) {
|
|
1072
|
-
throw new (0,
|
|
1760
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
1073
1761
|
`invoke: "${operation}" requires ${required.length} positional arg(s) (${required.map((n) => `${n}: ${schema[n].type}`).join(", ")}) but only ${args.length} were passed \u2014 declared args: ${declaredArgList(record)}`,
|
|
1074
1762
|
{ operation }
|
|
1075
1763
|
);
|
|
@@ -1080,7 +1768,7 @@ function validateDispatchArgs(record, argsIn) {
|
|
|
1080
1768
|
const name = names[i];
|
|
1081
1769
|
const spec = schema[name];
|
|
1082
1770
|
if (!looseTypeOk(spec.type, value)) {
|
|
1083
|
-
throw new (0,
|
|
1771
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
1084
1772
|
`invoke: "${operation}" arg ${i + 1} is declared ${name}: ${spec.type} \u2014 the value has the wrong type (pass a ${spec.type} value)`,
|
|
1085
1773
|
{ operation }
|
|
1086
1774
|
);
|
|
@@ -1088,17 +1776,17 @@ function validateDispatchArgs(record, argsIn) {
|
|
|
1088
1776
|
}
|
|
1089
1777
|
}
|
|
1090
1778
|
function foldFlattenedOpts(record, toolArgs) {
|
|
1091
|
-
if (!
|
|
1092
|
-
throw new (0,
|
|
1779
|
+
if (!_chunkFYF46OT3cjs.isRecord.call(void 0, toolArgs)) {
|
|
1780
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(`invoke: "${record.id}" tool args must be a plain object of named inputs`, { operation: record.id });
|
|
1093
1781
|
}
|
|
1094
|
-
const bagKeys = (_nullishCoalesce(_optionalChain([record, 'access',
|
|
1782
|
+
const bagKeys = (_nullishCoalesce(_optionalChain([record, 'access', _44 => _44.inputSchema, 'optionalAccess', _45 => _45.opts, 'optionalAccess', _46 => _46.properties]), () => ( []))).map((p) => p.name);
|
|
1095
1783
|
const flat = Object.keys(toolArgs).filter((k) => bagKeys.includes(k) && toolArgs[k] !== void 0 && toolArgs[k] !== null);
|
|
1096
1784
|
const folded = { ...toolArgs };
|
|
1097
1785
|
for (const k of bagKeys) delete folded[k];
|
|
1098
1786
|
if (folded.opts === null) delete folded.opts;
|
|
1099
1787
|
if (flat.length === 0) return folded;
|
|
1100
1788
|
if (folded.opts !== void 0) {
|
|
1101
|
-
throw new (0,
|
|
1789
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
1102
1790
|
`invoke: "${record.id}" was given both opts and flattened option(s) ${flat.map((k) => `"${k}"`).join(", ")} \u2014 pass one form`,
|
|
1103
1791
|
{ operation: record.id }
|
|
1104
1792
|
);
|
|
@@ -1159,12 +1847,12 @@ function validateParamsBag(record, args) {
|
|
|
1159
1847
|
if (record.inputSchemaStrict !== true) return;
|
|
1160
1848
|
const valid = PARAMS_BAG_KEYS[record.operation];
|
|
1161
1849
|
if (valid === void 0) return;
|
|
1162
|
-
const bag = _optionalChain([args, 'optionalAccess',
|
|
1163
|
-
if (bag === void 0 || bag === null || !
|
|
1850
|
+
const bag = _optionalChain([args, 'optionalAccess', _47 => _47[0]]);
|
|
1851
|
+
if (bag === void 0 || bag === null || !_chunkFYF46OT3cjs.isRecord.call(void 0, bag)) return;
|
|
1164
1852
|
const unknown = Object.keys(bag).filter((k) => !valid.includes(k));
|
|
1165
1853
|
if (unknown.length === 0) return;
|
|
1166
1854
|
const suggestions = unknown.map((k) => [k, suggestForKey(k, valid)]).filter((pair) => pair[1] !== null).map(([k, s]) => `"${k}" \u2192 "${s}"`);
|
|
1167
|
-
throw new (0,
|
|
1855
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
1168
1856
|
`invoke: "${record.id}" params bag has unknown key(s) ${unknown.map((k) => `"${k}"`).join(", ")} \u2014 the bag is closed (valid keys: ${valid.join(", ")})`,
|
|
1169
1857
|
{
|
|
1170
1858
|
operation: record.id,
|
|
@@ -1172,17 +1860,36 @@ function validateParamsBag(record, args) {
|
|
|
1172
1860
|
}
|
|
1173
1861
|
);
|
|
1174
1862
|
}
|
|
1175
|
-
|
|
1863
|
+
function validateGetBag(record, args) {
|
|
1864
|
+
if (record.kind !== "primitive" || record.operation !== "get") return;
|
|
1865
|
+
const at = Object.keys(_nullishCoalesce(record.inputSchema, () => ( {}))).indexOf("opts");
|
|
1866
|
+
const bag = at < 0 ? void 0 : _optionalChain([args, 'optionalAccess', _48 => _48[at]]);
|
|
1867
|
+
if (bag === void 0 || bag === null || !_chunkFYF46OT3cjs.isRecord.call(void 0, bag)) return;
|
|
1868
|
+
const unknown = Object.keys(bag).filter((k) => k !== "fresh");
|
|
1869
|
+
if (unknown.length > 0) {
|
|
1870
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
1871
|
+
`invoke: "${record.id}" opts bag has unknown key(s) ${unknown.map((k) => `"${k}"`).join(", ")} \u2014 the bag is closed (valid keys: fresh)`,
|
|
1872
|
+
{ operation: record.id, suggestedAction: "Pass only { fresh?: boolean }." }
|
|
1873
|
+
);
|
|
1874
|
+
}
|
|
1875
|
+
if (bag.fresh !== void 0 && typeof bag.fresh !== "boolean") {
|
|
1876
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
1877
|
+
`invoke: fresh must be a boolean (got ${typeof bag.fresh}) \u2014 "${record.id}" was not issued`,
|
|
1878
|
+
{ operation: record.id, suggestedAction: "Pass fresh: true (bypass the read cache) or omit it." }
|
|
1879
|
+
);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
var INVOKE_OPTION_KEYS = ["dryRun", "confirm", "lastModifiedDate", "maxRetries", "fresh", "mode"];
|
|
1176
1883
|
function validateInvokeOptions(opts, operation) {
|
|
1177
1884
|
if (opts === void 0 || opts === null) return;
|
|
1178
1885
|
const unknown = Object.keys(opts).filter((k) => !INVOKE_OPTION_KEYS.includes(k));
|
|
1179
1886
|
if (unknown.length === 0) return;
|
|
1180
|
-
const suggestions = unknown.map((k) => [k, suggestForKey(k, ["dryRun", "confirm", "lastModifiedDate", "maxRetries"])]).filter((pair) => pair[1] !== null).map(([k, s]) => `"${k}" \u2192 "${s}"`);
|
|
1181
|
-
throw new (0,
|
|
1182
|
-
`invoke: "${operation}" has unknown option key(s) ${unknown.map((k) => `"${k}"`).join(", ")} \u2014 the options set is closed (valid: dryRun, confirm, lastModifiedDate, maxRetries; "mode" is internal to the mode wrappers)`,
|
|
1887
|
+
const suggestions = unknown.map((k) => [k, suggestForKey(k, ["dryRun", "confirm", "lastModifiedDate", "maxRetries", "fresh"])]).filter((pair) => pair[1] !== null).map(([k, s]) => `"${k}" \u2192 "${s}"`);
|
|
1888
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
1889
|
+
`invoke: "${operation}" has unknown option key(s) ${unknown.map((k) => `"${k}"`).join(", ")} \u2014 the options set is closed (valid: dryRun, confirm, lastModifiedDate, maxRetries, fresh; "mode" is internal to the mode wrappers)`,
|
|
1183
1890
|
{
|
|
1184
1891
|
operation,
|
|
1185
|
-
suggestedAction: suggestions.length > 0 ? `Fix the option name (${suggestions.join(", ")}).` : `Pass only dryRun / confirm / lastModifiedDate / maxRetries.`
|
|
1892
|
+
suggestedAction: suggestions.length > 0 ? `Fix the option name (${suggestions.join(", ")}).` : `Pass only dryRun / confirm / lastModifiedDate / maxRetries / fresh.`
|
|
1186
1893
|
}
|
|
1187
1894
|
);
|
|
1188
1895
|
}
|
|
@@ -1191,7 +1898,7 @@ function unknownKeysRefusal(raw, validKeys, subject, operation) {
|
|
|
1191
1898
|
const unknown = Object.keys(raw).filter((k) => !validKeys.includes(k));
|
|
1192
1899
|
if (unknown.length === 0) return null;
|
|
1193
1900
|
const suggestions = unknown.map((k) => [k, suggestForKey(k, [...validKeys])]).filter((pair) => pair[1] !== null).map(([k, s]) => `"${k}" \u2192 "${s}"`);
|
|
1194
|
-
return new (0,
|
|
1901
|
+
return new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
1195
1902
|
`unknown ${subject} ${unknown.map((k) => `"${k}"`).join(", ")} \u2014 the ${subject} set is closed (valid keys: ${validKeys.join(", ")})`,
|
|
1196
1903
|
{
|
|
1197
1904
|
...operation !== void 0 ? { operation } : {},
|
|
@@ -1201,8 +1908,8 @@ function unknownKeysRefusal(raw, validKeys, subject, operation) {
|
|
|
1201
1908
|
}
|
|
1202
1909
|
|
|
1203
1910
|
// src/operations/invoke/registry.ts
|
|
1204
|
-
var RECORDS =
|
|
1205
|
-
var
|
|
1911
|
+
var RECORDS = _chunkHBYKTXN3cjs.CAPABILITIES;
|
|
1912
|
+
var byId2 = new Map(RECORDS.map((r) => [r.id, r]));
|
|
1206
1913
|
|
|
1207
1914
|
// src/operations/invoke/dispatch.ts
|
|
1208
1915
|
var INVOKE_MAX_INLINE_BYTES = 1024 * 1024;
|
|
@@ -1218,9 +1925,9 @@ function bufferResult(buf) {
|
|
|
1218
1925
|
};
|
|
1219
1926
|
}
|
|
1220
1927
|
function resolveRecord(operation) {
|
|
1221
|
-
const record =
|
|
1928
|
+
const record = byId2.get(operation);
|
|
1222
1929
|
if (record === void 0) {
|
|
1223
|
-
throw new (0,
|
|
1930
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
1224
1931
|
`invoke: unknown operation "${operation}" \u2014 it is not in the capability registry`,
|
|
1225
1932
|
{ operation, suggestedAction: "List the registry (the ./capabilities subpath) for the exact operation key." }
|
|
1226
1933
|
);
|
|
@@ -1231,16 +1938,30 @@ function governPreRequest(record, operation, opts) {
|
|
|
1231
1938
|
validateInvokeOptions(opts, operation);
|
|
1232
1939
|
const mode = opts.mode;
|
|
1233
1940
|
if (mode !== void 0 && MODE_BY_EFFECT[record.effect] !== mode) {
|
|
1234
|
-
throw new (0,
|
|
1941
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(crossModeRefusal(operation, record.effect, mode), { operation });
|
|
1235
1942
|
}
|
|
1236
1943
|
if (opts.dryRun !== void 0 && typeof opts.dryRun !== "boolean") {
|
|
1237
|
-
throw new (0,
|
|
1944
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
1238
1945
|
`invoke: dryRun must be a boolean (got ${typeof opts.dryRun}) \u2014 "${operation}" was not issued`,
|
|
1239
1946
|
{ operation, suggestedAction: "Pass dryRun: true (simulate) or dryRun: false (apply) as a real boolean." }
|
|
1240
1947
|
);
|
|
1241
1948
|
}
|
|
1949
|
+
if (opts.fresh !== void 0) {
|
|
1950
|
+
if (typeof opts.fresh !== "boolean") {
|
|
1951
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
1952
|
+
`invoke: fresh must be a boolean (got ${typeof opts.fresh}) \u2014 "${operation}" was not issued`,
|
|
1953
|
+
{ operation, suggestedAction: "Pass fresh: true (bypass the read cache) or omit it." }
|
|
1954
|
+
);
|
|
1955
|
+
}
|
|
1956
|
+
if (record.effect !== "read" || record.kind !== "primitive" || record.operation !== "get" || !("opts" in (_nullishCoalesce(record.inputSchema, () => ( {}))))) {
|
|
1957
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
1958
|
+
`invoke: fresh applies only to a single-record "<resource>.get" read \u2014 "${operation}" was not issued`,
|
|
1959
|
+
{ operation, suggestedAction: "Drop the fresh option (only <resource>.get consults the read cache per call)." }
|
|
1960
|
+
);
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1242
1963
|
if (record.effect === "read" && opts.dryRun === true) {
|
|
1243
|
-
throw new (0,
|
|
1964
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
1244
1965
|
`invoke: "${operation}" is a read \u2014 dryRun is not applicable (reads issue their request as-is)`,
|
|
1245
1966
|
{ operation }
|
|
1246
1967
|
);
|
|
@@ -1257,22 +1978,24 @@ function operationsHolder(client) {
|
|
|
1257
1978
|
return {
|
|
1258
1979
|
resolveAny: (identifier, opts) => resolveAny(client, identifier, opts),
|
|
1259
1980
|
searchAcrossResources: (query, opts) => searchAcrossResources(client, query, opts),
|
|
1260
|
-
fetchMany: (items, opts) =>
|
|
1981
|
+
fetchMany: (items, opts) => _chunkFYF46OT3cjs.fetchMany.call(void 0, client, items, opts),
|
|
1261
1982
|
// Issue #62: the generic aggregate + the cheap count — the
|
|
1262
1983
|
// groupBy/sum-absent mode is ONE vendor /query/count (the S12-C blind
|
|
1263
1984
|
// sentinel rides through); the group/sum modes walk the named
|
|
1264
1985
|
// <resource>.query op via the house bounded walk and fold client-side
|
|
1265
1986
|
// (truncation honesty: hasMore/totalScanned — no silent partial sums).
|
|
1266
1987
|
aggregate: (operation, filter, opts) => aggregate(client, operation, filter, opts),
|
|
1988
|
+
// Issue #234: revenue by dimension in one budgeted call (refuses over budget, never partial).
|
|
1989
|
+
revenueRollup: (opts) => revenueRollup(client, opts),
|
|
1267
1990
|
// Issue #59 PR-B: the bulk fetch by ids — query-first chunked `id in`
|
|
1268
1991
|
// reads + the per-id `get` fallback (blind-surface-safe; per-item
|
|
1269
1992
|
// typed outcomes reuse the fetchMany shapes).
|
|
1270
|
-
fetchByIds: (items, opts) =>
|
|
1993
|
+
fetchByIds: (items, opts) => _chunkFYF46OT3cjs.fetchByIds.call(void 0, client, items, opts),
|
|
1271
1994
|
// Issue #65: opt-in resource name expansion — per-id resources.get
|
|
1272
1995
|
// reads ONLY (never an id-in query — the resources table can be blind
|
|
1273
1996
|
// to count/query while per-id reads work); blind-marked rows for ids
|
|
1274
1997
|
// the API user cannot resolve; the name cache (default-on).
|
|
1275
|
-
expandResourceNames: (ids, opts) =>
|
|
1998
|
+
expandResourceNames: (ids, opts) => _chunkFYF46OT3cjs.expandResourceNames.call(void 0, client, ids, opts),
|
|
1276
1999
|
relationshipMap: (resourceKey) => relationshipMap(client, resourceKey),
|
|
1277
2000
|
// Issue #11: the served blind-surface inventory (zero wire) + the
|
|
1278
2001
|
// bounded caller-opted reachability sweep (read-only, confirmProbe-
|
|
@@ -1282,12 +2005,12 @@ function operationsHolder(client) {
|
|
|
1282
2005
|
// Issue #12: the served zone-fact create-guidance catalog (zero wire).
|
|
1283
2006
|
createGuidance: (opts) => createGuidance(client, opts),
|
|
1284
2007
|
// Issue #202: ExecuteCommand deep links — pure, zero wire.
|
|
1285
|
-
uiLink: (input) =>
|
|
2008
|
+
uiLink: (input) => _chunkFYF46OT3cjs.uiLink.call(void 0, client, input),
|
|
1286
2009
|
// Issue #31: the opt-in live field-metadata channel — N bounded
|
|
1287
2010
|
// entityInformation GETs (the op's own cap refuses over-cap fan-outs
|
|
1288
2011
|
// pre-request; per-resource failures are recorded on the row and
|
|
1289
2012
|
// never abort the call).
|
|
1290
|
-
fieldCatalog: (opts) =>
|
|
2013
|
+
fieldCatalog: (opts) => _chunkFYF46OT3cjs.fieldCatalog.call(void 0, client, opts),
|
|
1291
2014
|
// Issue #9: the one-call recovery/cleanup op — effect write in the
|
|
1292
2015
|
// registry (the governor's dry-run-first applies to it); the op's own
|
|
1293
2016
|
// confirm gate makes the destructive action deliberate (the governor
|
|
@@ -1297,15 +2020,15 @@ function operationsHolder(client) {
|
|
|
1297
2020
|
}
|
|
1298
2021
|
function resolveHolder(client, record, operation) {
|
|
1299
2022
|
const clientBag = client;
|
|
1300
|
-
let holder = clientBag[
|
|
2023
|
+
let holder = clientBag[_chunkFYF46OT3cjs.camelKey.call(void 0, record.resource)];
|
|
1301
2024
|
if (typeof holder === "function") {
|
|
1302
|
-
holder = _nullishCoalesce(clientBag[`${
|
|
2025
|
+
holder = _nullishCoalesce(clientBag[`${_chunkFYF46OT3cjs.camelKey.call(void 0, record.resource)}Resource`], () => ( holder));
|
|
1303
2026
|
}
|
|
1304
2027
|
if (holder === void 0 && record.resource === "operations") {
|
|
1305
2028
|
holder = operationsHolder(client);
|
|
1306
2029
|
}
|
|
1307
2030
|
if (holder === void 0) {
|
|
1308
|
-
throw new (0,
|
|
2031
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
1309
2032
|
`invoke: the client exposes no accessor for resource "${record.resource}"`,
|
|
1310
2033
|
{ operation }
|
|
1311
2034
|
);
|
|
@@ -1349,20 +2072,27 @@ function buildCallArgs(record, args, mutationOpts, isWrite) {
|
|
|
1349
2072
|
}
|
|
1350
2073
|
return callArgs;
|
|
1351
2074
|
}
|
|
2075
|
+
function appendFresh(record, callArgs) {
|
|
2076
|
+
const at = Object.keys(_nullishCoalesce(record.inputSchema, () => ( {}))).indexOf("opts");
|
|
2077
|
+
while (callArgs.length < at) callArgs.push(void 0);
|
|
2078
|
+
callArgs[at] = { fresh: true };
|
|
2079
|
+
return callArgs;
|
|
2080
|
+
}
|
|
1352
2081
|
async function invokeMode(client, operation, argsIn = [], opts = {}) {
|
|
1353
2082
|
const record = resolveRecord(operation);
|
|
1354
2083
|
governPreRequest(record, operation, opts);
|
|
1355
2084
|
const args = resolveDispatchArgs(record, argsIn);
|
|
1356
2085
|
validateDispatchArgs(record, args);
|
|
1357
2086
|
validateParamsBag(record, args);
|
|
2087
|
+
validateGetBag(record, args);
|
|
1358
2088
|
const boundedQuery = record.kind === "primitive" && record.operation === "query";
|
|
1359
2089
|
const countedQuery = record.kind === "primitive" && record.operation === "count";
|
|
1360
2090
|
const holder = resolveHolder(client, record, operation);
|
|
1361
|
-
if (boundedQuery || countedQuery) await
|
|
2091
|
+
if (boundedQuery || countedQuery) await _chunkFYF46OT3cjs.validateQueryFieldNames.call(void 0, record, holder, args);
|
|
1362
2092
|
const methodName = boundedQuery ? "queryBounded" : record.operation;
|
|
1363
2093
|
const method = holder[methodName];
|
|
1364
2094
|
if (typeof method !== "function") {
|
|
1365
|
-
throw new (0,
|
|
2095
|
+
throw new (0, _chunkFYF46OT3cjs.AutotaskConfigError)(
|
|
1366
2096
|
`invoke: "${record.resource}.${methodName}" is not a callable method on the client`,
|
|
1367
2097
|
{ operation }
|
|
1368
2098
|
);
|
|
@@ -1370,6 +2100,7 @@ async function invokeMode(client, operation, argsIn = [], opts = {}) {
|
|
|
1370
2100
|
const isWrite = record.effect !== "read";
|
|
1371
2101
|
const mutationOpts = buildMutationOpts(opts, isWrite);
|
|
1372
2102
|
const callArgs = buildCallArgs(record, args, mutationOpts, isWrite);
|
|
2103
|
+
if (opts.fresh === true) appendFresh(record, callArgs);
|
|
1373
2104
|
const result = await method.apply(holder, callArgs);
|
|
1374
2105
|
return Buffer.isBuffer(result) ? bufferResult(result) : result;
|
|
1375
2106
|
}
|
|
@@ -1439,4 +2170,5 @@ async function invokeDelete(client, operation, args = [], opts = {}) {
|
|
|
1439
2170
|
|
|
1440
2171
|
|
|
1441
2172
|
|
|
1442
|
-
|
|
2173
|
+
|
|
2174
|
+
exports.AGGREGATE_DATE_BUCKETS = AGGREGATE_DATE_BUCKETS; exports.AGGREGATE_DEFAULT_LIMIT = AGGREGATE_DEFAULT_LIMIT; exports.AGGREGATE_DEFAULT_TOP = AGGREGATE_DEFAULT_TOP; exports.AGGREGATE_TOP_MAX = AGGREGATE_TOP_MAX; exports.BLIND_SURFACE_KINDS = BLIND_SURFACE_KINDS; exports.CREATE_BLOCKER_KINDS = CREATE_BLOCKER_KINDS; exports.ENTITY_TYPE_TO_RESOURCE = _chunkFYF46OT3cjs.ENTITY_TYPE_TO_RESOURCE; exports.EXPAND_RESOURCE_NAMES_MAX_IDS = _chunkFYF46OT3cjs.EXPAND_RESOURCE_NAMES_MAX_IDS; exports.FETCH_BY_IDS_CHUNK_SIZE = _chunkFYF46OT3cjs.FETCH_BY_IDS_CHUNK_SIZE; exports.FETCH_BY_IDS_MAX_ITEMS = _chunkFYF46OT3cjs.FETCH_BY_IDS_MAX_ITEMS; exports.FETCH_MANY_MAX_ITEMS = _chunkFYF46OT3cjs.FETCH_MANY_MAX_ITEMS; exports.FIELD_CACHE_KINDS = _chunkFYF46OT3cjs.FIELD_CACHE_KINDS; exports.FIELD_CATALOG_HONESTY_NOTE = _chunkFYF46OT3cjs.FIELD_CATALOG_HONESTY_NOTE; exports.INVOKE_OPTION_KEYS = INVOKE_OPTION_KEYS; exports.MAX_FIELD_CATALOG_WIRE = _chunkFYF46OT3cjs.MAX_FIELD_CATALOG_WIRE; exports.PARAMS_BAG_KEYS = PARAMS_BAG_KEYS; exports.PICKLIST_VALUE_NOT_SERVED_NOTE = _chunkFYF46OT3cjs.PICKLIST_VALUE_NOT_SERVED_NOTE; exports.RECOVERY_CONFIRM = RECOVERY_CONFIRM; exports.RECOVERY_DEFAULT_LIMIT = RECOVERY_DEFAULT_LIMIT; exports.RECOVERY_MAX_LIMIT = RECOVERY_MAX_LIMIT; exports.UI_LINK_ENTITIES = _chunkFYF46OT3cjs.UI_LINK_ENTITIES; exports.WORKFLOW_RESOURCES = _chunkFYF46OT3cjs.WORKFLOW_RESOURCES; exports.aggregate = aggregate; exports.blindSurfaces = blindSurfaces; exports.buildUiLink = _chunkFYF46OT3cjs.buildUiLink; exports.createGuidance = createGuidance; exports.dateBucketLabel = dateBucketLabel; exports.effectiveRequiredFields = _chunkFYF46OT3cjs.effectiveRequiredFields; exports.expandResourceNames = _chunkFYF46OT3cjs.expandResourceNames; exports.fetchByIds = _chunkFYF46OT3cjs.fetchByIds; exports.fetchMany = _chunkFYF46OT3cjs.fetchMany; exports.fieldCatalog = _chunkFYF46OT3cjs.fieldCatalog; exports.foldFlattenedOpts = foldFlattenedOpts; exports.invoke = invoke; exports.invokeDelete = invokeDelete; exports.invokeRead = invokeRead; exports.invokeWrite = invokeWrite; exports.isEnumerableResource = _chunkFYF46OT3cjs.isEnumerableResource; exports.needsConfirmation = needsConfirmation; exports.probeReachability = probeReachability; exports.recovery = recovery; exports.relationshipMap = relationshipMap; exports.resolveAny = resolveAny; exports.resourceForEntityType = _chunkFYF46OT3cjs.resourceForEntityType; exports.revenueRollup = revenueRollup; exports.searchAcrossResources = searchAcrossResources; exports.uiLink = _chunkFYF46OT3cjs.uiLink; exports.unknownKeysRefusal = unknownKeysRefusal; exports.validateDispatchArgs = validateDispatchArgs; exports.validateFetchManyItems = _chunkFYF46OT3cjs.validateFetchManyItems; exports.validateInvokeOptions = validateInvokeOptions; exports.validateParamsBag = validateParamsBag;
|