@goxtechnologies/connectwise-psa-mcp 1.1.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/data/connectwise_api.db +0 -0
- package/data/manage.json +298179 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +116 -0
- package/dist/index.js.map +1 -0
- package/dist/operations/analytics-extended.d.ts +6 -0
- package/dist/operations/analytics-extended.d.ts.map +1 -0
- package/dist/operations/analytics-extended.js +825 -0
- package/dist/operations/analytics-extended.js.map +1 -0
- package/dist/operations/analytics-msp-assets.d.ts +3 -0
- package/dist/operations/analytics-msp-assets.d.ts.map +1 -0
- package/dist/operations/analytics-msp-assets.js +180 -0
- package/dist/operations/analytics-msp-assets.js.map +1 -0
- package/dist/operations/analytics-msp-clients.d.ts +3 -0
- package/dist/operations/analytics-msp-clients.d.ts.map +1 -0
- package/dist/operations/analytics-msp-clients.js +198 -0
- package/dist/operations/analytics-msp-clients.js.map +1 -0
- package/dist/operations/analytics-msp-comms.d.ts +3 -0
- package/dist/operations/analytics-msp-comms.d.ts.map +1 -0
- package/dist/operations/analytics-msp-comms.js +127 -0
- package/dist/operations/analytics-msp-comms.js.map +1 -0
- package/dist/operations/analytics-msp-contracts.d.ts +3 -0
- package/dist/operations/analytics-msp-contracts.d.ts.map +1 -0
- package/dist/operations/analytics-msp-contracts.js +91 -0
- package/dist/operations/analytics-msp-contracts.js.map +1 -0
- package/dist/operations/analytics-msp-financial.d.ts +3 -0
- package/dist/operations/analytics-msp-financial.d.ts.map +1 -0
- package/dist/operations/analytics-msp-financial.js +300 -0
- package/dist/operations/analytics-msp-financial.js.map +1 -0
- package/dist/operations/analytics-msp-procurement.d.ts +3 -0
- package/dist/operations/analytics-msp-procurement.d.ts.map +1 -0
- package/dist/operations/analytics-msp-procurement.js +78 -0
- package/dist/operations/analytics-msp-procurement.js.map +1 -0
- package/dist/operations/analytics-msp-projects.d.ts +3 -0
- package/dist/operations/analytics-msp-projects.d.ts.map +1 -0
- package/dist/operations/analytics-msp-projects.js +190 -0
- package/dist/operations/analytics-msp-projects.js.map +1 -0
- package/dist/operations/analytics-msp-sales.d.ts +3 -0
- package/dist/operations/analytics-msp-sales.d.ts.map +1 -0
- package/dist/operations/analytics-msp-sales.js +99 -0
- package/dist/operations/analytics-msp-sales.js.map +1 -0
- package/dist/operations/analytics-msp-schedule.d.ts +3 -0
- package/dist/operations/analytics-msp-schedule.d.ts.map +1 -0
- package/dist/operations/analytics-msp-schedule.js +339 -0
- package/dist/operations/analytics-msp-schedule.js.map +1 -0
- package/dist/operations/analytics-msp-team.d.ts +3 -0
- package/dist/operations/analytics-msp-team.d.ts.map +1 -0
- package/dist/operations/analytics-msp-team.js +195 -0
- package/dist/operations/analytics-msp-team.js.map +1 -0
- package/dist/operations/analytics-msp-tickets.d.ts +3 -0
- package/dist/operations/analytics-msp-tickets.d.ts.map +1 -0
- package/dist/operations/analytics-msp-tickets.js +578 -0
- package/dist/operations/analytics-msp-tickets.js.map +1 -0
- package/dist/operations/analytics-msp-time.d.ts +3 -0
- package/dist/operations/analytics-msp-time.d.ts.map +1 -0
- package/dist/operations/analytics-msp-time.js +485 -0
- package/dist/operations/analytics-msp-time.js.map +1 -0
- package/dist/operations/analytics-msp-utils.d.ts +49 -0
- package/dist/operations/analytics-msp-utils.d.ts.map +1 -0
- package/dist/operations/analytics-msp-utils.js +157 -0
- package/dist/operations/analytics-msp-utils.js.map +1 -0
- package/dist/operations/analytics.d.ts +9 -0
- package/dist/operations/analytics.d.ts.map +1 -0
- package/dist/operations/analytics.js +742 -0
- package/dist/operations/analytics.js.map +1 -0
- package/dist/operations/executor.d.ts +10 -0
- package/dist/operations/executor.d.ts.map +1 -0
- package/dist/operations/executor.js +243 -0
- package/dist/operations/executor.js.map +1 -0
- package/dist/operations/registry.d.ts +16 -0
- package/dist/operations/registry.d.ts.map +1 -0
- package/dist/operations/registry.js +847 -0
- package/dist/operations/registry.js.map +1 -0
- package/dist/services/api-database.d.ts +38 -0
- package/dist/services/api-database.d.ts.map +1 -0
- package/dist/services/api-database.js +191 -0
- package/dist/services/api-database.js.map +1 -0
- package/dist/services/cache.d.ts +12 -0
- package/dist/services/cache.d.ts.map +1 -0
- package/dist/services/cache.js +32 -0
- package/dist/services/cache.js.map +1 -0
- package/dist/services/connectwise-api.d.ts +43 -0
- package/dist/services/connectwise-api.d.ts.map +1 -0
- package/dist/services/connectwise-api.js +198 -0
- package/dist/services/connectwise-api.js.map +1 -0
- package/dist/services/db-builder.d.ts +11 -0
- package/dist/services/db-builder.d.ts.map +1 -0
- package/dist/services/db-builder.js +237 -0
- package/dist/services/db-builder.js.map +1 -0
- package/dist/services/fast-memory.d.ts +39 -0
- package/dist/services/fast-memory.d.ts.map +1 -0
- package/dist/services/fast-memory.js +147 -0
- package/dist/services/fast-memory.js.map +1 -0
- package/dist/services/load-env.d.ts +15 -0
- package/dist/services/load-env.d.ts.map +1 -0
- package/dist/services/load-env.js +59 -0
- package/dist/services/load-env.js.map +1 -0
- package/dist/tools/batch.d.ts +9 -0
- package/dist/tools/batch.d.ts.map +1 -0
- package/dist/tools/batch.js +159 -0
- package/dist/tools/batch.js.map +1 -0
- package/dist/tools/composite.d.ts +9 -0
- package/dist/tools/composite.d.ts.map +1 -0
- package/dist/tools/composite.js +353 -0
- package/dist/tools/composite.js.map +1 -0
- package/dist/tools/discovery.d.ts +9 -0
- package/dist/tools/discovery.d.ts.map +1 -0
- package/dist/tools/discovery.js +245 -0
- package/dist/tools/discovery.js.map +1 -0
- package/dist/tools/execution.d.ts +9 -0
- package/dist/tools/execution.d.ts.map +1 -0
- package/dist/tools/execution.js +130 -0
- package/dist/tools/execution.js.map +1 -0
- package/dist/tools/memory.d.ts +9 -0
- package/dist/tools/memory.d.ts.map +1 -0
- package/dist/tools/memory.js +152 -0
- package/dist/tools/memory.js.map +1 -0
- package/dist/tools/operations.d.ts +9 -0
- package/dist/tools/operations.d.ts.map +1 -0
- package/dist/tools/operations.js +214 -0
- package/dist/tools/operations.js.map +1 -0
- package/dist/tools/pagination.d.ts +9 -0
- package/dist/tools/pagination.d.ts.map +1 -0
- package/dist/tools/pagination.js +133 -0
- package/dist/tools/pagination.js.map +1 -0
- package/dist/tools/validation.d.ts +9 -0
- package/dist/tools/validation.d.ts.map +1 -0
- package/dist/tools/validation.js +705 -0
- package/dist/tools/validation.js.map +1 -0
- package/dist/types/index.d.ts +145 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/operations.d.ts +30 -0
- package/dist/types/operations.d.ts.map +1 -0
- package/dist/types/operations.js +3 -0
- package/dist/types/operations.js.map +1 -0
- package/dist/utils/conditions.d.ts +20 -0
- package/dist/utils/conditions.d.ts.map +1 -0
- package/dist/utils/conditions.js +78 -0
- package/dist/utils/conditions.js.map +1 -0
- package/dist/utils/formatters.d.ts +35 -0
- package/dist/utils/formatters.d.ts.map +1 -0
- package/dist/utils/formatters.js +337 -0
- package/dist/utils/formatters.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// ConnectWise PSA MCP Server — MSP Communications Analytics Handlers
|
|
2
|
+
import { getAPI } from '../services/connectwise-api.js';
|
|
3
|
+
import { num, nested, daysAgo, round2, mdTable, settledWarning } from './analytics-msp-utils.js';
|
|
4
|
+
export const mspCommsHandlers = {};
|
|
5
|
+
function reg(name, handler) { mspCommsHandlers[name] = handler; }
|
|
6
|
+
/** Compute median of a numeric array. Returns 0 for empty arrays. */
|
|
7
|
+
function medianOf(values) {
|
|
8
|
+
if (values.length === 0)
|
|
9
|
+
return 0;
|
|
10
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
11
|
+
const mid = Math.floor(sorted.length / 2);
|
|
12
|
+
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
13
|
+
}
|
|
14
|
+
/** Format minutes into a human-readable string (e.g. "2h 15m" or "45m"). */
|
|
15
|
+
function fmtMinutes(minutes) {
|
|
16
|
+
const h = Math.floor(minutes / 60);
|
|
17
|
+
const m = Math.round(minutes % 60);
|
|
18
|
+
return h > 0 ? `${h}h ${m}m` : `${m}m`;
|
|
19
|
+
}
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// first_contact_response_time
|
|
22
|
+
// For tickets created in period, fetch first note and compute response delta.
|
|
23
|
+
// Groups by priority.
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
reg('first_contact_response_time', async (params) => {
|
|
26
|
+
const api = getAPI();
|
|
27
|
+
const days = num(params['days'], 30);
|
|
28
|
+
const since = daysAgo(days);
|
|
29
|
+
const tickets = await api.paginatedFetch('/service/tickets', `dateEntered>=[${since}]`, 'id,priority/name,dateEntered', 500);
|
|
30
|
+
if (tickets.items.length === 0)
|
|
31
|
+
return `No tickets found in the last ${days} days.`;
|
|
32
|
+
// For each ticket, fetch the first note. Limit to 50 tickets to avoid excessive API load.
|
|
33
|
+
const sample = tickets.items.slice(0, 50);
|
|
34
|
+
const fcrNoteResults = await Promise.allSettled(sample.map(async (t) => {
|
|
35
|
+
const notes = await api.paginatedFetch(`/service/tickets/${t['id']}/notes`, '', 'id,dateCreated,createdBy', 1);
|
|
36
|
+
return { ticket: t, firstNote: notes.items[0] ?? null };
|
|
37
|
+
}));
|
|
38
|
+
// Bucket response times (in minutes) by priority
|
|
39
|
+
const byPriority = new Map();
|
|
40
|
+
for (const result of fcrNoteResults) {
|
|
41
|
+
if (result.status !== 'fulfilled')
|
|
42
|
+
continue;
|
|
43
|
+
const { ticket, firstNote } = result.value;
|
|
44
|
+
if (!firstNote)
|
|
45
|
+
continue;
|
|
46
|
+
const entered = new Date(String(ticket['dateEntered'] ?? '')).getTime();
|
|
47
|
+
const noted = new Date(String(firstNote['dateCreated'] ?? '')).getTime();
|
|
48
|
+
if (!entered || !noted || noted < entered)
|
|
49
|
+
continue;
|
|
50
|
+
const minutesDelta = (noted - entered) / 60000;
|
|
51
|
+
const priority = nested(ticket, 'priority', 'name');
|
|
52
|
+
const bucket = byPriority.get(priority);
|
|
53
|
+
if (bucket)
|
|
54
|
+
bucket.push(minutesDelta);
|
|
55
|
+
else
|
|
56
|
+
byPriority.set(priority, [minutesDelta]);
|
|
57
|
+
}
|
|
58
|
+
if (byPriority.size === 0)
|
|
59
|
+
return `No response time data available for the last ${days} days.`;
|
|
60
|
+
const rows = [];
|
|
61
|
+
for (const [priority, times] of [...byPriority.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
|
|
62
|
+
const avg = times.reduce((acc, t) => acc + t, 0) / times.length;
|
|
63
|
+
const med = medianOf(times);
|
|
64
|
+
rows.push([priority, fmtMinutes(round2(avg)), fmtMinutes(round2(med)), String(times.length)]);
|
|
65
|
+
}
|
|
66
|
+
return [
|
|
67
|
+
`## First Contact Response Time (last ${days} days, sample of ${sample.length} tickets)\n`,
|
|
68
|
+
mdTable(['Priority', 'Avg Response', 'Median', 'Count'], rows),
|
|
69
|
+
].join('\n') + settledWarning(fcrNoteResults, 'sub-requests');
|
|
70
|
+
});
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// communication_frequency_by_client
|
|
73
|
+
// Fetch ticket notes in period, group by ticket's company. Count notes/ticket.
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
reg('communication_frequency_by_client', async (params) => {
|
|
76
|
+
const api = getAPI();
|
|
77
|
+
const days = num(params['days'], 30);
|
|
78
|
+
const since = daysAgo(days);
|
|
79
|
+
// Fetch recent tickets with company info to build a ticket->company map
|
|
80
|
+
const tickets = await api.paginatedFetch('/service/tickets', `dateEntered>=[${since}]`, 'id,company/name', 1000);
|
|
81
|
+
if (tickets.items.length === 0)
|
|
82
|
+
return `No tickets found in the last ${days} days.`;
|
|
83
|
+
const ticketToCompany = new Map();
|
|
84
|
+
for (const t of tickets.items) {
|
|
85
|
+
ticketToCompany.set(String(t['id']), nested(t, 'company', 'name'));
|
|
86
|
+
}
|
|
87
|
+
// Fetch notes for those tickets. Limit sample to 100 tickets.
|
|
88
|
+
const sampleTickets = tickets.items.slice(0, 100);
|
|
89
|
+
const commNoteResults = await Promise.allSettled(sampleTickets.map(async (t) => {
|
|
90
|
+
const notes = await api.paginatedFetch(`/service/tickets/${t['id']}/notes`, '', 'id,dateCreated', 50);
|
|
91
|
+
return { ticketId: String(t['id']), noteCount: notes.items.length, lastNote: notes.items[0] ?? null };
|
|
92
|
+
}));
|
|
93
|
+
// Aggregate by company
|
|
94
|
+
const byCompany = new Map();
|
|
95
|
+
for (const result of commNoteResults) {
|
|
96
|
+
if (result.status !== 'fulfilled')
|
|
97
|
+
continue;
|
|
98
|
+
const { ticketId, noteCount, lastNote } = result.value;
|
|
99
|
+
const company = ticketToCompany.get(ticketId) ?? 'Unknown';
|
|
100
|
+
const lastDate = String(lastNote?.['dateCreated'] ?? '').substring(0, 10);
|
|
101
|
+
const existing = byCompany.get(company);
|
|
102
|
+
if (existing) {
|
|
103
|
+
existing.notes += noteCount;
|
|
104
|
+
existing.ticketIds.add(ticketId);
|
|
105
|
+
if (lastDate && lastDate > existing.lastContact)
|
|
106
|
+
existing.lastContact = lastDate;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
byCompany.set(company, { notes: noteCount, ticketIds: new Set([ticketId]), lastContact: lastDate });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (byCompany.size === 0)
|
|
113
|
+
return `No communication data available for the last ${days} days.`;
|
|
114
|
+
const rows = [...byCompany.entries()]
|
|
115
|
+
.sort((a, b) => b[1].notes - a[1].notes)
|
|
116
|
+
.slice(0, 30)
|
|
117
|
+
.map(([company, d]) => {
|
|
118
|
+
const ticketCount = d.ticketIds.size;
|
|
119
|
+
const notesPerTicket = ticketCount > 0 ? round2(d.notes / ticketCount) : 0;
|
|
120
|
+
return [company, String(d.notes), String(ticketCount), String(notesPerTicket), d.lastContact || 'N/A'];
|
|
121
|
+
});
|
|
122
|
+
return [
|
|
123
|
+
`## Communication Frequency by Client (last ${days} days, sample of ${sampleTickets.length} tickets)\n`,
|
|
124
|
+
mdTable(['Company', 'Notes', 'Tickets', 'Notes/Ticket', 'Last Contact'], rows),
|
|
125
|
+
].join('\n') + settledWarning(commNoteResults, 'sub-requests');
|
|
126
|
+
});
|
|
127
|
+
//# sourceMappingURL=analytics-msp-comms.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics-msp-comms.js","sourceRoot":"","sources":["../../src/operations/analytics-msp-comms.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics-msp-contracts.d.ts","sourceRoot":"","sources":["../../src/operations/analytics-msp-contracts.ts"],"names":[],"mappings":"AAGA,OAAO,EAA2B,KAAK,UAAU,EAAiD,MAAM,0BAA0B,CAAC;AAEnI,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAM,CAAC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// ConnectWise PSA MCP Server — MSP Contract & Agreement Analytics Handlers
|
|
2
|
+
import { getAPI } from '../services/connectwise-api.js';
|
|
3
|
+
import { num, nested, round2, currencyDisplay, mdTable } from './analytics-msp-utils.js';
|
|
4
|
+
export const mspContractHandlers = {};
|
|
5
|
+
function reg(name, handler) { mspContractHandlers[name] = handler; }
|
|
6
|
+
/** Returns today's ISO date string at midnight UTC. */
|
|
7
|
+
function today() { return new Date().toISOString().split('T')[0] + 'T00:00:00Z'; }
|
|
8
|
+
/** Returns ISO date string N days in the future at midnight UTC. */
|
|
9
|
+
function daysFromNow(n) {
|
|
10
|
+
const d = new Date();
|
|
11
|
+
d.setUTCDate(d.getUTCDate() + n);
|
|
12
|
+
return d.toISOString().split('T')[0] + 'T00:00:00Z';
|
|
13
|
+
}
|
|
14
|
+
/** Returns the number of days from today until the given ISO date. Negative if in the past. */
|
|
15
|
+
function daysUntil(iso) {
|
|
16
|
+
if (!iso)
|
|
17
|
+
return 0;
|
|
18
|
+
return Math.floor((new Date(iso).getTime() - Date.now()) / 86400000);
|
|
19
|
+
}
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// expiring_agreements_forecast
|
|
22
|
+
// Fetch agreements ending within N days. Show revenue at risk.
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
reg('expiring_agreements_forecast', async (params) => {
|
|
25
|
+
const api = getAPI();
|
|
26
|
+
const daysAhead = num(params['days_ahead'], 90);
|
|
27
|
+
const cutoff = daysFromNow(daysAhead);
|
|
28
|
+
const r = await api.paginatedFetch('/finance/agreements', `cancelledFlag=false AND endDate>=[${today()}] AND endDate<=[${cutoff}]`, 'id,name,company/name,endDate,billAmount', 500);
|
|
29
|
+
if (r.items.length === 0)
|
|
30
|
+
return `No agreements expiring in the next ${daysAhead} days.`;
|
|
31
|
+
const sorted = [...r.items].sort((a, b) => String(a['endDate'] ?? '').localeCompare(String(b['endDate'] ?? '')));
|
|
32
|
+
const totalMRR = sorted.reduce((acc, a) => acc + num(a['billAmount']), 0);
|
|
33
|
+
const rows = sorted.map(a => {
|
|
34
|
+
const endDate = String(a['endDate'] ?? '').substring(0, 10);
|
|
35
|
+
const daysLeft = daysUntil(String(a['endDate'] ?? ''));
|
|
36
|
+
const monthlyRev = num(a['billAmount']);
|
|
37
|
+
const revenueAtRisk = round2(monthlyRev * Math.max(1, Math.ceil(daysLeft / 30)));
|
|
38
|
+
return [
|
|
39
|
+
String(a['name'] ?? 'N/A'),
|
|
40
|
+
nested(a, 'company', 'name'),
|
|
41
|
+
endDate,
|
|
42
|
+
`${daysLeft}d`,
|
|
43
|
+
currencyDisplay(monthlyRev),
|
|
44
|
+
currencyDisplay(revenueAtRisk),
|
|
45
|
+
];
|
|
46
|
+
});
|
|
47
|
+
return [
|
|
48
|
+
`## Expiring Agreements Forecast (next ${daysAhead} days)\n`,
|
|
49
|
+
`**Agreements at Risk:** ${r.items.length} | **Total MRR at Risk:** ${currencyDisplay(round2(totalMRR))}\n`,
|
|
50
|
+
mdTable(['Agreement', 'Company', 'End Date', 'Days Left', 'Monthly Revenue', 'Revenue at Risk'], rows),
|
|
51
|
+
].join('\n');
|
|
52
|
+
});
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// renewal_pipeline_value
|
|
55
|
+
// Fetch agreements expiring in next 12 months, grouped by month.
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
reg('renewal_pipeline_value', async (_params) => {
|
|
58
|
+
const api = getAPI();
|
|
59
|
+
const cutoff = daysFromNow(365);
|
|
60
|
+
const r = await api.paginatedFetch('/finance/agreements', `cancelledFlag=false AND endDate>=[${today()}] AND endDate<=[${cutoff}]`, 'id,name,company/name,endDate,billAmount', 500);
|
|
61
|
+
if (r.items.length === 0)
|
|
62
|
+
return 'No agreements expiring in the next 12 months.';
|
|
63
|
+
// Group by YYYY-MM
|
|
64
|
+
const byMonth = new Map();
|
|
65
|
+
for (const a of r.items) {
|
|
66
|
+
const month = String(a['endDate'] ?? '').substring(0, 7);
|
|
67
|
+
if (!month)
|
|
68
|
+
continue;
|
|
69
|
+
const bucket = byMonth.get(month);
|
|
70
|
+
if (bucket)
|
|
71
|
+
bucket.push(a);
|
|
72
|
+
else
|
|
73
|
+
byMonth.set(month, [a]);
|
|
74
|
+
}
|
|
75
|
+
const sortedMonths = [...byMonth.keys()].sort();
|
|
76
|
+
let cumulative = 0;
|
|
77
|
+
const rows = [];
|
|
78
|
+
for (const month of sortedMonths) {
|
|
79
|
+
const agreements = byMonth.get(month) ?? [];
|
|
80
|
+
const mrr = agreements.reduce((acc, a) => acc + num(a['billAmount']), 0);
|
|
81
|
+
cumulative += mrr;
|
|
82
|
+
rows.push([month, String(agreements.length), currencyDisplay(round2(mrr)), currencyDisplay(round2(cumulative))]);
|
|
83
|
+
}
|
|
84
|
+
const totalMRR = r.items.reduce((acc, a) => acc + num(a['billAmount']), 0);
|
|
85
|
+
return [
|
|
86
|
+
`## Renewal Pipeline Value (next 12 months)\n`,
|
|
87
|
+
`**Total Agreements:** ${r.items.length} | **Total MRR at Risk:** ${currencyDisplay(round2(totalMRR))}\n`,
|
|
88
|
+
mdTable(['Month', 'Count', 'Total MRR', 'Cumulative at Risk'], rows),
|
|
89
|
+
].join('\n');
|
|
90
|
+
});
|
|
91
|
+
//# sourceMappingURL=analytics-msp-contracts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics-msp-contracts.js","sourceRoot":"","sources":["../../src/operations/analytics-msp-contracts.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAE3E,OAAO,EAAE,MAAM,EAAE,MAAM,gCAAgC,CAAC;AACxD,OAAO,EAA4C,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAEnI,MAAM,CAAC,MAAM,mBAAmB,GAA+B,EAAE,CAAC;AAClE,SAAS,GAAG,CAAC,IAAY,EAAE,OAAmB,IAAU,mBAAmB,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;AAE9F,uDAAuD;AACvD,SAAS,KAAK,KAAa,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC;AAE1F,oEAAoE;AACpE,SAAS,WAAW,CAAC,CAAS;IAC5B,MAAM,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC;IACrB,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,CAAC;IACjC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC;AACtD,CAAC;AAED,+FAA+F;AAC/F,SAAS,SAAS,CAAC,GAAW;IAC5B,IAAI,CAAC,GAAG;QAAE,OAAO,CAAC,CAAC;IACnB,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,QAAQ,CAAC,CAAC;AACvE,CAAC;AAED,8EAA8E;AAC9E,+BAA+B;AAC/B,+DAA+D;AAC/D,8EAA8E;AAE9E,GAAG,CAAC,8BAA8B,EAAE,KAAK,EAAE,MAAc,EAAE,EAAE;IAC3D,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IACrB,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IAEtC,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,cAAc,CAChC,qBAAqB,EACrB,qCAAqC,KAAK,EAAE,mBAAmB,MAAM,GAAG,EACxE,yCAAyC,EACzC,GAAG,CACJ,CAAC;IAEF,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,sCAAsC,SAAS,QAAQ,CAAC;IAEzF,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACjH,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAE1E,MAAM,IAAI,GAAe,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;QACtC,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5D,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACvD,MAAM,UAAU,GAAG,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;QACxC,MAAM,aAAa,GAAG,MAAM,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,QAAQ,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;QACjF,OAAO;YACL,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC;YAC1B,MAAM,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC;YAC5B,OAAO;YACP,GAAG,QAAQ,GAAG;YACd,eAAe,CAAC,UAAU,CAAC;YAC3B,eAAe,CAAC,aAAa,CAAC;SAC/B,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,yCAAyC,SAAS,UAAU;QAC5D,2BAA2B,CAAC,CAAC,KAAK,CAAC,MAAM,6BAA6B,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI;QAC3G,OAAO,CAAC,CAAC,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,CAAC,EAAE,IAAI,CAAC;KACvG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC,CAAC,CAAC;AAEH,8EAA8E;AAC9E,yBAAyB;AACzB,iEAAiE;AACjE,8EAA8E;AAE9E,GAAG,CAAC,wBAAwB,EAAE,KAAK,EAAE,OAAe,EAAE,EAAE;IACtD,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC;IACrB,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAEhC,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,cAAc,CAChC,qBAAqB,EACrB,qCAAqC,KAAK,EAAE,mBAAmB,MAAM,GAAG,EACxE,yCAAyC,EACzC,GAAG,CACJ,CAAC;IAEF,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,+CAA+C,CAAC;IAEjF,mBAAmB;IACnB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAmB,CAAC;IAC3C,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACzD,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAClC,IAAI,MAAM;YAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;;YAAM,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3D,CAAC;IAED,MAAM,YAAY,GAAG,CAAC,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAChD,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,MAAM,IAAI,GAAe,EAAE,CAAC;IAC5B,KAAK,MAAM,KAAK,IAAI,YAAY,EAAE,CAAC;QACjC,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;QAC5C,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACzE,UAAU,IAAI,GAAG,CAAC;QAClB,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,eAAe,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;IACnH,CAAC;IAED,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAE3E,OAAO;QACL,8CAA8C;QAC9C,yBAAyB,CAAC,CAAC,KAAK,CAAC,MAAM,6BAA6B,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI;QACzG,OAAO,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,oBAAoB,CAAC,EAAE,IAAI,CAAC;KACrE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics-msp-financial.d.ts","sourceRoot":"","sources":["../../src/operations/analytics-msp-financial.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,UAAU,EAYhB,MAAM,0BAA0B,CAAC;AAElC,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAM,CAAC"}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
// ConnectWise PSA MCP Server — MSP Analytics: Financial & Profitability (7 handlers)
|
|
2
|
+
import { getAPI } from '../services/connectwise-api.js';
|
|
3
|
+
import { num, nested, daysAgo, round2, groupByField, sum, pct, hoursDisplay, currencyDisplay, mdTable, settledWarning, } from './analytics-msp-utils.js';
|
|
4
|
+
export const mspFinancialHandlers = {};
|
|
5
|
+
function reg(name, handler) { mspFinancialHandlers[name] = handler; }
|
|
6
|
+
// ===========================================================================
|
|
7
|
+
// 1. revenue_per_company
|
|
8
|
+
// Billable time entries grouped by company, summing actualHours * hourlyRate.
|
|
9
|
+
// ===========================================================================
|
|
10
|
+
reg('revenue_per_company', async (params) => {
|
|
11
|
+
const api = getAPI();
|
|
12
|
+
const days = num(params['days'], 30);
|
|
13
|
+
const entries = await api.paginatedFetch('/time/entries', `billableOption='Billable' AND dateEntered>=[${daysAgo(days)}]`, 'id,actualHours,hourlyRate,company/name', 5000);
|
|
14
|
+
if (entries.items.length === 0)
|
|
15
|
+
return `No billable time entries found in the last ${days} days.`;
|
|
16
|
+
const byCompany = groupByField(entries.items, 'company/name');
|
|
17
|
+
const stats = new Map();
|
|
18
|
+
for (const [company, items] of byCompany) {
|
|
19
|
+
const hours = sum(items, 'actualHours');
|
|
20
|
+
const revenue = items.reduce((acc, e) => acc + num(e['actualHours']) * num(e['hourlyRate']), 0);
|
|
21
|
+
stats.set(company, { hours, revenue });
|
|
22
|
+
}
|
|
23
|
+
const sorted = [...stats.entries()].sort((a, b) => b[1].revenue - a[1].revenue);
|
|
24
|
+
const rows = sorted.map(([company, s]) => {
|
|
25
|
+
const effectiveRate = s.hours > 0 ? round2(s.revenue / s.hours) : 0;
|
|
26
|
+
return [company, hoursDisplay(s.hours), currencyDisplay(s.revenue), currencyDisplay(effectiveRate)];
|
|
27
|
+
});
|
|
28
|
+
const totalHours = [...stats.values()].reduce((a, s) => a + s.hours, 0);
|
|
29
|
+
const totalRevenue = [...stats.values()].reduce((a, s) => a + s.revenue, 0);
|
|
30
|
+
return [
|
|
31
|
+
`## Revenue per Company (last ${days} days)`,
|
|
32
|
+
``,
|
|
33
|
+
`**Total Hours:** ${hoursDisplay(totalHours)} | **Total Revenue:** ${currencyDisplay(totalRevenue)}`,
|
|
34
|
+
``,
|
|
35
|
+
mdTable(['Company', 'Hours', 'Revenue', 'Effective Rate'], rows),
|
|
36
|
+
].join('\n');
|
|
37
|
+
});
|
|
38
|
+
// ===========================================================================
|
|
39
|
+
// 2. agreement_utilization_summary
|
|
40
|
+
// Active agreements vs. time entries consumed, with at-risk status.
|
|
41
|
+
// ===========================================================================
|
|
42
|
+
reg('agreement_utilization_summary', async (_params) => {
|
|
43
|
+
const api = getAPI();
|
|
44
|
+
const agreements = await api.paginatedFetch('/finance/agreements', 'cancelledFlag=false', 'id,name,company/name,budgetHours', 500);
|
|
45
|
+
if (agreements.items.length === 0)
|
|
46
|
+
return 'No active agreements found.';
|
|
47
|
+
const batch = agreements.items.slice(0, 40);
|
|
48
|
+
const agrResults = await Promise.allSettled(batch.map(async (a) => {
|
|
49
|
+
const timeEntries = await api.paginatedFetch('/time/entries', `agreement/id=${a['id']}`, 'id,actualHours', 2000);
|
|
50
|
+
const usedHours = timeEntries.items.reduce((acc, e) => acc + num(e['actualHours']), 0);
|
|
51
|
+
return { agreement: a, usedHours };
|
|
52
|
+
}));
|
|
53
|
+
const rows = [];
|
|
54
|
+
for (const result of agrResults) {
|
|
55
|
+
if (result.status !== 'fulfilled')
|
|
56
|
+
continue;
|
|
57
|
+
const { agreement: a, usedHours } = result.value;
|
|
58
|
+
const budget = num(a['budgetHours']);
|
|
59
|
+
const usedPct = budget > 0 ? round2((usedHours / budget) * 100) : 0;
|
|
60
|
+
const status = budget === 0 ? 'N/A' : usedPct > 100 ? 'OVER' : usedPct >= 80 ? 'At Risk' : 'OK';
|
|
61
|
+
rows.push([
|
|
62
|
+
String(a['name'] ?? 'N/A'),
|
|
63
|
+
nested(a, 'company', 'name'),
|
|
64
|
+
budget > 0 ? hoursDisplay(budget) : 'N/A',
|
|
65
|
+
hoursDisplay(usedHours),
|
|
66
|
+
budget > 0 ? `${usedPct}%` : 'N/A',
|
|
67
|
+
status,
|
|
68
|
+
]);
|
|
69
|
+
}
|
|
70
|
+
if (rows.length === 0)
|
|
71
|
+
return 'No agreement utilization data available.';
|
|
72
|
+
const overCount = rows.filter(r => r[5] === 'OVER').length;
|
|
73
|
+
const atRiskCount = rows.filter(r => r[5] === 'At Risk').length;
|
|
74
|
+
return [
|
|
75
|
+
`## Agreement Utilization Summary`,
|
|
76
|
+
``,
|
|
77
|
+
`**Agreements:** ${rows.length} | **Over Budget:** ${overCount} | **At Risk:** ${atRiskCount}`,
|
|
78
|
+
``,
|
|
79
|
+
mdTable(['Agreement', 'Company', 'Budget', 'Used', '%', 'Status'], rows),
|
|
80
|
+
].join('\n') + settledWarning(agrResults, 'sub-requests');
|
|
81
|
+
});
|
|
82
|
+
// ===========================================================================
|
|
83
|
+
// 3. agreement_burn_rate
|
|
84
|
+
// Monthly consumption rate and projected exhaustion date per agreement.
|
|
85
|
+
// ===========================================================================
|
|
86
|
+
reg('agreement_burn_rate', async (params) => {
|
|
87
|
+
const api = getAPI();
|
|
88
|
+
const agreementId = params['agreement_id'] !== undefined ? num(params['agreement_id']) : null;
|
|
89
|
+
const condition = agreementId !== null
|
|
90
|
+
? `cancelledFlag=false AND id=${agreementId}`
|
|
91
|
+
: 'cancelledFlag=false';
|
|
92
|
+
const agreements = await api.paginatedFetch('/finance/agreements', condition, 'id,name,company/name,budgetHours,startDate', agreementId !== null ? 1 : 200);
|
|
93
|
+
if (agreements.items.length === 0)
|
|
94
|
+
return 'No matching agreements found.';
|
|
95
|
+
const batch = agreements.items.slice(0, 30);
|
|
96
|
+
const burnResults = await Promise.allSettled(batch.map(async (a) => {
|
|
97
|
+
const timeEntries = await api.paginatedFetch('/time/entries', `agreement/id=${a['id']}`, 'id,actualHours,dateEntered', 2000);
|
|
98
|
+
return { agreement: a, entries: timeEntries.items };
|
|
99
|
+
}));
|
|
100
|
+
const rows = [];
|
|
101
|
+
for (const result of burnResults) {
|
|
102
|
+
if (result.status !== 'fulfilled')
|
|
103
|
+
continue;
|
|
104
|
+
const { agreement: a, entries } = result.value;
|
|
105
|
+
const budget = num(a['budgetHours']);
|
|
106
|
+
const totalUsed = entries.reduce((acc, e) => acc + num(e['actualHours']), 0);
|
|
107
|
+
// Determine the date span of entries to compute monthly burn rate
|
|
108
|
+
const startDate = a['startDate'] ? new Date(String(a['startDate'])) : null;
|
|
109
|
+
const now = new Date();
|
|
110
|
+
let monthsElapsed = 1;
|
|
111
|
+
if (startDate !== null && startDate < now) {
|
|
112
|
+
const msPerMonth = 30.44 * 24 * 3600 * 1000;
|
|
113
|
+
monthsElapsed = Math.max(1, (now.getTime() - startDate.getTime()) / msPerMonth);
|
|
114
|
+
}
|
|
115
|
+
const monthlyBurn = round2(totalUsed / monthsElapsed);
|
|
116
|
+
const remaining = round2(budget - totalUsed);
|
|
117
|
+
let projectedExhaustion = 'N/A';
|
|
118
|
+
if (monthlyBurn > 0 && remaining > 0) {
|
|
119
|
+
const monthsLeft = remaining / monthlyBurn;
|
|
120
|
+
const exhaustDate = new Date();
|
|
121
|
+
exhaustDate.setDate(exhaustDate.getDate() + Math.round(monthsLeft * 30.44));
|
|
122
|
+
projectedExhaustion = exhaustDate.toISOString().substring(0, 10);
|
|
123
|
+
}
|
|
124
|
+
else if (remaining <= 0) {
|
|
125
|
+
projectedExhaustion = 'Already exhausted';
|
|
126
|
+
}
|
|
127
|
+
rows.push([
|
|
128
|
+
String(a['name'] ?? 'N/A'),
|
|
129
|
+
nested(a, 'company', 'name'),
|
|
130
|
+
`${monthlyBurn}h/mo`,
|
|
131
|
+
hoursDisplay(remaining),
|
|
132
|
+
projectedExhaustion,
|
|
133
|
+
]);
|
|
134
|
+
}
|
|
135
|
+
if (rows.length === 0)
|
|
136
|
+
return 'No burn rate data available.';
|
|
137
|
+
return [
|
|
138
|
+
`## Agreement Burn Rate`,
|
|
139
|
+
``,
|
|
140
|
+
mdTable(['Agreement', 'Company', 'Monthly Burn', 'Remaining', 'Projected Exhaustion Date'], rows),
|
|
141
|
+
].join('\n') + settledWarning(burnResults, 'sub-requests');
|
|
142
|
+
});
|
|
143
|
+
// ===========================================================================
|
|
144
|
+
// 4. effective_hourly_rate
|
|
145
|
+
// Revenue divided by hours, grouped by company or member.
|
|
146
|
+
// ===========================================================================
|
|
147
|
+
reg('effective_hourly_rate_msp', async (params) => {
|
|
148
|
+
const api = getAPI();
|
|
149
|
+
const days = num(params['days'], 30);
|
|
150
|
+
const groupBy = String(params['group_by'] ?? 'company');
|
|
151
|
+
const isGroupByMember = groupBy === 'member';
|
|
152
|
+
const groupField = isGroupByMember ? 'member/identifier' : 'company/name';
|
|
153
|
+
const fields = isGroupByMember
|
|
154
|
+
? 'id,actualHours,hourlyRate,member/identifier,member/firstName,member/lastName'
|
|
155
|
+
: 'id,actualHours,hourlyRate,company/name';
|
|
156
|
+
const entries = await api.paginatedFetch('/time/entries', `billableOption='Billable' AND dateEntered>=[${daysAgo(days)}]`, fields, 5000);
|
|
157
|
+
if (entries.items.length === 0)
|
|
158
|
+
return `No billable time entries found in the last ${days} days.`;
|
|
159
|
+
const byEntity = groupByField(entries.items, groupField);
|
|
160
|
+
const stats = new Map();
|
|
161
|
+
for (const [entity, items] of byEntity) {
|
|
162
|
+
const hours = sum(items, 'actualHours');
|
|
163
|
+
const revenue = items.reduce((acc, e) => acc + num(e['actualHours']) * num(e['hourlyRate']), 0);
|
|
164
|
+
const listRate = items.length > 0
|
|
165
|
+
? items.reduce((acc, e) => acc + num(e['hourlyRate']), 0) / items.length
|
|
166
|
+
: 0;
|
|
167
|
+
stats.set(entity, { hours, revenue, listRate, count: items.length });
|
|
168
|
+
}
|
|
169
|
+
const sorted = [...stats.entries()].sort((a, b) => b[1].revenue - a[1].revenue);
|
|
170
|
+
const rows = sorted.map(([entity, s]) => {
|
|
171
|
+
const effectiveRate = s.hours > 0 ? round2(s.revenue / s.hours) : 0;
|
|
172
|
+
const vsListRate = s.listRate > 0 ? `${round2(((effectiveRate - s.listRate) / s.listRate) * 100)}%` : 'N/A';
|
|
173
|
+
return [entity, hoursDisplay(s.hours), currencyDisplay(s.revenue), currencyDisplay(effectiveRate), vsListRate];
|
|
174
|
+
});
|
|
175
|
+
const entityLabel = isGroupByMember ? 'Member' : 'Company';
|
|
176
|
+
return [
|
|
177
|
+
`## Effective Hourly Rate by ${entityLabel} (last ${days} days)`,
|
|
178
|
+
``,
|
|
179
|
+
mdTable([entityLabel, 'Hours', 'Revenue', 'Effective Rate', 'vs List Rate'], rows),
|
|
180
|
+
].join('\n');
|
|
181
|
+
});
|
|
182
|
+
// ===========================================================================
|
|
183
|
+
// 5. profitability_by_company
|
|
184
|
+
// Revenue (billable hours × rate) minus cost (hours × hourlyCost) per company.
|
|
185
|
+
// ===========================================================================
|
|
186
|
+
reg('profitability_by_company', async (params) => {
|
|
187
|
+
const api = getAPI();
|
|
188
|
+
const days = num(params['days'], 30);
|
|
189
|
+
const entries = await api.paginatedFetch('/time/entries', `billableOption='Billable' AND dateEntered>=[${daysAgo(days)}]`, 'id,actualHours,hourlyRate,hourlyCost,company/name', 5000);
|
|
190
|
+
if (entries.items.length === 0)
|
|
191
|
+
return `No billable time entries found in the last ${days} days.`;
|
|
192
|
+
const byCompany = groupByField(entries.items, 'company/name');
|
|
193
|
+
const stats = new Map();
|
|
194
|
+
for (const [company, items] of byCompany) {
|
|
195
|
+
const revenue = items.reduce((acc, e) => acc + num(e['actualHours']) * num(e['hourlyRate']), 0);
|
|
196
|
+
const cost = items.reduce((acc, e) => acc + num(e['actualHours']) * num(e['hourlyCost']), 0);
|
|
197
|
+
const hours = sum(items, 'actualHours');
|
|
198
|
+
stats.set(company, { revenue, cost, hours });
|
|
199
|
+
}
|
|
200
|
+
const sorted = [...stats.entries()].sort((a, b) => (b[1].revenue - b[1].cost) - (a[1].revenue - a[1].cost));
|
|
201
|
+
const rows = sorted.map(([company, s]) => {
|
|
202
|
+
const margin = round2(s.revenue - s.cost);
|
|
203
|
+
const marginPct = pct(s.revenue - s.cost, s.revenue);
|
|
204
|
+
return [company, currencyDisplay(s.revenue), currencyDisplay(s.cost), currencyDisplay(margin), marginPct];
|
|
205
|
+
});
|
|
206
|
+
const totalRevenue = [...stats.values()].reduce((a, s) => a + s.revenue, 0);
|
|
207
|
+
const totalCost = [...stats.values()].reduce((a, s) => a + s.cost, 0);
|
|
208
|
+
const totalMargin = round2(totalRevenue - totalCost);
|
|
209
|
+
return [
|
|
210
|
+
`## Profitability by Company (last ${days} days)`,
|
|
211
|
+
``,
|
|
212
|
+
`**Total Revenue:** ${currencyDisplay(totalRevenue)} | **Total Cost:** ${currencyDisplay(totalCost)} | **Total Margin:** ${currencyDisplay(totalMargin)} (${pct(totalMargin, totalRevenue)})`,
|
|
213
|
+
``,
|
|
214
|
+
mdTable(['Company', 'Revenue', 'Cost', 'Margin', 'Margin %'], rows),
|
|
215
|
+
].join('\n');
|
|
216
|
+
});
|
|
217
|
+
// ===========================================================================
|
|
218
|
+
// 6. profitability_by_member
|
|
219
|
+
// Revenue minus cost per member (technician-level P&L).
|
|
220
|
+
// ===========================================================================
|
|
221
|
+
reg('profitability_by_member', async (params) => {
|
|
222
|
+
const api = getAPI();
|
|
223
|
+
const days = num(params['days'], 30);
|
|
224
|
+
const entries = await api.paginatedFetch('/time/entries', `billableOption='Billable' AND dateEntered>=[${daysAgo(days)}]`, 'id,actualHours,hourlyRate,hourlyCost,member/identifier', 5000);
|
|
225
|
+
if (entries.items.length === 0)
|
|
226
|
+
return `No billable time entries found in the last ${days} days.`;
|
|
227
|
+
const byMember = groupByField(entries.items, 'member/identifier');
|
|
228
|
+
const stats = new Map();
|
|
229
|
+
for (const [member, items] of byMember) {
|
|
230
|
+
const hours = sum(items, 'actualHours');
|
|
231
|
+
const revenue = items.reduce((acc, e) => acc + num(e['actualHours']) * num(e['hourlyRate']), 0);
|
|
232
|
+
const cost = items.reduce((acc, e) => acc + num(e['actualHours']) * num(e['hourlyCost']), 0);
|
|
233
|
+
stats.set(member, { hours, revenue, cost });
|
|
234
|
+
}
|
|
235
|
+
const sorted = [...stats.entries()].sort((a, b) => (b[1].revenue - b[1].cost) - (a[1].revenue - a[1].cost));
|
|
236
|
+
const rows = sorted.map(([member, s]) => {
|
|
237
|
+
const margin = round2(s.revenue - s.cost);
|
|
238
|
+
return [member, hoursDisplay(s.hours), currencyDisplay(s.revenue), currencyDisplay(s.cost), currencyDisplay(margin)];
|
|
239
|
+
});
|
|
240
|
+
const totalRevenue = [...stats.values()].reduce((a, s) => a + s.revenue, 0);
|
|
241
|
+
const totalCost = [...stats.values()].reduce((a, s) => a + s.cost, 0);
|
|
242
|
+
const totalMargin = round2(totalRevenue - totalCost);
|
|
243
|
+
return [
|
|
244
|
+
`## Profitability by Member (last ${days} days)`,
|
|
245
|
+
``,
|
|
246
|
+
`**Total Revenue:** ${currencyDisplay(totalRevenue)} | **Total Cost:** ${currencyDisplay(totalCost)} | **Total Margin:** ${currencyDisplay(totalMargin)} (${pct(totalMargin, totalRevenue)})`,
|
|
247
|
+
``,
|
|
248
|
+
mdTable(['Member', 'Billable Hours', 'Revenue', 'Cost', 'Margin'], rows),
|
|
249
|
+
].join('\n');
|
|
250
|
+
});
|
|
251
|
+
// ===========================================================================
|
|
252
|
+
// 7. invoice_readiness
|
|
253
|
+
// Billable time split by invoiceFlag (ready vs. pending) grouped by company.
|
|
254
|
+
// ===========================================================================
|
|
255
|
+
reg('invoice_readiness', async (_params) => {
|
|
256
|
+
const api = getAPI();
|
|
257
|
+
const entries = await api.paginatedFetch('/time/entries', `billableOption='Billable'`, 'id,actualHours,hourlyRate,invoiceFlag,company/name', 5000);
|
|
258
|
+
if (entries.items.length === 0)
|
|
259
|
+
return 'No billable time entries found.';
|
|
260
|
+
const byCompany = new Map();
|
|
261
|
+
for (const e of entries.items) {
|
|
262
|
+
const company = nested(e, 'company', 'name');
|
|
263
|
+
if (!byCompany.has(company)) {
|
|
264
|
+
byCompany.set(company, { readyHours: 0, readyRevenue: 0, pendingHours: 0, pendingRevenue: 0 });
|
|
265
|
+
}
|
|
266
|
+
const stat = byCompany.get(company);
|
|
267
|
+
const hours = num(e['actualHours']);
|
|
268
|
+
const revenue = hours * num(e['hourlyRate']);
|
|
269
|
+
const isReady = e['invoiceFlag'] === true;
|
|
270
|
+
if (isReady) {
|
|
271
|
+
stat.readyHours += hours;
|
|
272
|
+
stat.readyRevenue += revenue;
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
stat.pendingHours += hours;
|
|
276
|
+
stat.pendingRevenue += revenue;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const sorted = [...byCompany.entries()].sort((a, b) => (b[1].readyRevenue + b[1].pendingRevenue) - (a[1].readyRevenue + a[1].pendingRevenue));
|
|
280
|
+
const rows = sorted.map(([company, s]) => [
|
|
281
|
+
company,
|
|
282
|
+
hoursDisplay(s.readyHours),
|
|
283
|
+
currencyDisplay(s.readyRevenue),
|
|
284
|
+
hoursDisplay(s.pendingHours),
|
|
285
|
+
currencyDisplay(s.pendingRevenue),
|
|
286
|
+
]);
|
|
287
|
+
const totals = [...byCompany.values()];
|
|
288
|
+
const totalReadyHours = totals.reduce((a, s) => a + s.readyHours, 0);
|
|
289
|
+
const totalReadyRevenue = totals.reduce((a, s) => a + s.readyRevenue, 0);
|
|
290
|
+
const totalPendingHours = totals.reduce((a, s) => a + s.pendingHours, 0);
|
|
291
|
+
const totalPendingRevenue = totals.reduce((a, s) => a + s.pendingRevenue, 0);
|
|
292
|
+
return [
|
|
293
|
+
`## Invoice Readiness`,
|
|
294
|
+
``,
|
|
295
|
+
`**Ready:** ${hoursDisplay(totalReadyHours)} / ${currencyDisplay(totalReadyRevenue)} | **Pending:** ${hoursDisplay(totalPendingHours)} / ${currencyDisplay(totalPendingRevenue)}`,
|
|
296
|
+
``,
|
|
297
|
+
mdTable(['Company', 'Ready Hours', 'Ready $', 'Pending Hours', 'Pending $'], rows),
|
|
298
|
+
].join('\n');
|
|
299
|
+
});
|
|
300
|
+
//# sourceMappingURL=analytics-msp-financial.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics-msp-financial.js","sourceRoot":"","sources":["../../src/operations/analytics-msp-financial.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics-msp-procurement.d.ts","sourceRoot":"","sources":["../../src/operations/analytics-msp-procurement.ts"],"names":[],"mappings":"AAGA,OAAO,EAA2B,KAAK,UAAU,EAA6F,MAAM,0BAA0B,CAAC;AAE/K,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAM,CAAC"}
|