- API client with Clover REST API v3 integration (OAuth2 + API key auth) - 50+ comprehensive tools across 10 categories: * Orders: list, get, create, update, delete, add/remove line items, discounts, payments, fire order * Inventory: items, categories, modifiers, stock management * Customers: CRUD, search, addresses, payment cards * Employees: CRUD, roles, shifts, clock in/out * Payments: list, get, refunds * Merchants: settings, devices, tender types * Discounts: CRUD operations * Taxes: CRUD, tax rates * Reports: sales summary, revenue by item/category, employee performance * Cash: cash drawer tracking and events - 18 React MCP apps with full UI: * Order management: dashboard, detail, grid * Inventory: dashboard, detail, category manager * Customer: detail, grid * Employee: dashboard, schedule * Payment history * Analytics: sales dashboard, revenue by item, revenue by category * Configuration: discount manager, tax manager, device manager * Cash drawer - Complete TypeScript types for Clover API - Pagination support with automatic result fetching - Comprehensive error handling - Full README with examples and setup guide
257 lines
7.9 KiB
TypeScript
257 lines
7.9 KiB
TypeScript
import { CloverClient } from '../clients/clover.js';
|
|
import {
|
|
CloverOrder,
|
|
CloverPayment,
|
|
SalesSummary,
|
|
RevenueByItem,
|
|
RevenueByCategory,
|
|
EmployeePerformance,
|
|
} from '../types/index.js';
|
|
|
|
export function createReportsTools(client: CloverClient) {
|
|
return {
|
|
clover_sales_summary: {
|
|
description: 'Get sales summary report for a date range',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
startDate: {
|
|
type: 'number',
|
|
description: 'Start date (Unix timestamp in milliseconds)',
|
|
},
|
|
endDate: {
|
|
type: 'number',
|
|
description: 'End date (Unix timestamp in milliseconds)',
|
|
},
|
|
},
|
|
required: ['startDate', 'endDate'],
|
|
},
|
|
handler: async (args: any) => {
|
|
// Fetch orders and payments in date range
|
|
const orders = await client.fetchPaginated<CloverOrder>('/orders', {
|
|
filter: `createdTime>=${args.startDate} AND createdTime<=${args.endDate}`,
|
|
expand: 'lineItems',
|
|
});
|
|
|
|
const payments = await client.fetchPaginated<CloverPayment>('/payments', {
|
|
filter: `createdTime>=${args.startDate} AND createdTime<=${args.endDate}`,
|
|
});
|
|
|
|
const totalSales = payments
|
|
.filter((p) => p.result === 'SUCCESS')
|
|
.reduce((sum, p) => sum + p.amount, 0);
|
|
|
|
const totalRefunds = payments
|
|
.filter((p) => p.refunds && p.refunds.length > 0)
|
|
.reduce(
|
|
(sum, p) =>
|
|
sum + p.refunds!.reduce((rsum, r) => rsum + r.amount, 0),
|
|
0
|
|
);
|
|
|
|
const totalTax = payments
|
|
.filter((p) => p.result === 'SUCCESS')
|
|
.reduce((sum, p) => sum + (p.taxAmount || 0), 0);
|
|
|
|
const totalTips = payments
|
|
.filter((p) => p.result === 'SUCCESS')
|
|
.reduce((sum, p) => sum + (p.tipAmount || 0), 0);
|
|
|
|
const summary: SalesSummary = {
|
|
totalSales,
|
|
totalOrders: orders.length,
|
|
averageOrderValue: orders.length > 0 ? totalSales / orders.length : 0,
|
|
totalRefunds,
|
|
netSales: totalSales - totalRefunds,
|
|
totalTax,
|
|
totalTips,
|
|
};
|
|
|
|
return summary;
|
|
},
|
|
},
|
|
|
|
clover_revenue_by_item: {
|
|
description: 'Get revenue breakdown by item for a date range',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
startDate: {
|
|
type: 'number',
|
|
description: 'Start date (Unix timestamp in milliseconds)',
|
|
},
|
|
endDate: {
|
|
type: 'number',
|
|
description: 'End date (Unix timestamp in milliseconds)',
|
|
},
|
|
},
|
|
required: ['startDate', 'endDate'],
|
|
},
|
|
handler: async (args: any) => {
|
|
const orders = await client.fetchPaginated<CloverOrder>('/orders', {
|
|
filter: `createdTime>=${args.startDate} AND createdTime<=${args.endDate}`,
|
|
expand: 'lineItems',
|
|
});
|
|
|
|
const itemStats = new Map<
|
|
string,
|
|
{ name: string; quantity: number; revenue: number }
|
|
>();
|
|
|
|
orders.forEach((order) => {
|
|
order.lineItems?.forEach((lineItem) => {
|
|
const itemId = lineItem.item.id;
|
|
const existing = itemStats.get(itemId) || {
|
|
name: lineItem.name,
|
|
quantity: 0,
|
|
revenue: 0,
|
|
};
|
|
|
|
existing.quantity += lineItem.unitQty || 1;
|
|
existing.revenue += lineItem.price * (lineItem.unitQty || 1);
|
|
itemStats.set(itemId, existing);
|
|
});
|
|
});
|
|
|
|
const results: RevenueByItem[] = Array.from(itemStats.entries()).map(
|
|
([itemId, stats]) => ({
|
|
itemId,
|
|
itemName: stats.name,
|
|
quantitySold: stats.quantity,
|
|
totalRevenue: stats.revenue,
|
|
averagePrice: stats.revenue / stats.quantity,
|
|
})
|
|
);
|
|
|
|
return { items: results, count: results.length };
|
|
},
|
|
},
|
|
|
|
clover_revenue_by_category: {
|
|
description: 'Get revenue breakdown by category for a date range',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
startDate: {
|
|
type: 'number',
|
|
description: 'Start date (Unix timestamp in milliseconds)',
|
|
},
|
|
endDate: {
|
|
type: 'number',
|
|
description: 'End date (Unix timestamp in milliseconds)',
|
|
},
|
|
},
|
|
required: ['startDate', 'endDate'],
|
|
},
|
|
handler: async (args: any) => {
|
|
// Fetch items with categories
|
|
const items = await client.fetchPaginated('/items', {
|
|
expand: 'categories',
|
|
});
|
|
|
|
const orders = await client.fetchPaginated<CloverOrder>('/orders', {
|
|
filter: `createdTime>=${args.startDate} AND createdTime<=${args.endDate}`,
|
|
expand: 'lineItems',
|
|
});
|
|
|
|
// Build item-to-category map
|
|
const itemCategories = new Map<string, any[]>();
|
|
items.forEach((item: any) => {
|
|
if (item.categories) {
|
|
itemCategories.set(item.id, item.categories);
|
|
}
|
|
});
|
|
|
|
const categoryStats = new Map<
|
|
string,
|
|
{ name: string; itemCount: Set<string>; revenue: number }
|
|
>();
|
|
|
|
orders.forEach((order) => {
|
|
order.lineItems?.forEach((lineItem) => {
|
|
const categories = itemCategories.get(lineItem.item.id) || [];
|
|
categories.forEach((cat: any) => {
|
|
const existing = categoryStats.get(cat.id) || {
|
|
name: cat.name,
|
|
itemCount: new Set<string>(),
|
|
revenue: 0,
|
|
};
|
|
|
|
existing.itemCount.add(lineItem.item.id);
|
|
existing.revenue += lineItem.price * (lineItem.unitQty || 1);
|
|
categoryStats.set(cat.id, existing);
|
|
});
|
|
});
|
|
});
|
|
|
|
const results: RevenueByCategory[] = Array.from(
|
|
categoryStats.entries()
|
|
).map(([categoryId, stats]) => ({
|
|
categoryId,
|
|
categoryName: stats.name,
|
|
itemCount: stats.itemCount.size,
|
|
totalRevenue: stats.revenue,
|
|
}));
|
|
|
|
return { categories: results, count: results.length };
|
|
},
|
|
},
|
|
|
|
clover_employee_performance: {
|
|
description: 'Get employee performance report for a date range',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
startDate: {
|
|
type: 'number',
|
|
description: 'Start date (Unix timestamp in milliseconds)',
|
|
},
|
|
endDate: {
|
|
type: 'number',
|
|
description: 'End date (Unix timestamp in milliseconds)',
|
|
},
|
|
},
|
|
required: ['startDate', 'endDate'],
|
|
},
|
|
handler: async (args: any) => {
|
|
const payments = await client.fetchPaginated<CloverPayment>('/payments', {
|
|
filter: `createdTime>=${args.startDate} AND createdTime<=${args.endDate}`,
|
|
expand: 'employee',
|
|
});
|
|
|
|
const employeeStats = new Map<
|
|
string,
|
|
{ name: string; sales: number; orderCount: number }
|
|
>();
|
|
|
|
payments
|
|
.filter((p) => p.result === 'SUCCESS')
|
|
.forEach((payment) => {
|
|
const empId = payment.employee.id;
|
|
const existing = employeeStats.get(empId) || {
|
|
name: (payment.employee as any).name || 'Unknown',
|
|
sales: 0,
|
|
orderCount: 0,
|
|
};
|
|
|
|
existing.sales += payment.amount;
|
|
existing.orderCount += 1;
|
|
employeeStats.set(empId, existing);
|
|
});
|
|
|
|
const results: EmployeePerformance[] = Array.from(
|
|
employeeStats.entries()
|
|
).map(([employeeId, stats]) => ({
|
|
employeeId,
|
|
employeeName: stats.name,
|
|
totalSales: stats.sales,
|
|
orderCount: stats.orderCount,
|
|
averageOrderValue: stats.sales / stats.orderCount,
|
|
}));
|
|
|
|
return { employees: results, count: results.length };
|
|
},
|
|
},
|
|
};
|
|
}
|