node-autotask 0.11.0 → 0.12.1
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/MCP_TOOL_CATALOG.json +26 -8
- package/MCP_TOOL_MANIFEST.md +12 -11
- package/N8N_MCP_MANIFEST.json +3 -3
- package/N8N_MCP_MANIFEST.md +4 -4
- package/README.md +1 -1
- package/capabilities.json +1352 -1233
- package/dist/capabilities.cjs +2 -2
- package/dist/capabilities.js +1 -1
- package/dist/{chunk-QAX222R6.cjs → chunk-36PG2IU2.cjs} +1647 -394
- package/dist/{chunk-T5FLSJOD.cjs → chunk-CSRCMBZ2.cjs} +1351 -1232
- package/dist/{chunk-YKEX4SK7.cjs → chunk-IHRXVIUU.cjs} +519 -39
- package/dist/{chunk-7L23FDQW.js → chunk-NACI2NDF.js} +1351 -1232
- package/dist/{chunk-CJONZQ7E.js → chunk-P35GFG2H.js} +1259 -6
- package/dist/{chunk-25ZUKHS4.cjs → chunk-PGQIGE63.cjs} +135 -135
- package/dist/{chunk-SXHPUQMB.js → chunk-PPTAMCFA.js} +2 -2
- package/dist/{chunk-LNRN45XS.js → chunk-ZOA5HUFX.js} +498 -18
- package/dist/{index-B_xToSpq.d.ts → index-DZfd9Gt7.d.cts} +251 -2
- package/dist/{index-Dv_GSZaN.d.cts → index-UilX3PFL.d.ts} +251 -2
- package/dist/index.cjs +20 -20
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +5 -5
- package/dist/operations/index.cjs +123 -588
- package/dist/operations/index.d.cts +4 -4
- package/dist/operations/index.d.ts +4 -4
- package/dist/operations/index.js +10 -475
- package/dist/pool.cjs +8 -8
- package/dist/pool.d.cts +2 -2
- package/dist/pool.d.ts +2 -2
- package/dist/pool.js +4 -4
- package/dist/resources/index.cjs +4 -4
- package/dist/resources/index.d.cts +2 -2
- package/dist/resources/index.d.ts +2 -2
- package/dist/resources/index.js +3 -3
- package/dist/types/index.d.cts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/docs/API.md +301 -2
- package/docs/N8N-ADAPTER.md +1 -1
- package/package.json +1 -1
- package/dist/{index-cQ6CRexR.d.cts → index-BXtKN1M5.d.cts} +201 -201
- package/dist/{index-cQ6CRexR.d.ts → index-BXtKN1M5.d.ts} +201 -201
package/docs/API.md
CHANGED
|
@@ -847,7 +847,7 @@ be cross-mode-refused.
|
|
|
847
847
|
whose MCP annotation is `readOnlyHint !== true` — today exactly **254
|
|
848
848
|
tools**: the 252 dedicated write/delete tools (198 `effect=write` + 54
|
|
849
849
|
`effect=destructive`, every one of them has a dedicated tool) +
|
|
850
|
-
`autotask_write` + `autotask_delete`. The remaining **
|
|
850
|
+
`autotask_write` + `autotask_delete`. The remaining **601 tools** (the 588
|
|
851
851
|
dedicated read tools + the 10 core read tools + `autotask_read` +
|
|
852
852
|
`autotask_catalog` + `autotask_describe`) all carry `readOnlyHint: true`
|
|
853
853
|
and are safe ungated: every op they can route is `effect=read` (zero
|
|
@@ -1145,7 +1145,7 @@ Standard methods (every resource, `BaseResource`): `query` (stream, 1000-page gu
|
|
|
1145
1145
|
| `contract_service_units` | ContractServiceUnit | entity | extended | yes (contracts) | query, count, get, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_contractServiceUnit_get |
|
|
1146
1146
|
| `contract_services` | ContractService | entity | extended | yes (contracts) | query, count, get, create, update, resolve | createIfNotExists, getEntityInfo, getFieldInfo, getUdfInfo | autotask_contractService_get |
|
|
1147
1147
|
| `contract_ticket_purchases` | ContractTicketPurchases | entity | extended | yes (contracts) | query, count, get, create, update, resolve | getEntityInfo, getFieldInfo, getUdfInfo | autotask_contractTicketPurchase_get |
|
|
1148
|
-
| `contracts` | Contract | entity | extended | no | query, count, get, create, update, resolve, search | createIfNotExists, getEntityInfo, getFieldInfo, getUdfInfo | autotask_contract_get |
|
|
1148
|
+
| `contracts` | Contract | entity | extended | no | query, count, get, create, update, resolve, search | createIfNotExists, getEntityInfo, getFieldInfo, getUdfInfo, profitability | autotask_contract_get |
|
|
1149
1149
|
| `countries` | Country | entity | advanced | no | query, count, get, update, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_country_get |
|
|
1150
1150
|
| `departments` | Department | entity | extended | no | query, count, get, create, update, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_department_get |
|
|
1151
1151
|
| `domain_registrars` | DomainRegistrar | entity | extended | no | query, count, get, create, update, resolve, search | getEntityInfo, getFieldInfo, getUdfInfo | autotask_DomainRegistrar_get |
|
|
@@ -2601,6 +2601,305 @@ resource's query surface is pinned blind), and `next` (concrete next steps; the
|
|
|
2601
2601
|
remedy names the authority op). Hit paths carry no `miss` key. Thrown outcomes
|
|
2602
2602
|
(`NOT_FOUND` by numeric id, `RESOLUTION_AMBIGUOUS`, `RESOLUTION_TRUNCATED`) are unchanged.
|
|
2603
2603
|
|
|
2604
|
+
### 11.26 The contract P&L composite — `contracts.profitability` (issue #100)
|
|
2605
|
+
|
|
2606
|
+
Issue #100 adds the contract profit-and-loss composite op (an additive read;
|
|
2607
|
+
the plan change — a new registry op — is a minor at publish). One call
|
|
2608
|
+
returns the contract's reconciled P&L over a date window:
|
|
2609
|
+
|
|
2610
|
+
- **`contracts.profitability(contractId, opts)` — the closed opts bag.**
|
|
2611
|
+
`dateFrom`/`dateTo` are required (`YYYY-MM-DD`, `dateTo >= dateFrom`) and
|
|
2612
|
+
the window is capped at 36 calendar months (the per-month windowing budget
|
|
2613
|
+
stays bounded; OQ1). `labourCostMethod` (default
|
|
2614
|
+
`billing_item_our_cost`; the closed 4-set
|
|
2615
|
+
`billing_item_our_cost` / `contract_role_cost` / `resource_internal_cost` /
|
|
2616
|
+
`provided`) selects the START rung of the cost-rate ladder;
|
|
2617
|
+
`labourRatePerHour` (finite `>= 0`) is the `provided` catch-all rung and is
|
|
2618
|
+
REQUIRED when `labourCostMethod` is `provided`. `includeRecurringCosts` /
|
|
2619
|
+
`includeUnbilledRevenue` (default true) gate the charges/units folds and
|
|
2620
|
+
the expected-revenue basis; `externalMonthlyCosts` (finite `>= 0`, indexed
|
|
2621
|
+
by month from `dateFrom`, at most one entry per window month) is the
|
|
2622
|
+
caller-supplied non-vendor tooling cost, surfaced in
|
|
2623
|
+
`tooling_cost.caller_supplied` (stamped non-vendor — never folded into the
|
|
2624
|
+
vendor figure); `groupByActivity` (default true) gates the
|
|
2625
|
+
`ticketCategory` activity fold; `staleAfterDays` (default 30, integer `>=
|
|
2626
|
+
1`) is the open-stale horizon; `maxTicketsForJoin` (default 500, integer
|
|
2627
|
+
1..500) caps the ticket join set fed to the expenses + miscoding probe
|
|
2628
|
+
(sorted, first N, the `N of M` note) and the time-referenced ticket ids;
|
|
2629
|
+
`limit` (default 50, clamped 500) is
|
|
2630
|
+
every part's `MaxRecords` + the capped lists. Validation collects the
|
|
2631
|
+
COMPLETE problem list and throws ONE typed `CONFIG_ERROR` pre-request —
|
|
2632
|
+
zero wire on any malformed bag (the refusal table is house style: every
|
|
2633
|
+
message names the key + the valid values).
|
|
2634
|
+
- **Wire plan: ≤21 bounded reads, zero mutating, all pre-existing ops.**
|
|
2635
|
+
Issue order: contract `get` → customer `GET /V1.0/Companies/{companyID}` →
|
|
2636
|
+
the Contracts `entityInformation/fields` read (the `contractType` LABEL —
|
|
2637
|
+
verified on the ww29 sandbox 2026-10-16: 1 Time & Materials, 3 Fixed
|
|
2638
|
+
Price, 4 Block Hours, 6 Retainer, 7 Recurring Service, 8 Per Ticket, 9
|
|
2639
|
+
Umbrella; the folds still branch on `/block\s*hours?/i` and
|
|
2640
|
+
`/time.*materials?/i` over the label, never an id; `contractCategory` is
|
|
2641
|
+
ignored — all its values are inactive; an absent/0 type skips the read; an absent type, an
|
|
2642
|
+
unlisted id or a catalog fault is `CONTRACT_TYPE_UNKNOWN`, surfaced in
|
|
2643
|
+
`contract_type { id, label, absent? }`) → `ContractServices` / `ContractServiceUnits` (windowed on the period
|
|
2644
|
+
`startDate`) / `ContractCharges` / `ContractRoleCosts` / `ContractRates`
|
|
2645
|
+
queries → `ContractBlocks` query (Block Hours contracts only, by label;
|
|
2646
|
+
skipped otherwise) → `BillingItems` query → invoices
|
|
2647
|
+
(fetch-by-ids on the distinct billed `invoiceID`s; skipped on none) →
|
|
2648
|
+
`TimeEntries` query → `Tickets` activity query (`or[ and[contractID,
|
|
2649
|
+
createDate in window], id in <the ticket ids the in-window time
|
|
2650
|
+
references> ]` — an older or closed ticket carrying this period's time is
|
|
2651
|
+
fetched, never reported as unresolvable) → the tickets
|
|
2652
|
+
`entityInformation/fields` metadata read (the open-status picklist) → open
|
|
2653
|
+
`Tickets` query (`status in` the open set) → resources (fetch-by-ids on the
|
|
2654
|
+
distinct time-row `resourceID`s; skipped on none) → `ExpenseItems` query +
|
|
2655
|
+
the ONE extra bounded miscoding probe (`TimeEntries` with `ticketID in
|
|
2656
|
+
join` + `or[contractID noteq, contractID notExist]`; the join is the
|
|
2657
|
+
fetched tickets whose record says THIS contract; skipped on an empty set)
|
|
2658
|
+
→ `ConfigurationItems` query (`contractID` = the CIs this contract covers,
|
|
2659
|
+
never the whole company; default-on) → `Subscriptions` query (CI join;
|
|
2660
|
+
skipped on no CIs) → the Subscriptions `entityInformation/fields` read
|
|
2661
|
+
(the `status` + `periodType` labels; only when subscriptions came back).
|
|
2662
|
+
Every window filter is `gte dateFrom` + `lt` the day
|
|
2663
|
+
after `dateTo` (a datetime field keeps the last day's later rows). All
|
|
2664
|
+
windows and month buckets are UTC days: date-only fields (`itemDate`,
|
|
2665
|
+
units `startDate`/`endDate`) come back as UTC midnight (`Z`), datetimes
|
|
2666
|
+
(ticket `createDate`) as UTC instants, and bucketing slices the ISO
|
|
2667
|
+
string — in a UTC+10/11 tenant a ticket created late local time buckets
|
|
2668
|
+
one UTC day (possibly one month) earlier than its local date. The
|
|
2669
|
+
`time_entries.getPosted` / `getUnposted` endpoints are NEVER called (the
|
|
2670
|
+
pinned vendor 500 on AU03); posted/unposted derives from the
|
|
2671
|
+
`billingItems.timeEntryID` join (unposted = no billing item exists yet).
|
|
2672
|
+
The structural exclusions (`/Services/`, `/ExpenseReports/`,
|
|
2673
|
+
`/BillingCodes/` list reads, getPosted/getUnposted) are pinned by the
|
|
2674
|
+
recorded-request test. Baseline = 12 (a resolvable contract); the
|
|
2675
|
+
typical busy contract = 18; the full fixture = 20 (21 on a Block Hours
|
|
2676
|
+
contract). `wires: { issued, requests, budget: 21, note? }` rides the
|
|
2677
|
+
result as the honesty stamp: `issued` counts logical part reads (a
|
|
2678
|
+
fetch-by-ids part counts once), `requests` the raw HTTP requests (its
|
|
2679
|
+
chunk queries + per-id gets counted individually; a read-cache-served
|
|
2680
|
+
contract `get` adds no request).
|
|
2681
|
+
- **Per-month windowing (parts `billingItems` + `timeEntries` +
|
|
2682
|
+
`contractServiceUnits`).** One
|
|
2683
|
+
full-window page (`MaxRecords: limit`); when it comes back FULL with a
|
|
2684
|
+
live next cursor, the part re-queries PER CALENDAR MONTH intersecting the
|
|
2685
|
+
window (one page per month, the date field bounded to that month, in
|
|
2686
|
+
month order; rows deduped by `id`). A still-full month stamps
|
|
2687
|
+
`hasMore`/`capped` + the truncation note (`month YYYY-MM hit the limit of
|
|
2688
|
+
N — figures cover the returned rows only; widen with a narrower window or
|
|
2689
|
+
higher limit`) — NO cursor walk (the house single-page rule; the composite
|
|
2690
|
+
owns its own windowing so per-month figures stay correct by construction).
|
|
2691
|
+
Every expansion read counts toward `wires.issued` (the note says why);
|
|
2692
|
+
the 36-month cap bounds the worst case. A still-full month is also named
|
|
2693
|
+
in that month's `coverage_gaps` (`<part>: truncated at limit N`).
|
|
2694
|
+
- **The cost-rate ladder (per time entry, zero extra wire).** Rung 1
|
|
2695
|
+
`billing_item.ourCost`: a LINE cost, not a rate — Σ the finite `>= 0`
|
|
2696
|
+
`ourCost` over the entry's billed lines (partial billing across months
|
|
2697
|
+
sums naturally; an entry whose billed lines carry NO finite `ourCost`
|
|
2698
|
+
falls THROUGH — the rung is unusable, not zero). Rung 2
|
|
2699
|
+
`contract_role_costs.rate`: the `resourceID:roleID` row is preferred over
|
|
2700
|
+
the role-only row (`matched: 'resource+role' | 'role'`); `rate × hours`.
|
|
2701
|
+
Rung 3 `resource.internalCost`: the fetch-by-ids GET-fallback rows only
|
|
2702
|
+
(a `NOT_FOUND` miss makes the rung unavailable for that resource — the
|
|
2703
|
+
ww29 resources list-scoping is seen through by get-by-id, live-verified
|
|
2704
|
+
2026-10-15; the quirk is cited in the part note when the chunk queries
|
|
2705
|
+
0-rowed and the gets resolved). Rung 4 `provided`: `labourRatePerHour ×
|
|
2706
|
+
hours` (in the ladder only when the rate is finite). `hours(entry)` =
|
|
2707
|
+
`hoursWorked` when finite and `> 0`, else 0. A row resolving to
|
|
2708
|
+
`missing` contributes 0 to `labour_cost.total` but is audited:
|
|
2709
|
+
`costRateResolution.sources` (per-rung `entries`/`hours`/`cost`, ALWAYS
|
|
2710
|
+
including `missing`), `unresolvedHours` (= the `missing` hours — the
|
|
2711
|
+
lower-bound honesty number; `total_cost` is a LOWER BOUND exactly when it
|
|
2712
|
+
is `> 0`), and the pinned note lines (the `N hours unresolved (missing
|
|
2713
|
+
rate) — total_cost is a lower bound` line; the per-resource NOT_FOUND
|
|
2714
|
+
line; the no-billing-item line). NEVER a silent zero. `contract_rates`
|
|
2715
|
+
is NOT a ladder rung (the customer billing rate — the wrong side of the
|
|
2716
|
+
P&L).
|
|
2717
|
+
- **Monthly P&L (one row per calendar month in the window).**
|
|
2718
|
+
`revenue_invoiced` (Σ the line revenue of the month's items on
|
|
2719
|
+
non-voided invoices; line revenue = `internalCurrencyExtendedPrice`,
|
|
2720
|
+
falling back to `extendedPrice`) · `revenue_by_type { recurring, labour,
|
|
2721
|
+
charges, expenses, other }` (the invoiced split, keyed on the verified
|
|
2722
|
+
`billingItemType` picklist: 1 Labor / 2 Labor Adjustment → labour, 3 Cost
|
|
2723
|
+
→ charges, 4 Expense → expenses, 5 Subscription → other, 6 Recurring
|
|
2724
|
+
Service/Bundle → recurring, 7 Recurring Contract Setup Fee → charges, 8
|
|
2725
|
+
Milestone → other; an absent/unknown type falls back to the line's FKs in
|
|
2726
|
+
the order `timeEntryID` → labour, `expenseItemID` → expenses, a charge FK
|
|
2727
|
+
→ charges, a contract service/bundle/period/adjustment FK → recurring —
|
|
2728
|
+
labour billed against a service carries `contractServiceID` too, verified)
|
|
2729
|
+
·
|
|
2730
|
+
`revenue_voided_excluded` (Σ on voided invoices) ·
|
|
2731
|
+
`revenue_posted_uninvoiced` (Σ on posted items with no invoice yet) ·
|
|
2732
|
+
`revenue_unresolved_invoice` (Σ on items whose invoice could not be read
|
|
2733
|
+
or was beyond the join cap — not recognized revenue, never silent: the
|
|
2734
|
+
month's `coverage_gaps` names `invoices: N line(s) on unresolved
|
|
2735
|
+
invoices`) · `revenue_expected` (Σ the period `internalCurrencyPrice`
|
|
2736
|
+
(fallback `price`) of the service units whose
|
|
2737
|
+
period STARTS in the month — a multi-month period is counted once, when
|
|
2738
|
+
it bills; `null` when the services/units basis is absent or the units
|
|
2739
|
+
part is `absent` — unknown, not 0. Unit `price`/`cost` are PERIOD totals
|
|
2740
|
+
(units × per-unit, prorated for a partial period — verified: the type-6
|
|
2741
|
+
recurring billing item has `itemDate` = the unit `startDate` and
|
|
2742
|
+
`extendedPrice` = the unit `price`). Units are normally one month but can
|
|
2743
|
+
span a year; the start-month attribution matches billing, so the monthly
|
|
2744
|
+
figures are a billing-period (cash) view, NOT accrual — an annual unit
|
|
2745
|
+
lands in its start month in one lump) ·
|
|
2746
|
+
`revenue_expected_unbilled = max(0, expected − the month's invoiced
|
|
2747
|
+
SERVICE lines)` (labour and charges never mask a missed recurring
|
|
2748
|
+
invoice) · `unposted_time { entries, hours, value, absent? }` (billable
|
|
2749
|
+
time with no billing item yet — derived from the billing join, never
|
|
2750
|
+
getPosted/getUnposted; `value` = Σ `hoursToBill ×
|
|
2751
|
+
internalCurrencyContractHourlyRate` (fallback `contractHourlyRate`) on
|
|
2752
|
+
Time & Materials contracts only, else `null` + `absent:
|
|
2753
|
+
'NOT_TIME_AND_MATERIALS_CONTRACT'`, `'CONTRACT_TYPE_UNKNOWN'`, or
|
|
2754
|
+
`'NO_CONTRACT_RATE'` when a role has no contract rate) · `labour_hours`
|
|
2755
|
+
(Σ `hoursWorked` over positive entries — the same rule as the cost) ·
|
|
2756
|
+
`labour_cost { total, unresolvedHours, rateSources }` (the per-month
|
|
2757
|
+
mirror of the ladder audit) · `tooling_cost { vendor, caller_supplied }`
|
|
2758
|
+
(vendor = Σ `periodCost × the periodType monthly share` — by the tenant
|
|
2759
|
+
`periodType` LABEL (monthly 1, quarterly 1/3, semi-annual 1/6,
|
|
2760
|
+
annual/yearly 1/12); a One-Time subscription is never prorated: its
|
|
2761
|
+
`periodCost` is booked whole in its `effectiveDate` month; when the
|
|
2762
|
+
Subscriptions catalog read fails, the verified id maps apply (periodType
|
|
2763
|
+
1 One-Time, 2 Monthly, 3 Quarterly, 4 Semi-Annual, 5 Yearly; status 1
|
|
2764
|
+
Active, 2 Cancelled, 3 Inactive) and the note says so; an unrecognised
|
|
2765
|
+
periodType is excluded and named; only Active subscriptions count —
|
|
2766
|
+
Cancelled and Inactive are excluded — over the subscriptions on this
|
|
2767
|
+
contract's CIs active in the month, REPORTED ONLY:
|
|
2768
|
+
never folded into `total_cost`, since subscriptions bill outside the
|
|
2769
|
+
contract; `null` when the subscriptions part is skipped/absent;
|
|
2770
|
+
caller-supplied = the bag value, `0` when absent) · `recurring_cost`
|
|
2771
|
+
(charge COST — `extendedCost`, falling back to `unitCost × unitQuantity`
|
|
2772
|
+
when it is absent or 0 while a unit cost exists; never `billableAmount` —
|
|
2773
|
+
plus the `cost` of the units starting in the month, when included) ·
|
|
2774
|
+
`expense_cost` (Σ `internalCurrencyExpenseAmount`) · `total_cost` (labour
|
|
2775
|
+
+ caller-supplied tooling + recurring + expenses) · `profit =
|
|
2776
|
+
revenue_invoiced − total_cost` · `margin = profit / revenue_invoiced`
|
|
2777
|
+
when `> 0`, else `null` (the division is refused, not zeroed) ·
|
|
2778
|
+
`break_even_hours` (the labour hours the invoiced revenue absorbs at the
|
|
2779
|
+
average resolved cost rate before profit hits zero: `(revenue_invoiced −
|
|
2780
|
+
non-labour cost) / (Σcost / Σhours over resolved rows)`; `0` when the
|
|
2781
|
+
non-labour cost already exceeds revenue; `null` with no resolved hours or
|
|
2782
|
+
a zero rate — including an empty month) · `coverage_gaps` (why the
|
|
2783
|
+
month's figures may be incomplete: `<part>: absent (<code>)` /
|
|
2784
|
+
`truncated at limit N` / `capped at limit N` over units, charges, role
|
|
2785
|
+
costs, billing, time, expenses and subscriptions, plus the unresolved
|
|
2786
|
+
invoice lines; `[]` = complete). The P&L is single-currency in the
|
|
2787
|
+
INTERNAL currency (revenue and costs alike); money fields are emitted
|
|
2788
|
+
rounded to cents.
|
|
2789
|
+
- **The 11 data-quality checks (always present, order-stable, `count: 0` =
|
|
2790
|
+
a passed check).** Typed `{ code, count, sample_ids }` warnings
|
|
2791
|
+
(`count` = the full in-memory/fetched-set count, never capped;
|
|
2792
|
+
`sample_ids` = first `limit`, deduped + sorted): `miscoded_time_out` (the
|
|
2793
|
+
ONE extra bounded probe — time worked on THIS contract's tickets coded to
|
|
2794
|
+
another contract or to none) · `cross_contract_ticket_time` (a fetched
|
|
2795
|
+
ticket says it belongs elsewhere) · `duplicate_entries` (exact
|
|
2796
|
+
`resourceID+dateWorked+ticketID+hoursWorked` tuple; `count` = all copies,
|
|
2797
|
+
samples = copies beyond the lowest `id`) · `zero_negative_hours` ·
|
|
2798
|
+
`time_outside_ticket_period` (`dateWorked < ticket.createDate` or after
|
|
2799
|
+
the earlier of `resolvedDateTime`/`completedDate` — a fetched ticket's
|
|
2800
|
+
whole post-resolution life, not just the first late entry) ·
|
|
2801
|
+
`missing_cost_rate` (the ladder's `missing` rows; the resource NOT_FOUND
|
|
2802
|
+
misses ride the note) · `recurring_cost_drift` (month-over-month beyond
|
|
2803
|
+
the 0.005 float dust; samples the charge/unit ids whose monthly
|
|
2804
|
+
contribution changed) · `expected_unbilled_revenue` (expected > the
|
|
2805
|
+
invoiced service lines + 0.005; the pure "expected with nothing billed"
|
|
2806
|
+
case carries the EMPTY sample — that is the assertion) ·
|
|
2807
|
+
`cross_contract_tickets` (the
|
|
2808
|
+
ticket-side drift + the entry-side unresolvable set) ·
|
|
2809
|
+
`open_stale_tickets` (this contract's tickets; `now − lastActivityDate >
|
|
2810
|
+
staleAfterDays`, `now` =
|
|
2811
|
+
the run's wall clock stamped `period.asOf`; absent `lastActivityDate` is
|
|
2812
|
+
unknown, not zero) · `estimate_vs_actual` (both estimates prorated to the
|
|
2813
|
+
window — `estimate × overlapDays / termDays` over the contract term — and
|
|
2814
|
+
flagged at `> 10%` deviation; samples `[contractId]`). 10 of 11 are pure
|
|
2815
|
+
in-memory folds (zero extra wire).
|
|
2816
|
+
- **The response-side folds.** `activity_categories` (by `ticketCategory`:
|
|
2817
|
+
hours, cost = labour + that category's expenses, ticketCount, entryCount,
|
|
2818
|
+
sample ticket numbers) · `highest_cost_tickets` (cost desc, capped at
|
|
2819
|
+
`limit`; a missing-rate ticket prices 0 and is flagged via the audit —
|
|
2820
|
+
never silently dropped) · `possible_miscoding` (the probe rows + the
|
|
2821
|
+
confirmed cross-contract entries, closed reason set
|
|
2822
|
+
`no_contract_on_entry` / `ticket_of_other_contract` /
|
|
2823
|
+
`ticket_not_in_fetched_set`) · `open_renewal_items` (this contract's open
|
|
2824
|
+
tickets with the stale-days detail, subscription expirations with the
|
|
2825
|
+
prorated monthly cost, the contract end, one `included_hours_burn` row
|
|
2826
|
+
per purchased hour block) · `included_hours` (Block Hours contracts only:
|
|
2827
|
+
`{ value: { purchased, approved, remaining } }` from the `ContractBlocks`
|
|
2828
|
+
`hours` vs the vendor `hoursApproved`; any other contract type is `{
|
|
2829
|
+
value: null, absent: 'NOT_BLOCK_HOURS_CONTRACT' }`, an unresolvable type
|
|
2830
|
+
`'CONTRACT_TYPE_UNKNOWN'` — service units are a
|
|
2831
|
+
quantity, never read as hours, and retainers are dollar-denominated).
|
|
2832
|
+
The probe rows are EXCLUDED from the P&L folds (diagnostic — time not
|
|
2833
|
+
coded to this contract).
|
|
2834
|
+
- **Degradation (per part typed `ok` / `blind` / `skipped` / `absent`; the
|
|
2835
|
+
op NEVER fails the call on a degraded part).** Only the contract read is
|
|
2836
|
+
fatal (the root: `NOT_FOUND` — or any other fault on it — propagates).
|
|
2837
|
+
A vendor fault on any bounded list part degrades that part to `status:
|
|
2838
|
+
'absent'` + `absent: <the typed error code>` + the note (never a true
|
|
2839
|
+
zero — no `emptyReason`), and every monthly row it feeds names it in
|
|
2840
|
+
`coverage_gaps`; a non-404 fault on the customer read degrades to
|
|
2841
|
+
`customer: null` + `parts.customer: 'absent'` + `customerAbsent: <code>`.
|
|
2842
|
+
Every tickets call is
|
|
2843
|
+
`contractID`-filtered, so the pinned `unfiltered-blind` tickets class
|
|
2844
|
+
never stamps a filtered empty (a filtered 0 is a TRUE zero with the
|
|
2845
|
+
honest note); the 0-row parts carry the pinned
|
|
2846
|
+
`tenant-empty-for-user` `emptyReason` stamp where attached. A dangling
|
|
2847
|
+
customer FK (`companies.get` 404) degrades to `customer: null` +
|
|
2848
|
+
`parts.customer: 'skipped'` — NOT an error (the CIs read still fires on
|
|
2849
|
+
the contract row's `companyID`). A field-catalog fault degrades the open
|
|
2850
|
+
pass to a typed skip (the fault read still counts in `wires.issued`).
|
|
2851
|
+
fetch-by-ids per-item faults (invoices/resources) are typed partial
|
|
2852
|
+
misses named in the part note — a miss never aborts the rest. An empty
|
|
2853
|
+
tenant (the ww29 shape) degrades EVERY part — 13 wires, all-zero monthly
|
|
2854
|
+
rows with the divisions refused (`margin: null`, `break_even_hours:
|
|
2855
|
+
null`, `tooling_cost.vendor: null`, `revenue_expected: null`), all 11
|
|
2856
|
+
warnings at `count: 0`, zero tool failure.
|
|
2857
|
+
- **0.12.1 corrections.** The 0.12.0 folds counted the charge
|
|
2858
|
+
`billableAmount` (sell price) as recurring cost, read service `units` as
|
|
2859
|
+
included hours, joined every company CI's subscriptions and added a
|
|
2860
|
+
yearly `periodCost` every month, compared the expected recurring revenue
|
|
2861
|
+
against all billed lines (labour included) and priced it at today's
|
|
2862
|
+
`unitPrice`, read service units unwindowed, reported time on older
|
|
2863
|
+
tickets as unresolvable, defined break-even without revenue, folded
|
|
2864
|
+
expenses in their own currency, and threw on any bounded-part fault. The
|
|
2865
|
+
bullets above are the corrected semantics.
|
|
2866
|
+
- **Registration surface.** The plan row (`capabilities.plan.json` →
|
|
2867
|
+
re-derived hash) carries `kind: 'helper'`, `helperBasis: 'composite'`,
|
|
2868
|
+
`effect: 'read'`, `dryRun: false`, the closed-bag `inputSchema` (the
|
|
2869
|
+
signature-audited arg map in `scripts/lib/args-derive.mjs`); the emitted
|
|
2870
|
+
method delegates to the hand-written special module
|
|
2871
|
+
(`src/resources/special/contracts.ts`). The MCP projection serves
|
|
2872
|
+
`autotask_contract_profitability` (catalog + manifest); the N8N
|
|
2873
|
+
manifests legitimately EXCLUDE the op — `contracts` is not an n8n
|
|
2874
|
+
curated entity (the curation rule is evidence-based: the daily support
|
|
2875
|
+
subset is ticket/contact/company/resource/time_entry only).
|
|
2876
|
+
- **Test pins.** `test/resources/contracts.profitability.test.ts`: the
|
|
2877
|
+
hand-computed 2-month synthetic fixture (every figure derived from the
|
|
2878
|
+
fixture rows; `wires.issued = 18` with the exact queue; every wire pinned
|
|
2879
|
+
in issue order; the structural exclusions + zero-mutating recorded paths;
|
|
2880
|
+
the ladder audit incl. the per-month `rateSources`; the 11 checks with
|
|
2881
|
+
exact counts/samples; the activity/highest-cost/miscoding/renewal folds;
|
|
2882
|
+
the per-month windowing (exact extra month queries, id-dedup, the
|
|
2883
|
+
still-full month note; `wires.issued` counts the expansion); the
|
|
2884
|
+
join-set/`limit` caps; the malformed-bag refusal table (one complete
|
|
2885
|
+
problem list, zero wire); the contract NOT_FOUND; the 0.12.1 pins: the
|
|
2886
|
+
charge cost/period-type/line-type classifiers, the Block Hours
|
|
2887
|
+
`contract_blocks` path, T&M unposted pricing + the time-referenced old
|
|
2888
|
+
ticket, the exclusive window ends, the month-level `coverage_gaps`).
|
|
2889
|
+
`test/resources/profitability-degradation.test.ts`: the empty-tenant
|
|
2890
|
+
13-wire suite (every part degrades, zero tool failure), the dangling
|
|
2891
|
+
customer FK, the non-404 customer fault (`absent`), a bounded-part fault
|
|
2892
|
+
(`absent` + `coverage_gaps`, the call resolves), the resources blind-list get-fallback (rung 3 from the
|
|
2893
|
+
gets, the quirk note), the per-item invoice fault (retries off — the
|
|
2894
|
+
fault semantics are the pin, not the transport's 5xx budget), the
|
|
2895
|
+
fieldCatalog-fault open-pass skip, the status-helper sentinel
|
|
2896
|
+
portability (the `unfiltered-blind` predicate stamps the unfiltered call
|
|
2897
|
+
only), and the §6.4 rate-ladder branch table over synthetic entry sets
|
|
2898
|
+
(rung-1 line-cost sum, rung-1 fall-through, resource+role preference,
|
|
2899
|
+
role-only, rung-3 get-fallback, the resource miss, the provided
|
|
2900
|
+
catch-all/start, the provided-without-rate and fully-missing `missing`
|
|
2901
|
+
— never a silent zero — and `internalCost: 0` as a RESOLVED zero).
|
|
2902
|
+
|
|
2604
2903
|
## 12. Untrusted-content projection (issue #1)
|
|
2605
2904
|
|
|
2606
2905
|
The agent/MCP projection marks EXTERNALLY-SOURCED free text as untrusted,
|
package/docs/N8N-ADAPTER.md
CHANGED
|
@@ -82,7 +82,7 @@ Two sources in, markdown table out: the node repo (the ops column is re-derived
|
|
|
82
82
|
| `contractServiceUnits` | ContractServiceUnit | yes | get, getMany, count | — | count, get, getEntityInfo, getFieldInfo, getUdfInfo, query, resolve |
|
|
83
83
|
| `contractServices` | ContractService | yes | create, update, get, getMany, count, createIfNotExists | createIfNotExists → `contract_services.createIfNotExists` | count, create, createIfNotExists, get, getEntityInfo, getFieldInfo, getUdfInfo, query, resolve, update |
|
|
84
84
|
| `contractTicketPurchases` | ContractTicketPurchases | yes | create, update, get, getMany, count | — | count, create, get, getEntityInfo, getFieldInfo, getUdfInfo, query, resolve, update |
|
|
85
|
-
| `contracts` | Contract | no | create, update, get, getMany, count, createIfNotExists | createIfNotExists → `contracts.createIfNotExists` | count, create, createIfNotExists, get, getEntityInfo, getFieldInfo, getUdfInfo, query, resolve, search, update |
|
|
85
|
+
| `contracts` | Contract | no | create, update, get, getMany, count, createIfNotExists | createIfNotExists → `contracts.createIfNotExists` | count, create, createIfNotExists, get, getEntityInfo, getFieldInfo, getUdfInfo, profitability, query, resolve, search, update |
|
|
86
86
|
| `countries` | Country | no | get, getMany, update, count | — | count, get, getEntityInfo, getFieldInfo, getUdfInfo, query, resolve, search, update |
|
|
87
87
|
| `departments` | Department | no | count, create, get, getMany, update | — | count, create, get, getEntityInfo, getFieldInfo, getUdfInfo, query, resolve, search, update |
|
|
88
88
|
| `domainRegistrar` | DomainRegistrar | no | create, update, get, getMany, count | — | count, create, get, getEntityInfo, getFieldInfo, getUdfInfo, query, resolve, search, update |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-autotask",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "Fully-typed TypeScript SDK for the Autotask (Datto) PSA REST API. Zero runtime dependencies. Built for building MCP servers and integrations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|