untrap-mcp 0.1.4 → 0.2.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/dist/db/filters.d.ts +49 -0
- package/dist/db/filters.d.ts.map +1 -0
- package/dist/db/filters.js +97 -0
- package/dist/db/filters.js.map +1 -0
- package/dist/tools/analytics/billing.d.ts.map +1 -1
- package/dist/tools/analytics/billing.js +4 -1
- package/dist/tools/analytics/billing.js.map +1 -1
- package/dist/tools/analytics/qc-violations.d.ts.map +1 -1
- package/dist/tools/analytics/qc-violations.js +6 -0
- package/dist/tools/analytics/qc-violations.js.map +1 -1
- package/dist/tools/analytics/sla-breach-tickets.d.ts.map +1 -1
- package/dist/tools/analytics/sla-breach-tickets.js +4 -1
- package/dist/tools/analytics/sla-breach-tickets.js.map +1 -1
- package/dist/tools/analytics/sla-breaches.d.ts.map +1 -1
- package/dist/tools/analytics/sla-breaches.js +5 -2
- package/dist/tools/analytics/sla-breaches.js.map +1 -1
- package/dist/tools/analytics/technician-perf.d.ts.map +1 -1
- package/dist/tools/analytics/technician-perf.js +8 -0
- package/dist/tools/analytics/technician-perf.js.map +1 -1
- package/dist/tools/cfo/client-profitability.d.ts.map +1 -1
- package/dist/tools/cfo/client-profitability.js +18 -16
- package/dist/tools/cfo/client-profitability.js.map +1 -1
- package/dist/tools/cfo/financial-overview.d.ts.map +1 -1
- package/dist/tools/cfo/financial-overview.js +6 -4
- package/dist/tools/cfo/financial-overview.js.map +1 -1
- package/dist/tools/cfo/labor-cost-trend.d.ts.map +1 -1
- package/dist/tools/cfo/labor-cost-trend.js +33 -31
- package/dist/tools/cfo/labor-cost-trend.js.map +1 -1
- package/dist/tools/cfo/revenue-leakage.d.ts.map +1 -1
- package/dist/tools/cfo/revenue-leakage.js +20 -18
- package/dist/tools/cfo/revenue-leakage.js.map +1 -1
- package/dist/tools/cfo/service-cost-analysis.d.ts.map +1 -1
- package/dist/tools/cfo/service-cost-analysis.js +4 -2
- package/dist/tools/cfo/service-cost-analysis.js.map +1 -1
- package/dist/tools/cfo/technician-utilization.d.ts.map +1 -1
- package/dist/tools/cfo/technician-utilization.js +19 -17
- package/dist/tools/cfo/technician-utilization.js.map +1 -1
- package/dist/tools/composite/client-360.d.ts.map +1 -1
- package/dist/tools/composite/client-360.js +12 -9
- package/dist/tools/composite/client-360.js.map +1 -1
- package/dist/tools/composite/compare-periods.d.ts.map +1 -1
- package/dist/tools/composite/compare-periods.js +17 -13
- package/dist/tools/composite/compare-periods.js.map +1 -1
- package/dist/tools/composite/draft-email.d.ts.map +1 -1
- package/dist/tools/composite/draft-email.js +7 -4
- package/dist/tools/composite/draft-email.js.map +1 -1
- package/dist/tools/composite/morning-briefing.d.ts.map +1 -1
- package/dist/tools/composite/morning-briefing.js +10 -7
- package/dist/tools/composite/morning-briefing.js.map +1 -1
- package/dist/tools/composite/technician-360.d.ts.map +1 -1
- package/dist/tools/composite/technician-360.js +10 -6
- package/dist/tools/composite/technician-360.js.map +1 -1
- package/dist/tools/qbr/client-detail.d.ts.map +1 -1
- package/dist/tools/qbr/client-detail.js +13 -9
- package/dist/tools/qbr/client-detail.js.map +1 -1
- package/dist/tools/qbr/executive-summary.d.ts.map +1 -1
- package/dist/tools/qbr/executive-summary.js +20 -13
- package/dist/tools/qbr/executive-summary.js.map +1 -1
- package/dist/tools/qbr/financial-summary.d.ts.map +1 -1
- package/dist/tools/qbr/financial-summary.js +13 -8
- package/dist/tools/qbr/financial-summary.js.map +1 -1
- package/dist/tools/qbr/sla-performance.d.ts.map +1 -1
- package/dist/tools/qbr/sla-performance.js +11 -8
- package/dist/tools/qbr/sla-performance.js.map +1 -1
- package/dist/tools/qbr/technician-scorecard.d.ts.map +1 -1
- package/dist/tools/qbr/technician-scorecard.js +35 -26
- package/dist/tools/qbr/technician-scorecard.js.map +1 -1
- package/dist/tools/qbr/ticket-trends.d.ts.map +1 -1
- package/dist/tools/qbr/ticket-trends.js +18 -15
- package/dist/tools/qbr/ticket-trends.js.map +1 -1
- package/dist/tools/query/data-query.d.ts.map +1 -1
- package/dist/tools/query/data-query.js +24 -1
- package/dist/tools/query/data-query.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cached filter configuration for an MSP.
|
|
3
|
+
* Fetched once on first use, reused for all subsequent tool calls.
|
|
4
|
+
*/
|
|
5
|
+
interface MspFilters {
|
|
6
|
+
boardNames: string[];
|
|
7
|
+
enabledTechnicians: string[];
|
|
8
|
+
mspCompanyName: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Fetch and cache the user-configured filters for an MSP.
|
|
12
|
+
* These are READ-ONLY user preferences — never modify them.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getFilters(mspId: string): Promise<MspFilters>;
|
|
15
|
+
/**
|
|
16
|
+
* Build SQL WHERE fragments for ticket_lifecycle queries.
|
|
17
|
+
*
|
|
18
|
+
* Returns a string like:
|
|
19
|
+
* AND tl.board_name IN ('Board A', 'Board B')
|
|
20
|
+
* AND tl.owner_name IN ('Tech 1', 'Tech 2')
|
|
21
|
+
* AND LOWER(tl.company_name) NOT LIKE '%test%'
|
|
22
|
+
* ...
|
|
23
|
+
*
|
|
24
|
+
* Pass tableAlias to match your query (default 'tl').
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildTicketFilter(filters: MspFilters, opts?: {
|
|
27
|
+
tableAlias?: string;
|
|
28
|
+
includeBoardFilter?: boolean;
|
|
29
|
+
includeTechnicianFilter?: boolean;
|
|
30
|
+
includeCompanyExclusions?: boolean;
|
|
31
|
+
}): string;
|
|
32
|
+
/**
|
|
33
|
+
* Build SQL WHERE fragments for time_entries queries.
|
|
34
|
+
* time_entries uses member_name instead of owner_name.
|
|
35
|
+
*/
|
|
36
|
+
export declare function buildTimeEntriesFilter(filters: MspFilters, opts?: {
|
|
37
|
+
tableAlias?: string;
|
|
38
|
+
includeTechnicianFilter?: boolean;
|
|
39
|
+
}): string;
|
|
40
|
+
/**
|
|
41
|
+
* Build SQL WHERE fragments for queries that use company_name column
|
|
42
|
+
* but don't have board_name (e.g., billing_classifications, MVs).
|
|
43
|
+
*/
|
|
44
|
+
export declare function buildCompanyExclusionFilter(filters: MspFilters, opts?: {
|
|
45
|
+
tableAlias?: string;
|
|
46
|
+
companyColumn?: string;
|
|
47
|
+
}): string;
|
|
48
|
+
export {};
|
|
49
|
+
//# sourceMappingURL=filters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filters.d.ts","sourceRoot":"","sources":["../../src/db/filters.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,UAAU,UAAU;IAClB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC;CACxB;AAKD;;;GAGG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CA+BnE;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,UAAU,EACnB,IAAI,GAAE;IACJ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,wBAAwB,CAAC,EAAE,OAAO,CAAC;CAC/B,GACL,MAAM,CAsCR;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,UAAU,EACnB,IAAI,GAAE;IACJ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uBAAuB,CAAC,EAAE,OAAO,CAAC;CAC9B,GACL,MAAM,CAaR;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,UAAU,EACnB,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,GACzD,MAAM,CAYR"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { queryRLS } from "./gateway-client.js";
|
|
2
|
+
// In-memory cache keyed by mspId (lives for the process lifetime = one MCP session)
|
|
3
|
+
const filterCache = new Map();
|
|
4
|
+
/**
|
|
5
|
+
* Fetch and cache the user-configured filters for an MSP.
|
|
6
|
+
* These are READ-ONLY user preferences — never modify them.
|
|
7
|
+
*/
|
|
8
|
+
export async function getFilters(mspId) {
|
|
9
|
+
const cached = filterCache.get(mspId);
|
|
10
|
+
if (cached)
|
|
11
|
+
return cached;
|
|
12
|
+
// Fetch all three configs in parallel
|
|
13
|
+
const [boardRows, techRows, mspRows] = await Promise.all([
|
|
14
|
+
queryRLS(`SELECT board_name FROM msp_board_names WHERE msp_id = $1`, [mspId], mspId),
|
|
15
|
+
queryRLS(`SELECT technician_name FROM msp_technician_preferences WHERE msp_id = $1 AND is_enabled = true`, [mspId], mspId),
|
|
16
|
+
queryRLS(`SELECT name FROM msp_table WHERE id = $1`, [mspId], mspId),
|
|
17
|
+
]);
|
|
18
|
+
const filters = {
|
|
19
|
+
boardNames: boardRows.map((r) => r.board_name),
|
|
20
|
+
enabledTechnicians: techRows.map((r) => r.technician_name),
|
|
21
|
+
mspCompanyName: mspRows[0]?.name || "",
|
|
22
|
+
};
|
|
23
|
+
filterCache.set(mspId, filters);
|
|
24
|
+
return filters;
|
|
25
|
+
}
|
|
26
|
+
function escapeSQL(val) {
|
|
27
|
+
return val.replace(/'/g, "''");
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Build SQL WHERE fragments for ticket_lifecycle queries.
|
|
31
|
+
*
|
|
32
|
+
* Returns a string like:
|
|
33
|
+
* AND tl.board_name IN ('Board A', 'Board B')
|
|
34
|
+
* AND tl.owner_name IN ('Tech 1', 'Tech 2')
|
|
35
|
+
* AND LOWER(tl.company_name) NOT LIKE '%test%'
|
|
36
|
+
* ...
|
|
37
|
+
*
|
|
38
|
+
* Pass tableAlias to match your query (default 'tl').
|
|
39
|
+
*/
|
|
40
|
+
export function buildTicketFilter(filters, opts = {}) {
|
|
41
|
+
const { tableAlias: t = "tl", includeBoardFilter = true, includeTechnicianFilter = true, includeCompanyExclusions = true, } = opts;
|
|
42
|
+
let sql = "";
|
|
43
|
+
// Board filter
|
|
44
|
+
if (includeBoardFilter && filters.boardNames.length > 0) {
|
|
45
|
+
const list = filters.boardNames.map((b) => `'${escapeSQL(b)}'`).join(", ");
|
|
46
|
+
sql += `\n AND ${t}.board_name IN (${list})`;
|
|
47
|
+
}
|
|
48
|
+
// Technician filter (only when query involves owner_name)
|
|
49
|
+
if (includeTechnicianFilter && filters.enabledTechnicians.length > 0) {
|
|
50
|
+
const list = filters.enabledTechnicians
|
|
51
|
+
.map((n) => `'${escapeSQL(n)}'`)
|
|
52
|
+
.join(", ");
|
|
53
|
+
sql += `\n AND ${t}.owner_name IN (${list})`;
|
|
54
|
+
}
|
|
55
|
+
// Company exclusions
|
|
56
|
+
if (includeCompanyExclusions) {
|
|
57
|
+
sql += `
|
|
58
|
+
AND LOWER(${t}.company_name) NOT LIKE '%test%'
|
|
59
|
+
AND LOWER(${t}.company_name) NOT LIKE '%catchall%'
|
|
60
|
+
AND ${t}.company_name IS NOT NULL
|
|
61
|
+
AND ${t}.company_name != ''`;
|
|
62
|
+
if (filters.mspCompanyName) {
|
|
63
|
+
sql += `\n AND LOWER(${t}.company_name) NOT LIKE LOWER('%${escapeSQL(filters.mspCompanyName)}%')`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return sql;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Build SQL WHERE fragments for time_entries queries.
|
|
70
|
+
* time_entries uses member_name instead of owner_name.
|
|
71
|
+
*/
|
|
72
|
+
export function buildTimeEntriesFilter(filters, opts = {}) {
|
|
73
|
+
const { tableAlias: t = "te", includeTechnicianFilter = true } = opts;
|
|
74
|
+
let sql = "";
|
|
75
|
+
if (includeTechnicianFilter && filters.enabledTechnicians.length > 0) {
|
|
76
|
+
const list = filters.enabledTechnicians
|
|
77
|
+
.map((n) => `'${escapeSQL(n)}'`)
|
|
78
|
+
.join(", ");
|
|
79
|
+
sql += `\n AND ${t}.member_name IN (${list})`;
|
|
80
|
+
}
|
|
81
|
+
return sql;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Build SQL WHERE fragments for queries that use company_name column
|
|
85
|
+
* but don't have board_name (e.g., billing_classifications, MVs).
|
|
86
|
+
*/
|
|
87
|
+
export function buildCompanyExclusionFilter(filters, opts = {}) {
|
|
88
|
+
const { tableAlias: t = "bc", companyColumn = "company_name" } = opts;
|
|
89
|
+
let sql = `
|
|
90
|
+
AND LOWER(${t}.${companyColumn}) NOT LIKE '%test%'
|
|
91
|
+
AND LOWER(${t}.${companyColumn}) NOT LIKE '%catchall%'`;
|
|
92
|
+
if (filters.mspCompanyName) {
|
|
93
|
+
sql += `\n AND LOWER(${t}.${companyColumn}) NOT LIKE LOWER('%${escapeSQL(filters.mspCompanyName)}%')`;
|
|
94
|
+
}
|
|
95
|
+
return sql;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=filters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filters.js","sourceRoot":"","sources":["../../src/db/filters.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"billing.d.ts","sourceRoot":"","sources":["../../../src/tools/analytics/billing.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"billing.d.ts","sourceRoot":"","sources":["../../../src/tools/analytics/billing.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAErE,UAAU,aAAa;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,aAAa;;;;;;;GAgCtB"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { queryRLS } from "../../db/gateway-client.js";
|
|
2
|
+
import { getFilters, buildCompanyExclusionFilter } from "../../db/filters.js";
|
|
2
3
|
export async function getBillingOpportunities(mspId, params) {
|
|
3
4
|
const fromDate = params.fromDate ?? new Date(Date.now() - 30 * 86400000).toISOString().split("T")[0];
|
|
4
5
|
const toDate = params.toDate ?? new Date().toISOString().split("T")[0];
|
|
6
|
+
const filters = await getFilters(mspId);
|
|
7
|
+
const companyFilter = buildCompanyExclusionFilter(filters, { tableAlias: "bc" });
|
|
5
8
|
const rows = await queryRLS(`SELECT bc.ticket_id, bc.company_name, bc.classification,
|
|
6
9
|
bc.billable_amount, bc.resolved_date::text
|
|
7
10
|
FROM agents.billing_classifications bc
|
|
@@ -9,7 +12,7 @@ export async function getBillingOpportunities(mspId, params) {
|
|
|
9
12
|
AND bc.classification = 'out_of_scope'
|
|
10
13
|
AND bc.resolved_date >= $2::date
|
|
11
14
|
AND bc.resolved_date <= $3::date
|
|
12
|
-
AND bc.billable_amount > 0
|
|
15
|
+
AND bc.billable_amount > 0${companyFilter}
|
|
13
16
|
ORDER BY bc.billable_amount DESC`, [mspId, fromDate, toDate], mspId);
|
|
14
17
|
const totalDollars = rows.reduce((sum, r) => sum + (r.billable_amount || 0), 0);
|
|
15
18
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"billing.js","sourceRoot":"","sources":["../../../src/tools/analytics/billing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"billing.js","sourceRoot":"","sources":["../../../src/tools/analytics/billing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAQ9E,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,KAAa,EACb,MAAqB;IAErB,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACrG,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAEvE,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;IACxC,MAAM,aAAa,GAAG,2BAA2B,CAAC,OAAO,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;IAEjF,MAAM,IAAI,GAAG,MAAM,QAAQ,CACzB;;;;;;;mCAO+B,aAAa;sCACV,EAClC,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,EACzB,KAAK,CACN,CAAC;IAEF,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,eAAe,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAEhF,OAAO;QACL,OAAO,EAAE;YACP,aAAa,EAAE,IAAI,CAAC,MAAM;YAC1B,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,GAAG,CAAC,GAAG,GAAG;YACnD,cAAc,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;SAC3F;QACD,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qc-violations.d.ts","sourceRoot":"","sources":["../../../src/tools/analytics/qc-violations.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"qc-violations.d.ts","sourceRoot":"","sources":["../../../src/tools/analytics/qc-violations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D,UAAU,iBAAiB;IACzB,QAAQ,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;IAClD,MAAM,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,WAAW,GAAG,OAAO,CAAC;CACzD;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,iBAAiB;;;;;;;;;;;;;GA0E1B"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { queryRLS } from "../../db/gateway-client.js";
|
|
2
|
+
import { getFilters } from "../../db/filters.js";
|
|
2
3
|
export async function getQCViolations(mspId, params) {
|
|
4
|
+
const filters = await getFilters(mspId);
|
|
3
5
|
let whereClause = "WHERE v.msp_id = $1";
|
|
4
6
|
const queryParams = [mspId];
|
|
5
7
|
let paramIndex = 2;
|
|
@@ -13,6 +15,10 @@ export async function getQCViolations(mspId, params) {
|
|
|
13
15
|
queryParams.push(params.status);
|
|
14
16
|
paramIndex++;
|
|
15
17
|
}
|
|
18
|
+
if (filters.enabledTechnicians.length > 0) {
|
|
19
|
+
const techList = filters.enabledTechnicians.map(n => `'${n.replace(/'/g, "''")}'`).join(", ");
|
|
20
|
+
whereClause += ` AND v.technician_name IN (${techList})`;
|
|
21
|
+
}
|
|
16
22
|
const rows = await queryRLS(`SELECT v.violation_id, v.ticket_id, v.ticket_summary,
|
|
17
23
|
v.rule_id, v.rule_name, v.severity,
|
|
18
24
|
v.description, v.technician_name, v.client_name,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qc-violations.js","sourceRoot":"","sources":["../../../src/tools/analytics/qc-violations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"qc-violations.js","sourceRoot":"","sources":["../../../src/tools/analytics/qc-violations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAQjD,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,KAAa,EACb,MAAyB;IAEzB,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;IAExC,IAAI,WAAW,GAAG,qBAAqB,CAAC;IACxC,MAAM,WAAW,GAAc,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,UAAU,GAAG,CAAC,CAAC;IAEnB,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,WAAW,IAAI,sBAAsB,UAAU,EAAE,CAAC;QAClD,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAClC,UAAU,EAAE,CAAC;IACf,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,WAAW,IAAI,2BAA2B,UAAU,EAAE,CAAC;QACvD,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAChC,UAAU,EAAE,CAAC;IACf,CAAC;IAED,IAAI,OAAO,CAAC,kBAAkB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAG,OAAO,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9F,WAAW,IAAI,8BAA8B,QAAQ,GAAG,CAAC;IAC3D,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CACzB;;;;;OAKG,WAAW;;eAEH,EACX,WAAW,EACX,KAAK,CACN,CAAC;IAEF,MAAM,OAAO,GAAG;QACd,gBAAgB,EAAE,IAAI,CAAC,MAAM;QAC7B,WAAW,EAAE;YACX,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,MAAM;YAC9D,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,MAAM;YACtD,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,MAAM;YAC1D,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,KAAK,CAAC,CAAC,MAAM;SACrD;KACF,CAAC;IAEF,yCAAyC;IACzC,MAAM,YAAY,GAAG,MAAM,QAAQ,CACjC;;;;;;;;;;;aAWS,EACT,CAAC,KAAK,CAAC,EACP,KAAK,CACN,CAAC;IAEF,OAAO;QACL,OAAO,EAAE;YACP,GAAG,OAAO;YACV,gBAAgB,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,gBAAgB,IAAI,CAAC;YACxD,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,SAAS,IAAI,CAAC;SAC3C;QACD,UAAU,EAAE,IAAI;KACjB,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sla-breach-tickets.d.ts","sourceRoot":"","sources":["../../../src/tools/analytics/sla-breach-tickets.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sla-breach-tickets.d.ts","sourceRoot":"","sources":["../../../src/tools/analytics/sla-breach-tickets.ts"],"names":[],"mappings":"AAGA,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,UAAU,GAAG,YAAY,GAAG,KAAK,CAAC;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,sBAAsB;;;;;;;;;;GAiF/B"}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { queryRLS } from "../../db/gateway-client.js";
|
|
2
|
+
import { getFilters, buildTicketFilter } from "../../db/filters.js";
|
|
2
3
|
export async function getSlaBreachTickets(mspId, params) {
|
|
3
4
|
const fromDate = params.fromDate || new Date(Date.now() - 30 * 86400000).toISOString().slice(0, 10);
|
|
4
5
|
const toDate = params.toDate || new Date().toISOString().slice(0, 10);
|
|
5
6
|
const limit = params.limit || 25;
|
|
6
7
|
const breachType = params.breachType || "any";
|
|
8
|
+
const mspFilters = await getFilters(mspId);
|
|
9
|
+
const ticketFilter = buildTicketFilter(mspFilters);
|
|
7
10
|
const filters = [];
|
|
8
11
|
const queryParams = [mspId, fromDate, toDate];
|
|
9
12
|
let paramIndex = 4;
|
|
@@ -54,7 +57,7 @@ export async function getSlaBreachTickets(mspId, params) {
|
|
|
54
57
|
AND tl.responded_date >= $2::date AND tl.responded_date <= $3::date
|
|
55
58
|
AND tl.owner_name IS NOT NULL AND tl.owner_name != ''
|
|
56
59
|
${breachFilter}
|
|
57
|
-
${filters.join(" ")}
|
|
60
|
+
${filters.join(" ")}${ticketFilter}
|
|
58
61
|
ORDER BY GREATEST(
|
|
59
62
|
COALESCE(tl.response_minutes - sla.triage_minutes, 0),
|
|
60
63
|
COALESCE(tl.resolution_minutes - sla.resolve_minutes, 0)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sla-breach-tickets.js","sourceRoot":"","sources":["../../../src/tools/analytics/sla-breach-tickets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"sla-breach-tickets.js","sourceRoot":"","sources":["../../../src/tools/analytics/sla-breach-tickets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAWpE,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,KAAa,EACb,MAA8B;IAE9B,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACpG,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACtE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;IACjC,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,KAAK,CAAC;IAE9C,MAAM,UAAU,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;IAC3C,MAAM,YAAY,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAEnD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,WAAW,GAAc,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IACzD,IAAI,UAAU,GAAG,CAAC,CAAC;IAEnB,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;QAC1B,OAAO,CAAC,IAAI,CAAC,wCAAwC,UAAU,GAAG,CAAC,CAAC;QACpE,WAAW,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,cAAc,GAAG,CAAC,CAAC;QAC/C,UAAU,EAAE,CAAC;IACf,CAAC;IAED,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACtB,OAAO,CAAC,IAAI,CAAC,0CAA0C,UAAU,GAAG,CAAC,CAAC;QACtE,WAAW,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,UAAU,GAAG,CAAC,CAAC;QAC3C,UAAU,EAAE,CAAC;IACf,CAAC;IAED,IAAI,YAAoB,CAAC;IACzB,IAAI,UAAU,KAAK,UAAU,EAAE,CAAC;QAC9B,YAAY,GAAG,8CAA8C,CAAC;IAChE,CAAC;SAAM,IAAI,UAAU,KAAK,YAAY,EAAE,CAAC;QACvC,YAAY,GAAG,iDAAiD,CAAC;IACnE,CAAC;SAAM,CAAC;QACN,YAAY,GAAG,+FAA+F,CAAC;IACjH,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,QAAQ,CAC5B;;;;;;;;;;;;;;;;;;;;;;;;;;SA0BK,YAAY;SACZ,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,YAAY;;;;;aAK5B,KAAK,EAAE,EAChB,WAAW,EACX,KAAK,CACN,CAAC;IAEF,OAAO;QACL,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE;QACtC,WAAW,EAAE,UAAU;QACvB,iBAAiB,EAAE,MAAM,CAAC,cAAc,IAAI,IAAI;QAChD,aAAa,EAAE,MAAM,CAAC,UAAU,IAAI,IAAI;QACxC,cAAc,EAAE,OAAO,CAAC,MAAM;QAC9B,OAAO;KACR,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sla-breaches.d.ts","sourceRoot":"","sources":["../../../src/tools/analytics/sla-breaches.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sla-breaches.d.ts","sourceRoot":"","sources":["../../../src/tools/analytics/sla-breaches.ts"],"names":[],"mappings":"AAGA,UAAU,eAAe;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,eAAe;;;;;;;;;GA8ExB"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { queryRLS } from "../../db/gateway-client.js";
|
|
2
|
+
import { getFilters, buildTicketFilter } from "../../db/filters.js";
|
|
2
3
|
export async function getSlaBreachesByTechnician(mspId, params) {
|
|
3
4
|
const fromDate = params.fromDate || new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);
|
|
4
5
|
const toDate = params.toDate || new Date().toISOString().slice(0, 10);
|
|
6
|
+
const mspFilters = await getFilters(mspId);
|
|
7
|
+
const ticketFilter = buildTicketFilter(mspFilters);
|
|
5
8
|
const clientFilter = params.clientName
|
|
6
9
|
? "AND LOWER(tl.company_name) LIKE LOWER($4)"
|
|
7
10
|
: "";
|
|
@@ -24,7 +27,7 @@ export async function getSlaBreachesByTechnician(mspId, params) {
|
|
|
24
27
|
WHERE tl.msp_id = $1
|
|
25
28
|
AND tl.responded_date >= $2::date AND tl.responded_date <= $3::date
|
|
26
29
|
AND tl.owner_name IS NOT NULL AND tl.owner_name != ''
|
|
27
|
-
${clientFilter}
|
|
30
|
+
${clientFilter}${ticketFilter}
|
|
28
31
|
GROUP BY tl.owner_name
|
|
29
32
|
ORDER BY any_breach DESC
|
|
30
33
|
LIMIT 20`, [mspId, fromDate, toDate, ...clientParam], mspId),
|
|
@@ -47,7 +50,7 @@ export async function getSlaBreachesByTechnician(mspId, params) {
|
|
|
47
50
|
WHERE tl.msp_id = $1
|
|
48
51
|
AND tl.responded_date >= $2::date AND tl.responded_date <= $3::date
|
|
49
52
|
AND tl.owner_name IS NOT NULL AND tl.owner_name != ''
|
|
50
|
-
${clientFilter}`, [mspId, fromDate, toDate, ...clientParam], mspId),
|
|
53
|
+
${clientFilter}${ticketFilter}`, [mspId, fromDate, toDate, ...clientParam], mspId),
|
|
51
54
|
]);
|
|
52
55
|
return {
|
|
53
56
|
period: { from: fromDate, to: toDate },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sla-breaches.js","sourceRoot":"","sources":["../../../src/tools/analytics/sla-breaches.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"sla-breaches.js","sourceRoot":"","sources":["../../../src/tools/analytics/sla-breaches.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAQpE,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,KAAa,EACb,MAAuB;IAEvB,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACpG,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEtE,MAAM,UAAU,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;IAC3C,MAAM,YAAY,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAEnD,MAAM,YAAY,GAAG,MAAM,CAAC,UAAU;QACpC,CAAC,CAAC,2CAA2C;QAC7C,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,WAAW,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAExE,MAAM,CAAC,YAAY,EAAE,UAAU,EAAE,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAC5D,yBAAyB;QACzB,QAAQ,CACN;;;;;;;;;;;;;;;;WAgBK,YAAY,GAAG,YAAY;;;gBAGtB,EACV,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,WAAW,CAAC,EACzC,KAAK,CACN;QAED,4BAA4B;QAC5B,QAAQ,CACN;;;yBAGmB,EACnB,CAAC,KAAK,CAAC,EACP,KAAK,CACN;QAED,sBAAsB;QACtB,QAAQ,CACN;;;;;;;;;;;;;WAaK,YAAY,GAAG,YAAY,EAAE,EAClC,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,WAAW,CAAC,EACzC,KAAK,CACN;KACF,CAAC,CAAC;IAEH,OAAO;QACL,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE;QACtC,aAAa,EAAE,MAAM,CAAC,UAAU,IAAI,IAAI;QACxC,WAAW,EAAE,UAAU;QACvB,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,aAAa,EAAE,CAAC,EAAE,gBAAgB,EAAE,CAAC,EAAE,kBAAkB,EAAE,CAAC,EAAE;QACvF,aAAa,EAAE,YAAY;KAC5B,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"technician-perf.d.ts","sourceRoot":"","sources":["../../../src/tools/analytics/technician-perf.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"technician-perf.d.ts","sourceRoot":"","sources":["../../../src/tools/analytics/technician-perf.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAEjE,UAAU,oBAAoB;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,wBAAwB,CAC5C,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,oBAAoB;;GA0B7B"}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { queryRLS } from "../../db/gateway-client.js";
|
|
2
|
+
import { getFilters } from "../../db/filters.js";
|
|
2
3
|
export async function getTechnicianPerformance(mspId, params) {
|
|
4
|
+
const filters = await getFilters(mspId);
|
|
5
|
+
let techFilter = "";
|
|
6
|
+
if (filters.enabledTechnicians.length > 0) {
|
|
7
|
+
const techList = filters.enabledTechnicians.map(n => `'${n.replace(/'/g, "''")}'`).join(", ");
|
|
8
|
+
techFilter = `AND technician_name IN (${techList})`;
|
|
9
|
+
}
|
|
3
10
|
const rows = await queryRLS(`SELECT technician_name, calculation_date::text,
|
|
4
11
|
unique_tickets, total_notes, avg_note_length,
|
|
5
12
|
quality_points, completeness_points, accuracy_points,
|
|
@@ -8,6 +15,7 @@ export async function getTechnicianPerformance(mspId, params) {
|
|
|
8
15
|
WHERE msp_id = $1
|
|
9
16
|
AND calculation_date >= $2::date
|
|
10
17
|
AND calculation_date <= $3::date
|
|
18
|
+
${techFilter}
|
|
11
19
|
ORDER BY quality_points DESC`, [mspId, params.fromDate, params.toDate], mspId);
|
|
12
20
|
return { technicians: rows };
|
|
13
21
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"technician-perf.js","sourceRoot":"","sources":["../../../src/tools/analytics/technician-perf.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"technician-perf.js","sourceRoot":"","sources":["../../../src/tools/analytics/technician-perf.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAQjD,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,KAAa,EACb,MAA4B;IAE5B,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;IAExC,IAAI,UAAU,GAAG,EAAE,CAAC;IACpB,IAAI,OAAO,CAAC,kBAAkB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1C,MAAM,QAAQ,GAAG,OAAO,CAAC,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9F,UAAU,GAAG,2BAA2B,QAAQ,GAAG,CAAC;IACtD,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CACzB;;;;;;;;SAQK,UAAU;kCACe,EAC9B,CAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,EACvC,KAAK,CACN,CAAC;IAEF,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;AAC/B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-profitability.d.ts","sourceRoot":"","sources":["../../../src/tools/cfo/client-profitability.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client-profitability.d.ts","sourceRoot":"","sources":["../../../src/tools/cfo/client-profitability.ts"],"names":[],"mappings":"AAGA,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,mBAAmB;;;;;;;;;;;;;;;;;GAuFtF"}
|
|
@@ -1,33 +1,35 @@
|
|
|
1
1
|
import { queryRLS } from "../../db/gateway-client.js";
|
|
2
|
+
import { getFilters, buildTimeEntriesFilter } from "../../db/filters.js";
|
|
2
3
|
export async function cfoClientProfitability(mspId, params) {
|
|
3
4
|
const fromDate = params.fromDate ?? new Date(Date.now() - 90 * 86400000).toISOString().split("T")[0];
|
|
4
5
|
const toDate = params.toDate ?? new Date().toISOString().split("T")[0];
|
|
6
|
+
const filters = await getFilters(mspId);
|
|
5
7
|
let clientFilter = "";
|
|
6
8
|
const queryParams = [mspId, fromDate, toDate];
|
|
7
9
|
if (params.clientName) {
|
|
8
|
-
clientFilter = ` AND LOWER(company) LIKE LOWER($4)`;
|
|
10
|
+
clientFilter = ` AND LOWER(te.company) LIKE LOWER($4)`;
|
|
9
11
|
queryParams.push(`%${params.clientName}%`);
|
|
10
12
|
}
|
|
11
13
|
// Cost-to-serve per client
|
|
12
|
-
const clients = await queryRLS(`SELECT company as client_name,
|
|
13
|
-
COUNT(DISTINCT ticket_id)::int as tickets,
|
|
14
|
-
SUM(actual_hours)::numeric as total_hours,
|
|
15
|
-
SUM(billable_hours)::numeric as billable_hours,
|
|
16
|
-
SUM(total_cost)::numeric as total_cost,
|
|
17
|
-
CASE WHEN COUNT(DISTINCT ticket_id) > 0
|
|
18
|
-
THEN ROUND(SUM(total_cost) / COUNT(DISTINCT ticket_id), 2)
|
|
14
|
+
const clients = await queryRLS(`SELECT te.company as client_name,
|
|
15
|
+
COUNT(DISTINCT te.ticket_id)::int as tickets,
|
|
16
|
+
SUM(te.actual_hours)::numeric as total_hours,
|
|
17
|
+
SUM(te.billable_hours)::numeric as billable_hours,
|
|
18
|
+
SUM(te.total_cost)::numeric as total_cost,
|
|
19
|
+
CASE WHEN COUNT(DISTINCT te.ticket_id) > 0
|
|
20
|
+
THEN ROUND(SUM(te.total_cost) / COUNT(DISTINCT te.ticket_id), 2)
|
|
19
21
|
ELSE 0
|
|
20
22
|
END as cost_per_ticket,
|
|
21
|
-
CASE WHEN SUM(actual_hours) > 0
|
|
22
|
-
THEN ROUND((SUM(billable_hours) / SUM(actual_hours)) * 100, 1)
|
|
23
|
+
CASE WHEN SUM(te.actual_hours) > 0
|
|
24
|
+
THEN ROUND((SUM(te.billable_hours) / SUM(te.actual_hours)) * 100, 1)
|
|
23
25
|
ELSE 0
|
|
24
26
|
END as billable_pct
|
|
25
|
-
FROM time_entries
|
|
26
|
-
WHERE msp_id = $1
|
|
27
|
-
AND time_start >= $2::date AND time_start <= $3::date
|
|
28
|
-
${clientFilter}
|
|
29
|
-
GROUP BY company
|
|
30
|
-
HAVING SUM(actual_hours) > 0
|
|
27
|
+
FROM time_entries te
|
|
28
|
+
WHERE te.msp_id = $1
|
|
29
|
+
AND te.time_start >= $2::date AND te.time_start <= $3::date
|
|
30
|
+
${clientFilter}${buildTimeEntriesFilter(filters)}
|
|
31
|
+
GROUP BY te.company
|
|
32
|
+
HAVING SUM(te.actual_hours) > 0
|
|
31
33
|
ORDER BY total_cost DESC
|
|
32
34
|
LIMIT 25`, queryParams, mspId);
|
|
33
35
|
// Agreement coverage
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-profitability.js","sourceRoot":"","sources":["../../../src/tools/cfo/client-profitability.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"client-profitability.js","sourceRoot":"","sources":["../../../src/tools/cfo/client-profitability.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAQzE,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,KAAa,EAAE,MAA2B;IACrF,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACrG,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACvE,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;IAExC,IAAI,YAAY,GAAG,EAAE,CAAC;IACtB,MAAM,WAAW,GAAc,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IACzD,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACtB,YAAY,GAAG,uCAAuC,CAAC;QACvD,WAAW,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,UAAU,GAAG,CAAC,CAAC;IAC7C,CAAC;IAED,2BAA2B;IAC3B,MAAM,OAAO,GAAG,MAAM,QAAQ,CAC5B;;;;;;;;;;;;;;;;SAgBK,YAAY,GAAG,sBAAsB,CAAC,OAAO,CAAC;;;;cAIzC,EACV,WAAW,EACX,KAAK,CACN,CAAC;IAEF,qBAAqB;IACrB,MAAM,UAAU,GAAG,MAAM,QAAQ,CAC/B;;;;2BAIuB,EACvB,CAAC,KAAK,CAAC,EACP,KAAK,CACN,CAAC;IAEF,+CAA+C;IAC/C,MAAM,YAAY,GAA6B,EAAE,CAAC;IAClD,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;QACpC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC;YAAE,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;QACjD,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAC/C,CAAC;IAED,qCAAqC;IACrC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACjC,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;QACnC,MAAM,gBAAgB,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QAClD,OAAO;YACL,GAAG,CAAC;YACJ,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG;YACxD,aAAa,EAAE,gBAAgB,CAAC,MAAM,GAAG,CAAC;YAC1C,eAAe,EAAE,gBAAgB;SAClC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,gBAAgB;IAChB,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;IACzE,MAAM,uBAAuB,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;IAEzE,OAAO;QACL,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE;QACtC,OAAO,EAAE,QAAQ;QACjB,OAAO,EAAE;YACP,aAAa,EAAE,QAAQ,CAAC,MAAM;YAC9B,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,GAAG,CAAC,GAAG,GAAG;YAC7C,mBAAmB,EAAE,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;YACpG,yBAAyB,EAAE,uBAAuB,CAAC,MAAM;YACzD,sBAAsB,EAAE,IAAI,CAAC,KAAK,CAChC,uBAAuB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,GAAG,GAAG,CAC5E,GAAG,GAAG;SACR;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"financial-overview.d.ts","sourceRoot":"","sources":["../../../src/tools/cfo/financial-overview.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"financial-overview.d.ts","sourceRoot":"","sources":["../../../src/tools/cfo/financial-overview.ts"],"names":[],"mappings":"AAGA,UAAU,cAAc;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiF/E"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { queryRLS } from "../../db/gateway-client.js";
|
|
2
|
+
import { getFilters, buildTimeEntriesFilter, buildCompanyExclusionFilter } from "../../db/filters.js";
|
|
2
3
|
export async function cfoFinancialOverview(mspId, params) {
|
|
3
4
|
const fromDate = params.fromDate ?? new Date(Date.now() - 90 * 86400000).toISOString().split("T")[0];
|
|
4
5
|
const toDate = params.toDate ?? new Date().toISOString().split("T")[0];
|
|
6
|
+
const filters = await getFilters(mspId);
|
|
5
7
|
const laborCost = await queryRLS(`SELECT SUM(actual_hours)::numeric as total_hours,
|
|
6
8
|
SUM(billable_hours)::numeric as billable_hours,
|
|
7
9
|
SUM(non_billable_hours)::numeric as non_billable_hours,
|
|
@@ -13,18 +15,18 @@ export async function cfoFinancialOverview(mspId, params) {
|
|
|
13
15
|
THEN ROUND((SUM(billable_hours) / SUM(actual_hours)) * 100, 1)
|
|
14
16
|
ELSE 0
|
|
15
17
|
END as utilization_pct
|
|
16
|
-
FROM time_entries
|
|
18
|
+
FROM time_entries te
|
|
17
19
|
WHERE msp_id = $1
|
|
18
|
-
AND time_start >= $2::date AND time_start <= $3::date`, [mspId, fromDate, toDate], mspId);
|
|
20
|
+
AND time_start >= $2::date AND time_start <= $3::date${buildTimeEntriesFilter(filters)}`, [mspId, fromDate, toDate], mspId);
|
|
19
21
|
let outOfScope = { tickets: 0, dollars: 0 };
|
|
20
22
|
try {
|
|
21
23
|
const oos = await queryRLS(`SELECT COUNT(DISTINCT ticket_id)::int as tickets,
|
|
22
24
|
COALESCE(SUM(billable_amount), 0)::numeric as dollars
|
|
23
|
-
FROM agents.billing_classifications
|
|
25
|
+
FROM agents.billing_classifications bc
|
|
24
26
|
WHERE msp_id::uuid = $1::uuid
|
|
25
27
|
AND classification = 'out_of_scope'
|
|
26
28
|
AND billable_amount > 0
|
|
27
|
-
AND resolved_date >= $2::date AND resolved_date <= $3::date`, [mspId, fromDate, toDate], mspId);
|
|
29
|
+
AND resolved_date >= $2::date AND resolved_date <= $3::date${buildCompanyExclusionFilter(filters)}`, [mspId, fromDate, toDate], mspId);
|
|
28
30
|
outOfScope = {
|
|
29
31
|
tickets: Number(oos[0]?.tickets) || 0,
|
|
30
32
|
dollars: Number(oos[0]?.dollars) || 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"financial-overview.js","sourceRoot":"","sources":["../../../src/tools/cfo/financial-overview.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"financial-overview.js","sourceRoot":"","sources":["../../../src/tools/cfo/financial-overview.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"labor-cost-trend.d.ts","sourceRoot":"","sources":["../../../src/tools/cfo/labor-cost-trend.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"labor-cost-trend.d.ts","sourceRoot":"","sources":["../../../src/tools/cfo/labor-cost-trend.ts"],"names":[],"mappings":"AAGA,UAAU,WAAW;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW;;;;;;;;GAgFzE"}
|
|
@@ -1,45 +1,47 @@
|
|
|
1
1
|
import { queryRLS } from "../../db/gateway-client.js";
|
|
2
|
+
import { getFilters, buildTimeEntriesFilter } from "../../db/filters.js";
|
|
2
3
|
export async function cfoLaborCostTrend(mspId, params) {
|
|
3
4
|
const months = params.months ?? 6;
|
|
4
5
|
const fromDate = new Date(Date.now() - months * 30 * 86400000).toISOString().split("T")[0];
|
|
6
|
+
const filters = await getFilters(mspId);
|
|
5
7
|
// Monthly cost and hours trend
|
|
6
|
-
const monthly = await queryRLS(`SELECT TO_CHAR(time_start, 'YYYY-MM') as month,
|
|
7
|
-
SUM(actual_hours)::numeric as total_hours,
|
|
8
|
-
SUM(billable_hours)::numeric as billable_hours,
|
|
9
|
-
SUM(total_cost)::numeric as total_cost,
|
|
10
|
-
COUNT(DISTINCT ticket_id)::int as tickets,
|
|
11
|
-
COUNT(DISTINCT member_name)::int as active_techs,
|
|
12
|
-
COUNT(DISTINCT company)::int as active_clients,
|
|
13
|
-
CASE WHEN SUM(actual_hours) > 0
|
|
14
|
-
THEN ROUND((SUM(billable_hours) / SUM(actual_hours)) * 100, 1)
|
|
8
|
+
const monthly = await queryRLS(`SELECT TO_CHAR(te.time_start, 'YYYY-MM') as month,
|
|
9
|
+
SUM(te.actual_hours)::numeric as total_hours,
|
|
10
|
+
SUM(te.billable_hours)::numeric as billable_hours,
|
|
11
|
+
SUM(te.total_cost)::numeric as total_cost,
|
|
12
|
+
COUNT(DISTINCT te.ticket_id)::int as tickets,
|
|
13
|
+
COUNT(DISTINCT te.member_name)::int as active_techs,
|
|
14
|
+
COUNT(DISTINCT te.company)::int as active_clients,
|
|
15
|
+
CASE WHEN SUM(te.actual_hours) > 0
|
|
16
|
+
THEN ROUND((SUM(te.billable_hours) / SUM(te.actual_hours)) * 100, 1)
|
|
15
17
|
ELSE 0
|
|
16
18
|
END as utilization_pct
|
|
17
|
-
FROM time_entries
|
|
18
|
-
WHERE msp_id = $1
|
|
19
|
-
AND time_start >= $2::date
|
|
20
|
-
GROUP BY TO_CHAR(time_start, 'YYYY-MM')
|
|
19
|
+
FROM time_entries te
|
|
20
|
+
WHERE te.msp_id = $1
|
|
21
|
+
AND te.time_start >= $2::date${buildTimeEntriesFilter(filters)}
|
|
22
|
+
GROUP BY TO_CHAR(te.time_start, 'YYYY-MM')
|
|
21
23
|
ORDER BY month`, [mspId, fromDate], mspId);
|
|
22
24
|
// Cost by department trend
|
|
23
|
-
const byDepartment = await queryRLS(`SELECT department,
|
|
24
|
-
SUM(actual_hours)::numeric as hours,
|
|
25
|
-
SUM(total_cost)::numeric as cost,
|
|
26
|
-
COUNT(DISTINCT ticket_id)::int as tickets
|
|
27
|
-
FROM time_entries
|
|
28
|
-
WHERE msp_id = $1
|
|
29
|
-
AND time_start >= $2::date
|
|
30
|
-
AND department IS NOT NULL
|
|
31
|
-
GROUP BY department
|
|
25
|
+
const byDepartment = await queryRLS(`SELECT te.department,
|
|
26
|
+
SUM(te.actual_hours)::numeric as hours,
|
|
27
|
+
SUM(te.total_cost)::numeric as cost,
|
|
28
|
+
COUNT(DISTINCT te.ticket_id)::int as tickets
|
|
29
|
+
FROM time_entries te
|
|
30
|
+
WHERE te.msp_id = $1
|
|
31
|
+
AND te.time_start >= $2::date
|
|
32
|
+
AND te.department IS NOT NULL${buildTimeEntriesFilter(filters)}
|
|
33
|
+
GROUP BY te.department
|
|
32
34
|
ORDER BY cost DESC`, [mspId, fromDate], mspId);
|
|
33
35
|
// Cost by work type (top categories)
|
|
34
|
-
const byWorkType = await queryRLS(`SELECT work_type,
|
|
35
|
-
SUM(actual_hours)::numeric as hours,
|
|
36
|
-
SUM(total_cost)::numeric as cost,
|
|
37
|
-
COUNT(DISTINCT ticket_id)::int as tickets
|
|
38
|
-
FROM time_entries
|
|
39
|
-
WHERE msp_id = $1
|
|
40
|
-
AND time_start >= $2::date
|
|
41
|
-
AND work_type IS NOT NULL
|
|
42
|
-
GROUP BY work_type
|
|
36
|
+
const byWorkType = await queryRLS(`SELECT te.work_type,
|
|
37
|
+
SUM(te.actual_hours)::numeric as hours,
|
|
38
|
+
SUM(te.total_cost)::numeric as cost,
|
|
39
|
+
COUNT(DISTINCT te.ticket_id)::int as tickets
|
|
40
|
+
FROM time_entries te
|
|
41
|
+
WHERE te.msp_id = $1
|
|
42
|
+
AND te.time_start >= $2::date
|
|
43
|
+
AND te.work_type IS NOT NULL${buildTimeEntriesFilter(filters)}
|
|
44
|
+
GROUP BY te.work_type
|
|
43
45
|
ORDER BY cost DESC
|
|
44
46
|
LIMIT 10`, [mspId, fromDate], mspId);
|
|
45
47
|
// Calculate month-over-month changes
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"labor-cost-trend.js","sourceRoot":"","sources":["../../../src/tools/cfo/labor-cost-trend.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"labor-cost-trend.js","sourceRoot":"","sources":["../../../src/tools/cfo/labor-cost-trend.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAMzE,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,KAAa,EAAE,MAAmB;IACxE,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC;IAClC,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3F,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;IAExC,+BAA+B;IAC/B,MAAM,OAAO,GAAG,MAAM,QAAQ,CAC5B;;;;;;;;;;;;;sCAakC,sBAAsB,CAAC,OAAO,CAAC;;oBAEjD,EAChB,CAAC,KAAK,EAAE,QAAQ,CAAC,EACjB,KAAK,CACN,CAAC;IAEF,2BAA2B;IAC3B,MAAM,YAAY,GAAG,MAAM,QAAQ,CACjC;;;;;;;sCAOkC,sBAAsB,CAAC,OAAO,CAAC;;wBAE7C,EACpB,CAAC,KAAK,EAAE,QAAQ,CAAC,EACjB,KAAK,CACN,CAAC;IAEF,qCAAqC;IACrC,MAAM,UAAU,GAAG,MAAM,QAAQ,CAC/B;;;;;;;qCAOiC,sBAAsB,CAAC,OAAO,CAAC;;;cAGtD,EACV,CAAC,KAAK,EAAE,QAAQ,CAAC,EACjB,KAAK,CACN,CAAC;IAEF,qCAAqC;IACrC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACjC,MAAM,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC3C,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACzD,OAAO;YACL,GAAG,CAAC;YACJ,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,CAAC,GAAG,GAAG;YACxC,cAAc,EAAE,IAAI,IAAI,QAAQ,GAAG,CAAC;gBAClC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,QAAQ,CAAC,GAAG,QAAQ,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE;gBACxD,CAAC,CAAC,IAAI;SACT,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,eAAe,EAAE,MAAM;QACvB,aAAa,EAAE,KAAK;QACpB,aAAa,EAAE,YAAY;QAC3B,YAAY,EAAE,UAAU;KACzB,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"revenue-leakage.d.ts","sourceRoot":"","sources":["../../../src/tools/cfo/revenue-leakage.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"revenue-leakage.d.ts","sourceRoot":"","sources":["../../../src/tools/cfo/revenue-leakage.ts"],"names":[],"mappings":"AAGA,UAAU,aAAa;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa;;;;;;;;;;;;;;;GAgF1E"}
|