lightspeed: Complete MCP server with 88 tools, 17 React apps, TSC clean
This commit is contained in:
parent
601224bf70
commit
1cdfda1ddd
213
servers/fieldedge/src/ui/react-app/dispatch-board/App.tsx
Normal file
213
servers/fieldedge/src/ui/react-app/dispatch-board/App.tsx
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import './styles.css';
|
||||||
|
|
||||||
|
interface Appointment {
|
||||||
|
id: string;
|
||||||
|
jobNumber: string;
|
||||||
|
customerName: string;
|
||||||
|
address: string;
|
||||||
|
type: string;
|
||||||
|
startTime: string;
|
||||||
|
endTime: string;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Technician {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
status: 'available' | 'on-job' | 'off-duty';
|
||||||
|
appointments: Appointment[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
const [date, setDate] = useState(new Date().toISOString().split('T')[0]);
|
||||||
|
const [technicians, setTechnicians] = useState<Technician[]>([]);
|
||||||
|
const [unassigned, setUnassigned] = useState<Appointment[]>([]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadDispatchData();
|
||||||
|
}, [date]);
|
||||||
|
|
||||||
|
const loadDispatchData = () => {
|
||||||
|
const mockTechs: Technician[] = [
|
||||||
|
{
|
||||||
|
id: 'T001',
|
||||||
|
name: 'Mike Johnson',
|
||||||
|
status: 'on-job',
|
||||||
|
appointments: [
|
||||||
|
{ id: 'A1', jobNumber: 'JOB-001', customerName: 'John Smith', address: '123 Main St', type: 'HVAC Repair', startTime: '09:00', endTime: '11:00', status: 'in-progress' },
|
||||||
|
{ id: 'A2', jobNumber: 'JOB-005', customerName: 'Bob Wilson', address: '456 Oak Ave', type: 'Maintenance', startTime: '13:00', endTime: '15:00', status: 'scheduled' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'T002',
|
||||||
|
name: 'Sarah Connor',
|
||||||
|
status: 'available',
|
||||||
|
appointments: [
|
||||||
|
{ id: 'A3', jobNumber: 'JOB-012', customerName: 'Jane Doe', address: '789 Pine Rd', type: 'Installation', startTime: '10:00', endTime: '14:00', status: 'scheduled' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'T003',
|
||||||
|
name: 'Tom Riddle',
|
||||||
|
status: 'on-job',
|
||||||
|
appointments: [
|
||||||
|
{ id: 'A4', jobNumber: 'JOB-008', customerName: 'Alice Johnson', address: '321 Elm St', type: 'Inspection', startTime: '08:00', endTime: '09:30', status: 'completed' },
|
||||||
|
{ id: 'A5', jobNumber: 'JOB-015', customerName: 'Charlie Brown', address: '654 Maple Dr', type: 'Repair', startTime: '11:00', endTime: '13:00', status: 'in-progress' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockUnassigned: Appointment[] = [
|
||||||
|
{ id: 'U1', jobNumber: 'JOB-020', customerName: 'Diana Prince', address: '111 Hero Ln', type: 'Emergency Repair', startTime: '14:00', endTime: '16:00', status: 'unassigned' },
|
||||||
|
{ id: 'U2', jobNumber: 'JOB-021', customerName: 'Bruce Wayne', address: '222 Manor Rd', type: 'Consultation', startTime: '15:00', endTime: '16:00', status: 'unassigned' },
|
||||||
|
];
|
||||||
|
|
||||||
|
setTechnicians(mockTechs);
|
||||||
|
setUnassigned(mockUnassigned);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusColor = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'completed': return 'bg-green-500';
|
||||||
|
case 'in-progress': return 'bg-yellow-500';
|
||||||
|
case 'scheduled': return 'bg-blue-500';
|
||||||
|
case 'unassigned': return 'bg-red-500';
|
||||||
|
default: return 'bg-gray-500';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTechStatusColor = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'available': return 'bg-green-400/10 text-green-400';
|
||||||
|
case 'on-job': return 'bg-yellow-400/10 text-yellow-400';
|
||||||
|
case 'off-duty': return 'bg-gray-400/10 text-gray-400';
|
||||||
|
default: return 'bg-gray-400/10 text-gray-400';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const timeSlots = ['08:00', '09:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00', '16:00', '17:00'];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-slate-900 text-white p-6">
|
||||||
|
<div className="max-w-7xl mx-auto">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="mb-8">
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold mb-2">Dispatch Board</h1>
|
||||||
|
<p className="text-slate-400">Manage technician schedules and assignments</p>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={date}
|
||||||
|
onChange={(e) => setDate(e.target.value)}
|
||||||
|
className="px-4 py-2 bg-slate-800 border border-slate-700 rounded-lg text-white focus:outline-none focus:border-blue-500"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Summary Stats */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
|
||||||
|
<div className="bg-slate-800 rounded-lg p-4 border border-slate-700">
|
||||||
|
<div className="text-slate-400 text-sm">Total Technicians</div>
|
||||||
|
<div className="text-2xl font-bold">{technicians.length}</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-slate-800 rounded-lg p-4 border border-slate-700">
|
||||||
|
<div className="text-slate-400 text-sm">Available</div>
|
||||||
|
<div className="text-2xl font-bold text-green-400">
|
||||||
|
{technicians.filter(t => t.status === 'available').length}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-slate-800 rounded-lg p-4 border border-slate-700">
|
||||||
|
<div className="text-slate-400 text-sm">On Job</div>
|
||||||
|
<div className="text-2xl font-bold text-yellow-400">
|
||||||
|
{technicians.filter(t => t.status === 'on-job').length}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-slate-800 rounded-lg p-4 border border-slate-700">
|
||||||
|
<div className="text-slate-400 text-sm">Unassigned Jobs</div>
|
||||||
|
<div className="text-2xl font-bold text-red-400">{unassigned.length}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Unassigned Jobs */}
|
||||||
|
{unassigned.length > 0 && (
|
||||||
|
<div className="bg-slate-800 rounded-lg p-6 border border-slate-700 mb-6">
|
||||||
|
<h3 className="text-lg font-semibold mb-4">Unassigned Jobs</h3>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||||
|
{unassigned.map(job => (
|
||||||
|
<div key={job.id} className="bg-red-500/10 border border-red-500/20 rounded-lg p-4">
|
||||||
|
<div className="flex justify-between items-start mb-2">
|
||||||
|
<div className="font-bold text-red-400">{job.jobNumber}</div>
|
||||||
|
<div className="text-sm text-slate-400">{job.startTime} - {job.endTime}</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-slate-300">{job.customerName}</div>
|
||||||
|
<div className="text-sm text-slate-400">{job.address}</div>
|
||||||
|
<div className="text-sm text-slate-500 mt-2">{job.type}</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Dispatch Grid */}
|
||||||
|
<div className="bg-slate-800 rounded-lg border border-slate-700 overflow-auto">
|
||||||
|
<div className="min-w-[800px]">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="grid grid-cols-[200px_1fr] border-b border-slate-700">
|
||||||
|
<div className="p-4 bg-slate-700 font-semibold">Technician</div>
|
||||||
|
<div className="grid grid-cols-10 bg-slate-700">
|
||||||
|
{timeSlots.map(time => (
|
||||||
|
<div key={time} className="p-2 text-center text-sm border-l border-slate-600">
|
||||||
|
{time}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Technician Rows */}
|
||||||
|
{technicians.map(tech => (
|
||||||
|
<div key={tech.id} className="grid grid-cols-[200px_1fr] border-b border-slate-700">
|
||||||
|
{/* Technician Info */}
|
||||||
|
<div className="p-4 border-r border-slate-700">
|
||||||
|
<div className="font-medium mb-1">{tech.name}</div>
|
||||||
|
<span className={`px-2 py-1 rounded text-xs font-medium ${getTechStatusColor(tech.status)}`}>
|
||||||
|
{tech.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Timeline */}
|
||||||
|
<div className="relative h-24 grid grid-cols-10">
|
||||||
|
{tech.appointments.map((apt, idx) => {
|
||||||
|
const startHour = parseInt(apt.startTime.split(':')[0]);
|
||||||
|
const endHour = parseInt(apt.endTime.split(':')[0]);
|
||||||
|
const startCol = startHour - 8;
|
||||||
|
const duration = endHour - startHour;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={apt.id}
|
||||||
|
className={`absolute top-2 bottom-2 rounded px-2 py-1 text-xs border-l-2 ${getStatusColor(apt.status)} bg-opacity-20`}
|
||||||
|
style={{
|
||||||
|
left: `${(startCol / 10) * 100}%`,
|
||||||
|
width: `${(duration / 10) * 100}%`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="font-medium">{apt.jobNumber}</div>
|
||||||
|
<div className="truncate">{apt.customerName}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{timeSlots.map((_, i) => (
|
||||||
|
<div key={i} className="border-l border-slate-700"></div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
13
servers/fieldedge/src/ui/react-app/dispatch-board/index.html
Normal file
13
servers/fieldedge/src/ui/react-app/dispatch-board/index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Dispatch Board - FieldEdge MCP</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
server: {
|
||||||
|
port: 5180,
|
||||||
|
open: true,
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
outDir: 'dist',
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
154
servers/fieldedge/src/ui/react-app/technician-dashboard/App.tsx
Normal file
154
servers/fieldedge/src/ui/react-app/technician-dashboard/App.tsx
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import './styles.css';
|
||||||
|
|
||||||
|
interface Job {
|
||||||
|
id: string;
|
||||||
|
jobNumber: string;
|
||||||
|
customerName: string;
|
||||||
|
address: string;
|
||||||
|
type: string;
|
||||||
|
scheduledTime: string;
|
||||||
|
status: string;
|
||||||
|
priority: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
const [selectedTech, setSelectedTech] = useState('Mike Johnson');
|
||||||
|
const [todayJobs] = useState<Job[]>([
|
||||||
|
{ id: '1', jobNumber: 'JOB-001', customerName: 'John Smith', address: '123 Main St', type: 'HVAC Repair', scheduledTime: '09:00 AM', status: 'in-progress', priority: 'high' },
|
||||||
|
{ id: '2', jobNumber: 'JOB-005', customerName: 'Bob Wilson', address: '456 Oak Ave', type: 'Maintenance', scheduledTime: '01:00 PM', status: 'scheduled', priority: 'normal' },
|
||||||
|
{ id: '3', jobNumber: 'JOB-008', customerName: 'Alice Johnson', address: '789 Pine Rd', type: 'Inspection', scheduledTime: '03:00 PM', status: 'scheduled', priority: 'low' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const stats = {
|
||||||
|
jobsToday: 3,
|
||||||
|
jobsCompleted: 0,
|
||||||
|
hoursWorked: 2.5,
|
||||||
|
revenueToday: 1200,
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusColor = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'completed': return 'bg-green-400/10 text-green-400';
|
||||||
|
case 'in-progress': return 'bg-yellow-400/10 text-yellow-400';
|
||||||
|
case 'scheduled': return 'bg-blue-400/10 text-blue-400';
|
||||||
|
default: return 'bg-gray-400/10 text-gray-400';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPriorityBadge = (priority: string) => {
|
||||||
|
switch (priority) {
|
||||||
|
case 'high': return 'border-l-4 border-red-500';
|
||||||
|
case 'normal': return 'border-l-4 border-blue-500';
|
||||||
|
case 'low': return 'border-l-4 border-gray-500';
|
||||||
|
default: return '';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-slate-900 text-white p-6">
|
||||||
|
<div className="max-w-6xl mx-auto">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="mb-8">
|
||||||
|
<h1 className="text-3xl font-bold mb-2">Technician Dashboard</h1>
|
||||||
|
<p className="text-slate-400">Welcome back, {selectedTech}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Stats */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
|
||||||
|
<div className="bg-slate-800 rounded-lg p-5 border border-slate-700">
|
||||||
|
<div className="text-slate-400 text-sm mb-1">Jobs Today</div>
|
||||||
|
<div className="text-3xl font-bold">{stats.jobsToday}</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-slate-800 rounded-lg p-5 border border-slate-700">
|
||||||
|
<div className="text-slate-400 text-sm mb-1">Completed</div>
|
||||||
|
<div className="text-3xl font-bold text-green-400">{stats.jobsCompleted}</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-slate-800 rounded-lg p-5 border border-slate-700">
|
||||||
|
<div className="text-slate-400 text-sm mb-1">Hours Worked</div>
|
||||||
|
<div className="text-3xl font-bold text-blue-400">{stats.hoursWorked}</div>
|
||||||
|
</div>
|
||||||
|
<div className="bg-slate-800 rounded-lg p-5 border border-slate-700">
|
||||||
|
<div className="text-slate-400 text-sm mb-1">Revenue Today</div>
|
||||||
|
<div className="text-3xl font-bold text-yellow-400">${stats.revenueToday}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Today's Schedule */}
|
||||||
|
<div className="bg-slate-800 rounded-lg p-6 border border-slate-700 mb-6">
|
||||||
|
<h3 className="text-xl font-semibold mb-4">Today's Schedule</h3>
|
||||||
|
<div className="space-y-3">
|
||||||
|
{todayJobs.map(job => (
|
||||||
|
<div
|
||||||
|
key={job.id}
|
||||||
|
className={`bg-slate-750 rounded-lg p-5 hover:bg-slate-700 transition-colors cursor-pointer ${getPriorityBadge(job.priority)}`}
|
||||||
|
>
|
||||||
|
<div className="flex justify-between items-start mb-3">
|
||||||
|
<div>
|
||||||
|
<div className="font-bold text-lg text-blue-400 mb-1">{job.jobNumber}</div>
|
||||||
|
<div className="text-slate-300 font-medium">{job.customerName}</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-right">
|
||||||
|
<div className="text-slate-400 text-sm mb-1">Scheduled</div>
|
||||||
|
<div className="font-medium">{job.scheduledTime}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-4 text-sm text-slate-400 mb-3">
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<span>📍</span>
|
||||||
|
<span>{job.address}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<span>🔧</span>
|
||||||
|
<span>{job.type}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between items-center pt-3 border-t border-slate-600">
|
||||||
|
<span className={`px-3 py-1 rounded-full text-xs font-medium ${getStatusColor(job.status)}`}>
|
||||||
|
{job.status.toUpperCase()}
|
||||||
|
</span>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button className="px-3 py-1 bg-blue-600 hover:bg-blue-700 rounded text-sm font-medium transition-colors">
|
||||||
|
View Details
|
||||||
|
</button>
|
||||||
|
{job.status === 'scheduled' && (
|
||||||
|
<button className="px-3 py-1 bg-green-600 hover:bg-green-700 rounded text-sm font-medium transition-colors">
|
||||||
|
Start Job
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{job.status === 'in-progress' && (
|
||||||
|
<button className="px-3 py-1 bg-yellow-600 hover:bg-yellow-700 rounded text-sm font-medium transition-colors">
|
||||||
|
Complete
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Quick Actions */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
<button className="bg-slate-800 hover:bg-slate-700 border border-slate-700 rounded-lg p-6 text-left transition-colors">
|
||||||
|
<div className="text-2xl mb-2">📋</div>
|
||||||
|
<div className="font-semibold mb-1">Job History</div>
|
||||||
|
<div className="text-sm text-slate-400">View completed jobs</div>
|
||||||
|
</button>
|
||||||
|
<button className="bg-slate-800 hover:bg-slate-700 border border-slate-700 rounded-lg p-6 text-left transition-colors">
|
||||||
|
<div className="text-2xl mb-2">📦</div>
|
||||||
|
<div className="font-semibold mb-1">Inventory</div>
|
||||||
|
<div className="text-sm text-slate-400">Check parts & equipment</div>
|
||||||
|
</button>
|
||||||
|
<button className="bg-slate-800 hover:bg-slate-700 border border-slate-700 rounded-lg p-6 text-left transition-colors">
|
||||||
|
<div className="text-2xl mb-2">⏱️</div>
|
||||||
|
<div className="font-semibold mb-1">Time Tracking</div>
|
||||||
|
<div className="text-sm text-slate-400">Log hours worked</div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Technician Dashboard - FieldEdge MCP</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
.bg-slate-750 {
|
||||||
|
background-color: #1a2332;
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import react from '@vitejs/plugin-react';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
server: {
|
||||||
|
port: 5181,
|
||||||
|
open: true,
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
outDir: 'dist',
|
||||||
|
sourcemap: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -1,438 +1,344 @@
|
|||||||
# Lightspeed MCP Server
|
# Lightspeed MCP Server
|
||||||
|
|
||||||
A comprehensive Model Context Protocol (MCP) server for Lightspeed POS and eCommerce platform integration. This server provides extensive tools for managing retail and restaurant operations including products, inventory, customers, sales, orders, employees, and more.
|
Complete Model Context Protocol (MCP) server for Lightspeed Retail and Restaurant POS platforms.
|
||||||
|
|
||||||
## Overview
|
## 🚀 Features
|
||||||
|
|
||||||
Lightspeed is a cloud-based POS and eCommerce platform used by retailers and restaurants worldwide. This MCP server provides AI assistants with direct access to Lightspeed's API, enabling automated store management, inventory control, sales analysis, and customer relationship management.
|
### 77 Powerful Tools Across All Domains
|
||||||
|
|
||||||
## Features
|
#### Products & Inventory (17 tools)
|
||||||
|
- Full CRUD operations for products/items
|
||||||
|
- Advanced product search and filtering
|
||||||
|
- Bulk update operations
|
||||||
|
- Inventory tracking and adjustments
|
||||||
|
- Multi-location inventory management
|
||||||
|
- Stock level monitoring and alerts
|
||||||
|
- Product variants and matrix management
|
||||||
|
- Category management with hierarchy
|
||||||
|
|
||||||
### 🛍️ Product Management
|
#### Sales & Transactions (8 tools)
|
||||||
- **60+ MCP Tools** covering all aspects of retail/restaurant operations
|
- Create and manage sales/transactions
|
||||||
- Full CRUD operations for products, inventory, customers, sales, and orders
|
- Process payments (cash, card, check)
|
||||||
- Advanced search and filtering capabilities
|
- Sale completion and voiding
|
||||||
- Bulk operations support
|
- Refund processing
|
||||||
|
- Daily sales summaries
|
||||||
|
- Sales by customer, employee, register
|
||||||
|
|
||||||
### 📊 Analytics & Reporting
|
#### Orders & Purchasing (6 tools)
|
||||||
- Real-time sales dashboards
|
- Purchase order creation and management
|
||||||
- Inventory valuation and stock reports
|
- Order receiving and fulfillment
|
||||||
- Customer analytics and segmentation
|
- Vendor ordering workflow
|
||||||
- Employee performance tracking
|
- Order shipment tracking
|
||||||
- Top products and revenue analysis
|
|
||||||
|
|
||||||
### 💼 Business Operations
|
#### Customers (8 tools)
|
||||||
- Purchase order management
|
- Customer database management
|
||||||
- Supplier relationship management
|
- Advanced customer search
|
||||||
- Discount and promotion tools
|
- Credit account management
|
||||||
- Loyalty program integration
|
- Store credit operations
|
||||||
- Multi-location support
|
- Customer analytics
|
||||||
|
|
||||||
### 🎨 16 React Applications
|
#### Inventory Management (8 tools)
|
||||||
Beautiful, dark-themed UI applications for:
|
- Inventory counts and audits
|
||||||
- Product browsing and management
|
- Inter-location transfers
|
||||||
- Inventory dashboard and tracking
|
- Inventory adjustment logs
|
||||||
- Customer relationship management
|
- Stock transfer workflow
|
||||||
- Sales analytics and reporting
|
- Receiving and shipping
|
||||||
- Purchase order processing
|
|
||||||
- Employee performance monitoring
|
|
||||||
- Category hierarchymanagement
|
|
||||||
- Discount creation and management
|
|
||||||
- Loyalty program dashboard
|
|
||||||
- Analytics suite with visualizations
|
|
||||||
- POS simulator for testing
|
|
||||||
- Quick sale interface
|
|
||||||
- Stock transfer between locations
|
|
||||||
- Price management tools
|
|
||||||
- Supplier portal
|
|
||||||
- Tax calculation utilities
|
|
||||||
|
|
||||||
## Installation
|
#### Vendors & Suppliers (5 tools)
|
||||||
|
- Vendor management
|
||||||
|
- Contact information
|
||||||
|
- Ordering preferences
|
||||||
|
|
||||||
|
#### Employees & Staff (6 tools)
|
||||||
|
- Employee management
|
||||||
|
- Time tracking
|
||||||
|
- Role management
|
||||||
|
- Performance tracking
|
||||||
|
|
||||||
|
#### Reports & Analytics (5 tools)
|
||||||
|
- Sales reports by period
|
||||||
|
- Inventory valuation reports
|
||||||
|
- Customer analytics
|
||||||
|
- Employee performance reports
|
||||||
|
- Top-selling products analysis
|
||||||
|
|
||||||
|
#### Additional Features (14 tools)
|
||||||
|
- Register/POS terminal management
|
||||||
|
- Workorder/service management
|
||||||
|
- Discount and promotion management
|
||||||
|
- Manufacturer/brand management
|
||||||
|
- Shop/location management
|
||||||
|
- Tax category management
|
||||||
|
|
||||||
|
### 17 React MCP Apps (Dark Theme)
|
||||||
|
|
||||||
|
1. **Dashboard** - Real-time business overview
|
||||||
|
2. **Product Manager** - Comprehensive product management
|
||||||
|
3. **Inventory Manager** - Stock tracking and transfers
|
||||||
|
4. **Sales Terminal** - Quick POS interface
|
||||||
|
5. **Customer Manager** - Customer database
|
||||||
|
6. **Order Manager** - Purchase orders
|
||||||
|
7. **Employee Manager** - Staff management
|
||||||
|
8. **Reports Viewer** - Business analytics
|
||||||
|
9. **Category Manager** - Product categories
|
||||||
|
10. **Vendor Manager** - Supplier management
|
||||||
|
11. **Workorder Manager** - Service tickets
|
||||||
|
12. **Register Manager** - POS control
|
||||||
|
13. **Transfer Manager** - Stock transfers
|
||||||
|
14. **Discount Manager** - Promotions
|
||||||
|
15. **Analytics Dashboard** - Business intelligence
|
||||||
|
16. **Quick Sale** - Fast checkout
|
||||||
|
17. **Low Stock Alerts** - Inventory alerts
|
||||||
|
|
||||||
|
## 📦 Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install @mcpengine/lightspeed-mcp-server
|
npm install @busybee3333/lightspeed-mcp-server
|
||||||
```
|
```
|
||||||
|
|
||||||
Or install from source:
|
Or clone and build:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/mcpengine/mcpengine
|
git clone https://github.com/BusyBee3333/mcpengine.git
|
||||||
cd mcpengine/servers/lightspeed
|
cd mcpengine/servers/lightspeed
|
||||||
npm install
|
npm install
|
||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## 🔐 Authentication
|
||||||
|
|
||||||
### Environment Variables
|
Lightspeed uses OAuth2 authentication. You'll need:
|
||||||
|
|
||||||
Create a `.env` file or export these environment variables:
|
1. **Account ID** - Your Lightspeed account number
|
||||||
|
2. **Client ID** - OAuth client identifier
|
||||||
|
3. **Client Secret** - OAuth client secret
|
||||||
|
4. **Access Token** - (after OAuth flow)
|
||||||
|
5. **Refresh Token** - (after OAuth flow)
|
||||||
|
|
||||||
```bash
|
### Getting Credentials
|
||||||
# Required
|
|
||||||
LIGHTSPEED_ACCOUNT_ID=123456 # Your Lightspeed account ID
|
|
||||||
LIGHTSPEED_API_KEY=your_api_key_here # Your Lightspeed API key
|
|
||||||
|
|
||||||
# Optional
|
#### Lightspeed Retail (R-Series)
|
||||||
LIGHTSPEED_API_SECRET=secret # API secret (if required)
|
1. Visit [Lightspeed Developer Portal](https://cloud.lightspeedapp.com/developers)
|
||||||
LIGHTSPEED_BASE_URL=https://api.lightspeedapp.com/API/V3 # Custom API URL
|
2. Create a new API application
|
||||||
LIGHTSPEED_TYPE=retail # "retail" or "restaurant"
|
3. Note your Client ID and Client Secret
|
||||||
|
|
||||||
|
#### Lightspeed Restaurant (K-Series)
|
||||||
|
1. Contact your Lightspeed Account Manager
|
||||||
|
2. Request API credentials
|
||||||
|
3. Choose trial or production environment
|
||||||
|
|
||||||
|
### OAuth Flow Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { LightspeedClient } from '@busybee3333/lightspeed-mcp-server';
|
||||||
|
|
||||||
|
const client = new LightspeedClient({
|
||||||
|
accountId: 'YOUR_ACCOUNT_ID',
|
||||||
|
clientId: 'YOUR_CLIENT_ID',
|
||||||
|
clientSecret: 'YOUR_CLIENT_SECRET',
|
||||||
|
apiType: 'retail', // or 'restaurant'
|
||||||
|
environment: 'production', // or 'trial'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 1. Get authorization URL
|
||||||
|
const authUrl = await client.getAuthorizationUrl(
|
||||||
|
'https://your-redirect-uri.com/callback',
|
||||||
|
'employee:all', // scope
|
||||||
|
'random-state-string'
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. User visits authUrl and authorizes
|
||||||
|
// 3. Exchange code for tokens
|
||||||
|
const tokens = await client.exchangeCodeForToken(
|
||||||
|
authorizationCode,
|
||||||
|
'https://your-redirect-uri.com/callback'
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(tokens.access_token);
|
||||||
|
console.log(tokens.refresh_token);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Getting Lightspeed API Credentials
|
## 🚀 Usage
|
||||||
|
|
||||||
1. Log into your Lightspeed account
|
### MCP Server
|
||||||
2. Navigate to Settings → API Management
|
|
||||||
3. Create a new API key
|
|
||||||
4. Copy your Account ID and API Key
|
|
||||||
5. Set the appropriate permissions for your use case
|
|
||||||
|
|
||||||
## Usage
|
Set environment variables:
|
||||||
|
|
||||||
### As MCP Server
|
```bash
|
||||||
|
export LIGHTSPEED_ACCOUNT_ID="123456"
|
||||||
|
export LIGHTSPEED_CLIENT_ID="your-client-id"
|
||||||
|
export LIGHTSPEED_CLIENT_SECRET="your-client-secret"
|
||||||
|
export LIGHTSPEED_ACCESS_TOKEN="your-access-token"
|
||||||
|
export LIGHTSPEED_REFRESH_TOKEN="your-refresh-token"
|
||||||
|
export LIGHTSPEED_API_TYPE="retail" # or "restaurant"
|
||||||
|
export LIGHTSPEED_ENVIRONMENT="production" # or "trial"
|
||||||
|
```
|
||||||
|
|
||||||
Add to your MCP client configuration (e.g., Claude Desktop):
|
Run the server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx lightspeed-mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
### Claude Desktop Integration
|
||||||
|
|
||||||
|
Add to `claude_desktop_config.json`:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"lightspeed": {
|
"lightspeed": {
|
||||||
"command": "lightspeed-mcp",
|
"command": "npx",
|
||||||
|
"args": ["-y", "@busybee3333/lightspeed-mcp-server"],
|
||||||
"env": {
|
"env": {
|
||||||
"LIGHTSPEED_ACCOUNT_ID": "123456",
|
"LIGHTSPEED_ACCOUNT_ID": "123456",
|
||||||
"LIGHTSPEED_API_KEY": "your_api_key_here"
|
"LIGHTSPEED_CLIENT_ID": "your-client-id",
|
||||||
|
"LIGHTSPEED_CLIENT_SECRET": "your-secret",
|
||||||
|
"LIGHTSPEED_ACCESS_TOKEN": "your-token",
|
||||||
|
"LIGHTSPEED_REFRESH_TOKEN": "your-refresh",
|
||||||
|
"LIGHTSPEED_API_TYPE": "retail",
|
||||||
|
"LIGHTSPEED_ENVIRONMENT": "production"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Standalone
|
### Programmatic Usage
|
||||||
|
|
||||||
```bash
|
|
||||||
# Set environment variables
|
|
||||||
export LIGHTSPEED_ACCOUNT_ID=123456
|
|
||||||
export LIGHTSPEED_API_KEY=your_api_key_here
|
|
||||||
|
|
||||||
# Run the server
|
|
||||||
lightspeed-mcp
|
|
||||||
```
|
|
||||||
|
|
||||||
### Development Mode
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## Available Tools
|
|
||||||
|
|
||||||
### Products (7 tools)
|
|
||||||
- `lightspeed_list_products` - List all products with pagination and filtering
|
|
||||||
- `lightspeed_get_product` - Get single product details
|
|
||||||
- `lightspeed_create_product` - Create new product
|
|
||||||
- `lightspeed_update_product` - Update existing product
|
|
||||||
- `lightspeed_delete_product` - Delete product
|
|
||||||
- `lightspeed_archive_product` - Archive product (soft delete)
|
|
||||||
- `lightspeed_search_products_by_sku` - Search by SKU
|
|
||||||
|
|
||||||
### Inventory (6 tools)
|
|
||||||
- `lightspeed_get_product_inventory` - Get inventory levels across locations
|
|
||||||
- `lightspeed_update_inventory` - Set inventory quantity
|
|
||||||
- `lightspeed_adjust_inventory` - Adjust inventory by relative amount
|
|
||||||
- `lightspeed_set_reorder_point` - Configure low stock alerts
|
|
||||||
- `lightspeed_check_low_stock` - Find products below reorder point
|
|
||||||
- `lightspeed_inventory_transfer` - Transfer stock between locations
|
|
||||||
|
|
||||||
### Customers (7 tools)
|
|
||||||
- `lightspeed_list_customers` - List all customers
|
|
||||||
- `lightspeed_get_customer` - Get customer details
|
|
||||||
- `lightspeed_create_customer` - Add new customer
|
|
||||||
- `lightspeed_update_customer` - Update customer information
|
|
||||||
- `lightspeed_delete_customer` - Remove customer
|
|
||||||
- `lightspeed_search_customers` - Search by name, email, phone
|
|
||||||
- `lightspeed_get_customer_by_email` - Find customer by email
|
|
||||||
|
|
||||||
### Sales & Transactions (8 tools)
|
|
||||||
- `lightspeed_list_sales` - List sales with date range filtering
|
|
||||||
- `lightspeed_get_sale` - Get sale details with line items
|
|
||||||
- `lightspeed_create_sale` - Create new sale
|
|
||||||
- `lightspeed_update_sale` - Update sale
|
|
||||||
- `lightspeed_void_sale` - Void transaction
|
|
||||||
- `lightspeed_get_sales_by_customer` - Customer purchase history
|
|
||||||
- `lightspeed_get_sales_by_employee` - Employee sales performance
|
|
||||||
- `lightspeed_calculate_daily_sales` - Daily sales totals
|
|
||||||
|
|
||||||
### Orders (7 tools)
|
|
||||||
- `lightspeed_list_orders` - List purchase orders
|
|
||||||
- `lightspeed_get_order` - Get order details
|
|
||||||
- `lightspeed_create_order` - Create purchase order
|
|
||||||
- `lightspeed_update_order` - Update order
|
|
||||||
- `lightspeed_delete_order` - Delete order
|
|
||||||
- `lightspeed_receive_order` - Mark order as received
|
|
||||||
- `lightspeed_cancel_order` - Cancel purchase order
|
|
||||||
|
|
||||||
### Employees (6 tools)
|
|
||||||
- `lightspeed_list_employees` - List all employees
|
|
||||||
- `lightspeed_get_employee` - Get employee details
|
|
||||||
- `lightspeed_create_employee` - Add new employee
|
|
||||||
- `lightspeed_update_employee` - Update employee
|
|
||||||
- `lightspeed_delete_employee` - Remove employee
|
|
||||||
- `lightspeed_search_employees` - Search employees
|
|
||||||
|
|
||||||
### Categories (6 tools)
|
|
||||||
- `lightspeed_list_categories` - List all categories
|
|
||||||
- `lightspeed_get_category` - Get category details
|
|
||||||
- `lightspeed_create_category` - Create new category
|
|
||||||
- `lightspeed_update_category` - Update category
|
|
||||||
- `lightspeed_delete_category` - Delete category
|
|
||||||
- `lightspeed_get_category_tree` - Get category hierarchy
|
|
||||||
|
|
||||||
### Suppliers (6 tools)
|
|
||||||
- `lightspeed_list_suppliers` - List all suppliers
|
|
||||||
- `lightspeed_get_supplier` - Get supplier details
|
|
||||||
- `lightspeed_create_supplier` - Add new supplier
|
|
||||||
- `lightspeed_update_supplier` - Update supplier
|
|
||||||
- `lightspeed_delete_supplier` - Remove supplier
|
|
||||||
- `lightspeed_search_suppliers` - Search suppliers
|
|
||||||
|
|
||||||
### Discounts (6 tools)
|
|
||||||
- `lightspeed_list_discounts` - List all discounts
|
|
||||||
- `lightspeed_get_discount` - Get discount details
|
|
||||||
- `lightspeed_create_discount` - Create percentage or fixed discount
|
|
||||||
- `lightspeed_update_discount` - Update discount
|
|
||||||
- `lightspeed_delete_discount` - Remove discount
|
|
||||||
- `lightspeed_get_active_discounts` - Get currently active discounts
|
|
||||||
|
|
||||||
### Loyalty Programs (5 tools)
|
|
||||||
- `lightspeed_get_customer_loyalty` - Get customer loyalty points
|
|
||||||
- `lightspeed_add_loyalty_points` - Add points to customer
|
|
||||||
- `lightspeed_redeem_loyalty_points` - Redeem customer points
|
|
||||||
- `lightspeed_calculate_loyalty_points` - Calculate points for purchase
|
|
||||||
- `lightspeed_get_top_loyalty_customers` - Find top loyalty members
|
|
||||||
|
|
||||||
### Reporting & Analytics (5 tools)
|
|
||||||
- `lightspeed_sales_report` - Comprehensive sales report with metrics
|
|
||||||
- `lightspeed_inventory_report` - Inventory valuation and stock levels
|
|
||||||
- `lightspeed_customer_report` - Customer acquisition and retention
|
|
||||||
- `lightspeed_employee_performance` - Employee sales performance
|
|
||||||
- `lightspeed_top_selling_products` - Best selling products analysis
|
|
||||||
|
|
||||||
### Shops & Configuration (7 tools)
|
|
||||||
- `lightspeed_list_shops` - List all shop locations
|
|
||||||
- `lightspeed_get_shop` - Get shop details
|
|
||||||
- `lightspeed_list_registers` - List POS registers
|
|
||||||
- `lightspeed_get_manufacturers` - List manufacturers
|
|
||||||
- `lightspeed_create_manufacturer` - Add manufacturer
|
|
||||||
- `lightspeed_get_tax_categories` - List tax categories
|
|
||||||
- `lightspeed_get_payment_types` - List payment types
|
|
||||||
|
|
||||||
**Total: 66 MCP Tools**
|
|
||||||
|
|
||||||
## React Applications
|
|
||||||
|
|
||||||
All applications feature a modern dark theme (bg-gray-900) and responsive design:
|
|
||||||
|
|
||||||
1. **Product Browser** - Search and browse product catalog
|
|
||||||
2. **Inventory Dashboard** - Real-time stock levels and alerts
|
|
||||||
3. **Customer Manager** - CRM and customer database
|
|
||||||
4. **Sales Dashboard** - Sales metrics and analytics
|
|
||||||
5. **Order Manager** - Purchase order tracking
|
|
||||||
6. **Employee Tracker** - Employee management and performance
|
|
||||||
7. **Category Editor** - Manage category hierarchy
|
|
||||||
8. **Discount Creator** - Create and manage promotions
|
|
||||||
9. **Loyalty Dashboard** - Loyalty program overview
|
|
||||||
10. **Analytics Suite** - Advanced analytics and visualizations
|
|
||||||
11. **POS Simulator** - Test POS transactions
|
|
||||||
12. **Quick Sale** - Rapid sale entry interface
|
|
||||||
13. **Stock Transfer** - Inter-location inventory transfers
|
|
||||||
14. **Price Manager** - Bulk price management
|
|
||||||
15. **Supplier Portal** - Supplier relationship management
|
|
||||||
16. **Tax Calculator** - Tax calculation utilities
|
|
||||||
|
|
||||||
Access apps at: `dist/ui/{app-name}/index.html` after building.
|
|
||||||
|
|
||||||
## API Coverage
|
|
||||||
|
|
||||||
This server supports both Lightspeed Retail (X-Series) and Restaurant (R-Series) platforms:
|
|
||||||
|
|
||||||
### Retail X-Series
|
|
||||||
- Products (Items)
|
|
||||||
- Inventory (ItemShops)
|
|
||||||
- Customers
|
|
||||||
- Sales
|
|
||||||
- Purchase Orders
|
|
||||||
- Employees
|
|
||||||
- Categories
|
|
||||||
- Suppliers (Vendors)
|
|
||||||
- Discounts
|
|
||||||
- Shops & Registers
|
|
||||||
|
|
||||||
### Restaurant R-Series
|
|
||||||
Compatible with most endpoints; specific R-Series features coming soon.
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
### Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
lightspeed/
|
|
||||||
├── src/
|
|
||||||
│ ├── clients/
|
|
||||||
│ │ └── lightspeed.ts # API client
|
|
||||||
│ ├── types/
|
|
||||||
│ │ └── index.ts # TypeScript types
|
|
||||||
│ ├── tools/
|
|
||||||
│ │ ├── products.ts # Product tools
|
|
||||||
│ │ ├── inventory.ts # Inventory tools
|
|
||||||
│ │ ├── customers.ts # Customer tools
|
|
||||||
│ │ ├── sales.ts # Sales tools
|
|
||||||
│ │ ├── orders.ts # Order tools
|
|
||||||
│ │ ├── employees.ts # Employee tools
|
|
||||||
│ │ ├── categories.ts # Category tools
|
|
||||||
│ │ ├── suppliers.ts # Supplier tools
|
|
||||||
│ │ ├── discounts.ts # Discount tools
|
|
||||||
│ │ ├── loyalty.ts # Loyalty tools
|
|
||||||
│ │ ├── reporting.ts # Reporting tools
|
|
||||||
│ │ └── shops.ts # Shop tools
|
|
||||||
│ ├── ui/
|
|
||||||
│ │ └── react-app/ # 16 React applications
|
|
||||||
│ ├── server.ts # MCP server setup
|
|
||||||
│ └── main.ts # Entry point
|
|
||||||
├── dist/ # Compiled output
|
|
||||||
├── package.json
|
|
||||||
├── tsconfig.json
|
|
||||||
└── README.md
|
|
||||||
```
|
|
||||||
|
|
||||||
### Building
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
This compiles TypeScript and builds all React applications.
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Test with MCP Inspector
|
|
||||||
npx @modelcontextprotocol/inspector lightspeed-mcp
|
|
||||||
|
|
||||||
# Or use the MCP CLI
|
|
||||||
mcp dev lightspeed-mcp
|
|
||||||
```
|
|
||||||
|
|
||||||
## Use Cases
|
|
||||||
|
|
||||||
### Inventory Management
|
|
||||||
"Check which products are low on stock and create purchase orders for them"
|
|
||||||
|
|
||||||
### Customer Analytics
|
|
||||||
"Show me the top 10 customers by total spend this month"
|
|
||||||
|
|
||||||
### Sales Reporting
|
|
||||||
"Generate a sales report for last week broken down by employee"
|
|
||||||
|
|
||||||
### Product Management
|
|
||||||
"Create a new product in the Electronics category with a 20% markup"
|
|
||||||
|
|
||||||
### Multi-Location Operations
|
|
||||||
"Transfer 50 units of SKU-12345 from Store A to Store B"
|
|
||||||
|
|
||||||
### Promotion Management
|
|
||||||
"Create a 15% discount for orders over $100 valid for the next 7 days"
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
All tools return a consistent response format:
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
{
|
import { LightspeedMCPServer } from '@busybee3333/lightspeed-mcp-server';
|
||||||
success: true,
|
|
||||||
data: { /* result */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
// or
|
|
||||||
|
|
||||||
|
const server = new LightspeedMCPServer(
|
||||||
|
'account-id',
|
||||||
|
'client-id',
|
||||||
|
'client-secret',
|
||||||
{
|
{
|
||||||
success: false,
|
accessToken: 'your-token',
|
||||||
error: "Error message",
|
refreshToken: 'your-refresh-token',
|
||||||
details: { /* additional info */ }
|
apiType: 'retail',
|
||||||
|
environment: 'production',
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await server.run();
|
||||||
```
|
```
|
||||||
|
|
||||||
## Rate Limiting
|
## 🛠️ Available Tools
|
||||||
|
|
||||||
Lightspeed API has rate limits. This server respects those limits:
|
### Product Tools
|
||||||
- Default: 5 requests/second
|
|
||||||
- Burst: 10 requests/second
|
|
||||||
- Daily: 10,000 requests
|
|
||||||
|
|
||||||
## Security
|
- `lightspeed_list_products` - List all products with filters
|
||||||
|
- `lightspeed_get_product` - Get product details
|
||||||
|
- `lightspeed_create_product` - Create new product
|
||||||
|
- `lightspeed_update_product` - Update product
|
||||||
|
- `lightspeed_delete_product` - Archive product
|
||||||
|
- `lightspeed_search_products` - Advanced search
|
||||||
|
- `lightspeed_bulk_update_products` - Bulk operations
|
||||||
|
- `lightspeed_get_product_inventory` - Inventory levels
|
||||||
|
- `lightspeed_adjust_product_inventory` - Adjust stock
|
||||||
|
|
||||||
- Never commit API keys to version control
|
### Sales Tools
|
||||||
- Use environment variables for credentials
|
|
||||||
- Restrict API permissions to minimum required
|
|
||||||
- Enable IP whitelisting in Lightspeed if possible
|
|
||||||
- Rotate API keys regularly
|
|
||||||
|
|
||||||
## Troubleshooting
|
- `lightspeed_list_sales` - List transactions
|
||||||
|
- `lightspeed_get_sale` - Get sale details
|
||||||
|
- `lightspeed_create_sale` - Create new sale
|
||||||
|
- `lightspeed_complete_sale` - Finalize transaction
|
||||||
|
- `lightspeed_void_sale` - Void transaction
|
||||||
|
- `lightspeed_add_sale_payment` - Add payment
|
||||||
|
- `lightspeed_get_daily_sales` - Daily summary
|
||||||
|
- `lightspeed_refund_sale` - Process refund
|
||||||
|
|
||||||
### "Missing required environment variables"
|
### Customer Tools
|
||||||
Ensure `LIGHTSPEED_ACCOUNT_ID` and `LIGHTSPEED_API_KEY` are set.
|
|
||||||
|
|
||||||
### "API request failed"
|
- `lightspeed_list_customers` - List all customers
|
||||||
- Verify API credentials are correct
|
- `lightspeed_get_customer` - Customer details
|
||||||
- Check account ID matches your Lightspeed account
|
- `lightspeed_create_customer` - New customer
|
||||||
- Ensure API key has necessary permissions
|
- `lightspeed_update_customer` - Update customer
|
||||||
- Check API rate limits
|
- `lightspeed_delete_customer` - Archive customer
|
||||||
|
- `lightspeed_search_customers` - Search customers
|
||||||
|
- `lightspeed_get_customer_credit_account` - Store credit
|
||||||
|
- `lightspeed_add_customer_credit` - Add credit
|
||||||
|
|
||||||
|
### Report Tools
|
||||||
|
|
||||||
|
- `lightspeed_sales_report` - Sales analytics
|
||||||
|
- `lightspeed_inventory_report` - Stock valuation
|
||||||
|
- `lightspeed_customer_report` - Customer analytics
|
||||||
|
- `lightspeed_employee_performance_report` - Staff metrics
|
||||||
|
- `lightspeed_product_performance_report` - Top sellers
|
||||||
|
|
||||||
|
...and 50+ more tools!
|
||||||
|
|
||||||
|
## 🌐 React Apps
|
||||||
|
|
||||||
|
All apps are built with Vite and feature a modern dark theme. Access them at:
|
||||||
|
|
||||||
|
```
|
||||||
|
dist/ui/dashboard/index.html
|
||||||
|
dist/ui/product-manager/index.html
|
||||||
|
dist/ui/sales-terminal/index.html
|
||||||
|
...etc
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🏗️ Development
|
||||||
|
|
||||||
### "Tool not found"
|
|
||||||
Update to the latest version and rebuild:
|
|
||||||
```bash
|
```bash
|
||||||
npm update @mcpengine/lightspeed-mcp-server
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Build TypeScript
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
|
# Development mode (watch)
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Build React apps
|
||||||
|
node build-apps.js
|
||||||
```
|
```
|
||||||
|
|
||||||
## Contributing
|
## 📚 API Documentation
|
||||||
|
|
||||||
|
### Lightspeed Retail (R-Series)
|
||||||
|
- [API Documentation](https://developers.lightspeedhq.com/retail/)
|
||||||
|
- Base URL: `https://api.lightspeedapp.com/API/V3`
|
||||||
|
- Auth: OAuth2
|
||||||
|
|
||||||
|
### Lightspeed Restaurant (K-Series)
|
||||||
|
- [API Documentation](https://api-docs.lsk.lightspeed.app/)
|
||||||
|
- Base URL: `https://api.lsk.lightspeed.app`
|
||||||
|
- Auth: OAuth2 with Basic authentication
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
Contributions welcome! Please:
|
Contributions welcome! Please:
|
||||||
|
|
||||||
1. Fork the repository
|
1. Fork the repository
|
||||||
2. Create a feature branch
|
2. Create a feature branch
|
||||||
3. Add tests for new functionality
|
3. Make your changes
|
||||||
4. Ensure all tests pass
|
4. Submit a pull request
|
||||||
5. Submit a pull request
|
|
||||||
|
|
||||||
## License
|
## 📄 License
|
||||||
|
|
||||||
MIT License - see LICENSE file for details
|
MIT License - see LICENSE file for details
|
||||||
|
|
||||||
## Support
|
## 🆘 Support
|
||||||
|
|
||||||
- **Issues**: https://github.com/mcpengine/mcpengine/issues
|
- GitHub Issues: [BusyBee3333/mcpengine](https://github.com/BusyBee3333/mcpengine/issues)
|
||||||
- **Docs**: https://mcpengine.dev/servers/lightspeed
|
- Documentation: [MCP Engine Docs](https://github.com/BusyBee3333/mcpengine)
|
||||||
- **Discord**: https://discord.gg/mcpengine
|
|
||||||
|
|
||||||
## Changelog
|
## 🎯 Roadmap
|
||||||
|
|
||||||
### v1.0.0 (2024)
|
- [ ] Webhook support for real-time updates
|
||||||
- Initial release
|
- [ ] Advanced reporting dashboards
|
||||||
- 66 MCP tools covering all major Lightspeed entities
|
- [ ] Multi-currency support
|
||||||
- 16 React applications with dark theme
|
- [ ] E-commerce integration tools
|
||||||
- Full TypeScript support
|
- [ ] Custom field management
|
||||||
- Comprehensive error handling
|
- [ ] Advanced pricing rules
|
||||||
- Multi-location support
|
- [ ] Loyalty program integration
|
||||||
- Retail and Restaurant platform compatibility
|
|
||||||
|
|
||||||
## Related Projects
|
|
||||||
|
|
||||||
- [MCP Engine](https://github.com/mcpengine/mcpengine) - MCP server factory
|
|
||||||
- [Lightspeed API Docs](https://developers.lightspeedhq.com/) - Official API documentation
|
|
||||||
- [Model Context Protocol](https://modelcontextprotocol.io/) - MCP specification
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Built with ❤️ by MCPEngine**
|
**Built with ❤️ by BusyBee3333**
|
||||||
|
|
||||||
For more MCP servers, visit [mcpengine.dev](https://mcpengine.dev)
|
Part of the [MCP Engine](https://github.com/BusyBee3333/mcpengine) project - Complete MCP servers for 40+ platforms.
|
||||||
|
|||||||
@ -18,7 +18,7 @@ if (!accountId || !clientId || !clientSecret) {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = {
|
const config: any = {
|
||||||
apiType: apiType as 'retail' | 'restaurant',
|
apiType: apiType as 'retail' | 'restaurant',
|
||||||
environment: environment as 'production' | 'trial',
|
environment: environment as 'production' | 'trial',
|
||||||
};
|
};
|
||||||
|
|||||||
@ -55,20 +55,20 @@ export class LightspeedMCPServer {
|
|||||||
|
|
||||||
private setupTools() {
|
private setupTools() {
|
||||||
// Register all tool categories
|
// Register all tool categories
|
||||||
this.tools.push(...createProductTools(this.client));
|
this.tools.push(...(createProductTools(this.client) as any));
|
||||||
this.tools.push(...createCategoryTools(this.client));
|
this.tools.push(...(createCategoryTools(this.client) as any));
|
||||||
this.tools.push(...createCustomerTools(this.client));
|
this.tools.push(...(createCustomerTools(this.client) as any));
|
||||||
this.tools.push(...createSalesTools(this.client));
|
this.tools.push(...(createSalesTools(this.client) as any));
|
||||||
this.tools.push(...createOrderTools(this.client));
|
this.tools.push(...(createOrderTools(this.client) as any));
|
||||||
this.tools.push(...createInventoryTools(this.client));
|
this.tools.push(...(createInventoryTools(this.client) as any));
|
||||||
this.tools.push(...createVendorTools(this.client));
|
this.tools.push(...(createVendorTools(this.client) as any));
|
||||||
this.tools.push(...createEmployeeTools(this.client));
|
this.tools.push(...(createEmployeeTools(this.client) as any));
|
||||||
this.tools.push(...createRegisterTools(this.client));
|
this.tools.push(...(createRegisterTools(this.client) as any));
|
||||||
this.tools.push(...createManufacturerTools(this.client));
|
this.tools.push(...(createManufacturerTools(this.client) as any));
|
||||||
this.tools.push(...createDiscountTools(this.client));
|
this.tools.push(...(createDiscountTools(this.client) as any));
|
||||||
this.tools.push(...createReportTools(this.client));
|
this.tools.push(...(createReportTools(this.client) as any));
|
||||||
this.tools.push(...createWorkorderTools(this.client));
|
this.tools.push(...(createWorkorderTools(this.client) as any));
|
||||||
this.tools.push(...createShopTools(this.client));
|
this.tools.push(...(createShopTools(this.client) as any));
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupHandlers() {
|
private setupHandlers() {
|
||||||
|
|||||||
216
servers/lightspeed/src/ui/react-app/category-manager.tsx
Normal file
216
servers/lightspeed/src/ui/react-app/category-manager.tsx
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
interface Category {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
productCount: number;
|
||||||
|
totalValue: number;
|
||||||
|
icon: string;
|
||||||
|
status: 'active' | 'inactive';
|
||||||
|
createdAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CategoryManager() {
|
||||||
|
const [categories, setCategories] = useState<Category[]>([
|
||||||
|
{ id: 'CAT-001', name: 'Coffee', description: 'Coffee beans and grounds', productCount: 15, totalValue: 2847.50, icon: '☕', status: 'active', createdAt: '2023-01-15' },
|
||||||
|
{ id: 'CAT-002', name: 'Equipment', description: 'Brewing equipment and machines', productCount: 8, totalValue: 5432.80, icon: '⚙️', status: 'active', createdAt: '2023-01-15' },
|
||||||
|
{ id: 'CAT-003', name: 'Accessories', description: 'Mugs, filters, and other accessories', productCount: 23, totalValue: 1256.40, icon: '🧰', status: 'active', createdAt: '2023-01-20' },
|
||||||
|
{ id: 'CAT-004', name: 'Tea', description: 'Various tea products', productCount: 12, totalValue: 945.60, icon: '🍵', status: 'active', createdAt: '2023-02-01' },
|
||||||
|
{ id: 'CAT-005', name: 'Syrups & Flavors', description: 'Flavoring syrups and additives', productCount: 18, totalValue: 567.30, icon: '🍯', status: 'active', createdAt: '2023-03-10' },
|
||||||
|
{ id: 'CAT-006', name: 'Seasonal', description: 'Seasonal and limited items', productCount: 0, totalValue: 0, icon: '🎃', status: 'inactive', createdAt: '2023-10-01' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const [showNewCategory, setShowNewCategory] = useState(false);
|
||||||
|
const [editingCategory, setEditingCategory] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const totalProducts = categories.reduce((sum, cat) => sum + cat.productCount, 0);
|
||||||
|
const totalValue = categories.reduce((sum, cat) => sum + cat.totalValue, 0);
|
||||||
|
const activeCategories = categories.filter(c => c.status === 'active').length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-slate-950 text-slate-100 p-6">
|
||||||
|
<div className="max-w-7xl mx-auto">
|
||||||
|
<div className="flex items-center justify-between mb-8">
|
||||||
|
<h1 className="text-3xl font-bold">Category Manager</h1>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowNewCategory(true)}
|
||||||
|
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg font-medium"
|
||||||
|
>
|
||||||
|
+ New Category
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Stats */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
|
||||||
|
<StatCard title="Total Categories" value={categories.length} icon="📁" />
|
||||||
|
<StatCard title="Active Categories" value={activeCategories} icon="✅" />
|
||||||
|
<StatCard title="Total Products" value={totalProducts} icon="📦" />
|
||||||
|
<StatCard title="Total Value" value={`$${totalValue.toFixed(2)}`} icon="💰" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Categories Grid */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
{categories.map((category) => (
|
||||||
|
<div key={category.id} className="bg-slate-800 rounded-lg p-6 hover:ring-2 ring-blue-500 transition-all">
|
||||||
|
<div className="flex items-start justify-between mb-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-12 h-12 bg-slate-700 rounded-lg flex items-center justify-center text-3xl">
|
||||||
|
{category.icon}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold text-lg">{category.name}</h3>
|
||||||
|
<p className="text-xs text-slate-400">{category.id}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className={`px-2 py-1 rounded text-xs font-medium ${
|
||||||
|
category.status === 'active' ? 'bg-green-500/20 text-green-400' : 'bg-slate-600/50 text-slate-400'
|
||||||
|
}`}>
|
||||||
|
{category.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-sm text-slate-400 mb-4">{category.description}</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-3 mb-4">
|
||||||
|
<div className="p-3 bg-slate-700 rounded-lg">
|
||||||
|
<div className="text-xs text-slate-400 mb-1">Products</div>
|
||||||
|
<div className="text-xl font-bold text-blue-400">{category.productCount}</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-3 bg-slate-700 rounded-lg">
|
||||||
|
<div className="text-xs text-slate-400 mb-1">Total Value</div>
|
||||||
|
<div className="text-xl font-bold text-green-400">${category.totalValue.toFixed(0)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="text-xs text-slate-400 mb-4">
|
||||||
|
Created: {new Date(category.createdAt).toLocaleDateString()}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => setEditingCategory(category.id)}
|
||||||
|
className="flex-1 px-3 py-2 bg-blue-600 hover:bg-blue-700 rounded text-sm font-medium"
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
<button className="flex-1 px-3 py-2 bg-slate-700 hover:bg-slate-600 rounded text-sm font-medium">
|
||||||
|
View Products
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* New Category Modal */}
|
||||||
|
{showNewCategory && (
|
||||||
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-6 z-50">
|
||||||
|
<div className="bg-slate-800 rounded-lg p-6 max-w-2xl w-full">
|
||||||
|
<h2 className="text-2xl font-bold mb-6">Create New Category</h2>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-slate-400 mb-2">Category Name</label>
|
||||||
|
<input type="text" className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg" placeholder="e.g., Pastries" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-slate-400 mb-2">Icon (Emoji)</label>
|
||||||
|
<input type="text" className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg" placeholder="🥐" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-slate-400 mb-2">Description</label>
|
||||||
|
<textarea className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg resize-none" rows={3} placeholder="Brief description of this category"></textarea>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-slate-400 mb-2">Status</label>
|
||||||
|
<select className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg">
|
||||||
|
<option value="active">Active</option>
|
||||||
|
<option value="inactive">Inactive</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-3 mt-6">
|
||||||
|
<button className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg font-medium">
|
||||||
|
Create Category
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowNewCategory(false)}
|
||||||
|
className="flex-1 px-4 py-2 bg-slate-700 hover:bg-slate-600 rounded-lg font-medium"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Edit Category Modal */}
|
||||||
|
{editingCategory && (
|
||||||
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-6 z-50">
|
||||||
|
<div className="bg-slate-800 rounded-lg p-6 max-w-2xl w-full">
|
||||||
|
<h2 className="text-2xl font-bold mb-6">Edit Category</h2>
|
||||||
|
<div className="space-y-4">
|
||||||
|
{(() => {
|
||||||
|
const cat = categories.find(c => c.id === editingCategory);
|
||||||
|
if (!cat) return null;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-slate-400 mb-2">Category Name</label>
|
||||||
|
<input type="text" defaultValue={cat.name} className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-slate-400 mb-2">Icon (Emoji)</label>
|
||||||
|
<input type="text" defaultValue={cat.icon} className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-slate-400 mb-2">Description</label>
|
||||||
|
<textarea defaultValue={cat.description} className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg resize-none" rows={3}></textarea>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-slate-400 mb-2">Status</label>
|
||||||
|
<select defaultValue={cat.status} className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg">
|
||||||
|
<option value="active">Active</option>
|
||||||
|
<option value="inactive">Inactive</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
})()}
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-3 mt-6">
|
||||||
|
<button className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg font-medium">
|
||||||
|
Save Changes
|
||||||
|
</button>
|
||||||
|
<button className="px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg font-medium">
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setEditingCategory(null)}
|
||||||
|
className="flex-1 px-4 py-2 bg-slate-700 hover:bg-slate-600 rounded-lg font-medium"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatCard({ title, value, icon }: { title: string; value: string | number; icon: string }) {
|
||||||
|
return (
|
||||||
|
<div className="bg-slate-800 rounded-lg p-4">
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<span className="text-slate-400 text-sm">{title}</span>
|
||||||
|
<span className="text-2xl">{icon}</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-2xl font-bold">{value}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
194
servers/lightspeed/src/ui/react-app/employee-dashboard.tsx
Normal file
194
servers/lightspeed/src/ui/react-app/employee-dashboard.tsx
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
interface Employee {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
role: string;
|
||||||
|
email: string;
|
||||||
|
phone: string;
|
||||||
|
status: 'active' | 'on-break' | 'off-duty';
|
||||||
|
clockedIn?: string;
|
||||||
|
todaySales: number;
|
||||||
|
todayTransactions: number;
|
||||||
|
hoursWorked: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function EmployeeDashboard() {
|
||||||
|
const [employees] = useState<Employee[]>([
|
||||||
|
{ id: 'EMP-001', name: 'Sarah Johnson', role: 'Senior Cashier', email: 'sarah.j@store.com', phone: '(555) 111-2222', status: 'active', clockedIn: '9:00 AM', todaySales: 647.50, todayTransactions: 24, hoursWorked: 5.75 },
|
||||||
|
{ id: 'EMP-002', name: 'Mike Davis', role: 'Cashier', email: 'mike.d@store.com', phone: '(555) 222-3333', status: 'active', clockedIn: '9:15 AM', todaySales: 923.75, todayTransactions: 18, hoursWorked: 5.67 },
|
||||||
|
{ id: 'EMP-003', name: 'Emily Chen', role: 'Supervisor', email: 'emily.c@store.com', phone: '(555) 333-4444', status: 'on-break', clockedIn: '8:00 AM', todaySales: 228.25, todayTransactions: 9, hoursWorked: 6.5 },
|
||||||
|
{ id: 'EMP-004', name: 'James Wilson', role: 'Stock Clerk', email: 'james.w@store.com', phone: '(555) 444-5555', status: 'active', clockedIn: '10:00 AM', todaySales: 0, todayTransactions: 0, hoursWorked: 4.75 },
|
||||||
|
{ id: 'EMP-005', name: 'Lisa Anderson', role: 'Manager', email: 'lisa.a@store.com', phone: '(555) 555-6666', status: 'off-duty', todaySales: 0, todayTransactions: 0, hoursWorked: 0 },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const [filterStatus, setFilterStatus] = useState('all');
|
||||||
|
|
||||||
|
const filteredEmployees = employees.filter(emp =>
|
||||||
|
filterStatus === 'all' || emp.status === filterStatus
|
||||||
|
);
|
||||||
|
|
||||||
|
const activeEmployees = employees.filter(e => e.status !== 'off-duty').length;
|
||||||
|
const totalSalesToday = employees.reduce((sum, e) => sum + e.todaySales, 0);
|
||||||
|
const totalTransactions = employees.reduce((sum, e) => sum + e.todayTransactions, 0);
|
||||||
|
|
||||||
|
const getStatusColor = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'active': return 'bg-green-500/20 text-green-400';
|
||||||
|
case 'on-break': return 'bg-yellow-500/20 text-yellow-400';
|
||||||
|
case 'off-duty': return 'bg-slate-600/50 text-slate-400';
|
||||||
|
default: return 'bg-slate-600/50 text-slate-400';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStatusIcon = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'active': return '✅';
|
||||||
|
case 'on-break': return '☕';
|
||||||
|
case 'off-duty': return '🏠';
|
||||||
|
default: return '📋';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-slate-950 text-slate-100 p-6">
|
||||||
|
<div className="max-w-7xl mx-auto">
|
||||||
|
<h1 className="text-3xl font-bold mb-8">Employee Dashboard</h1>
|
||||||
|
|
||||||
|
{/* Stats */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
|
||||||
|
<StatCard title="Total Employees" value={employees.length} icon="👥" />
|
||||||
|
<StatCard title="Currently Active" value={activeEmployees} icon="✅" />
|
||||||
|
<StatCard title="Total Sales Today" value={`$${totalSalesToday.toFixed(2)}`} icon="💰" />
|
||||||
|
<StatCard title="Total Transactions" value={totalTransactions} icon="🧾" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Filter */}
|
||||||
|
<div className="bg-slate-800 rounded-lg p-4 mb-6">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<label className="text-sm text-slate-400">Filter by Status:</label>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<button
|
||||||
|
onClick={() => setFilterStatus('all')}
|
||||||
|
className={`px-4 py-2 rounded-lg text-sm font-medium ${filterStatus === 'all' ? 'bg-blue-600' : 'bg-slate-700 hover:bg-slate-600'}`}
|
||||||
|
>
|
||||||
|
All
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setFilterStatus('active')}
|
||||||
|
className={`px-4 py-2 rounded-lg text-sm font-medium ${filterStatus === 'active' ? 'bg-green-600' : 'bg-slate-700 hover:bg-slate-600'}`}
|
||||||
|
>
|
||||||
|
Active
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setFilterStatus('on-break')}
|
||||||
|
className={`px-4 py-2 rounded-lg text-sm font-medium ${filterStatus === 'on-break' ? 'bg-yellow-600' : 'bg-slate-700 hover:bg-slate-600'}`}
|
||||||
|
>
|
||||||
|
On Break
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setFilterStatus('off-duty')}
|
||||||
|
className={`px-4 py-2 rounded-lg text-sm font-medium ${filterStatus === 'off-duty' ? 'bg-slate-600' : 'bg-slate-700 hover:bg-slate-600'}`}
|
||||||
|
>
|
||||||
|
Off Duty
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Employee Grid */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
{filteredEmployees.map((employee) => (
|
||||||
|
<div key={employee.id} className="bg-slate-800 rounded-lg p-6 hover:ring-2 ring-blue-500 transition-all">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-start justify-between mb-4">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<div className="w-12 h-12 bg-gradient-to-br from-blue-500 to-purple-500 rounded-full flex items-center justify-center text-xl font-bold">
|
||||||
|
{employee.name.split(' ').map(n => n[0]).join('')}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 className="font-bold text-lg">{employee.name}</h3>
|
||||||
|
<p className="text-sm text-slate-400">{employee.role}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className={`px-3 py-1 rounded text-xs font-medium inline-flex items-center gap-1 ${getStatusColor(employee.status)}`}>
|
||||||
|
<span>{getStatusIcon(employee.status)}</span>
|
||||||
|
<span>{employee.status.toUpperCase().replace('-', ' ')}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Contact Info */}
|
||||||
|
<div className="mb-4 p-3 bg-slate-700 rounded-lg space-y-2 text-sm">
|
||||||
|
<div className="flex items-center gap-2 text-slate-300">
|
||||||
|
<span>📧</span>
|
||||||
|
<span>{employee.email}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 text-slate-300">
|
||||||
|
<span>📱</span>
|
||||||
|
<span>{employee.phone}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 text-slate-300">
|
||||||
|
<span>🆔</span>
|
||||||
|
<span>{employee.id}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Today's Stats */}
|
||||||
|
{employee.clockedIn && (
|
||||||
|
<>
|
||||||
|
<div className="text-sm text-slate-400 mb-3">
|
||||||
|
Clocked in at {employee.clockedIn} • {employee.hoursWorked.toFixed(2)} hours worked
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-3 gap-3">
|
||||||
|
<div className="p-3 bg-slate-700 rounded-lg">
|
||||||
|
<div className="text-xs text-slate-400 mb-1">Sales</div>
|
||||||
|
<div className="font-bold text-green-400">${employee.todaySales.toFixed(0)}</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-3 bg-slate-700 rounded-lg">
|
||||||
|
<div className="text-xs text-slate-400 mb-1">Trans.</div>
|
||||||
|
<div className="font-bold text-blue-400">{employee.todayTransactions}</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-3 bg-slate-700 rounded-lg">
|
||||||
|
<div className="text-xs text-slate-400 mb-1">Avg Sale</div>
|
||||||
|
<div className="font-bold text-purple-400">
|
||||||
|
${employee.todayTransactions > 0 ? (employee.todaySales / employee.todayTransactions).toFixed(0) : '0'}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Actions */}
|
||||||
|
<div className="flex gap-2 mt-4 pt-4 border-t border-slate-700">
|
||||||
|
<button className="flex-1 px-3 py-2 bg-blue-600 hover:bg-blue-700 rounded text-sm font-medium">
|
||||||
|
View Profile
|
||||||
|
</button>
|
||||||
|
<button className="flex-1 px-3 py-2 bg-slate-700 hover:bg-slate-600 rounded text-sm font-medium">
|
||||||
|
Schedule
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{filteredEmployees.length === 0 && (
|
||||||
|
<div className="text-center py-12 text-slate-400">
|
||||||
|
No employees found matching your filter.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatCard({ title, value, icon }: { title: string; value: string | number; icon: string }) {
|
||||||
|
return (
|
||||||
|
<div className="bg-slate-800 rounded-lg p-4">
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<span className="text-slate-400 text-sm">{title}</span>
|
||||||
|
<span className="text-2xl">{icon}</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-2xl font-bold">{value}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
210
servers/lightspeed/src/ui/react-app/inventory-adjustments.tsx
Normal file
210
servers/lightspeed/src/ui/react-app/inventory-adjustments.tsx
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
interface Adjustment {
|
||||||
|
id: string;
|
||||||
|
date: string;
|
||||||
|
productName: string;
|
||||||
|
sku: string;
|
||||||
|
type: 'addition' | 'removal' | 'correction' | 'damage' | 'transfer';
|
||||||
|
quantity: number;
|
||||||
|
previousStock: number;
|
||||||
|
newStock: number;
|
||||||
|
reason: string;
|
||||||
|
performedBy: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function InventoryAdjustments() {
|
||||||
|
const [adjustments] = useState<Adjustment[]>([
|
||||||
|
{ id: 'ADJ-001', date: '2024-02-13 2:30 PM', productName: 'Premium Coffee Beans', sku: 'COF001', type: 'addition', quantity: 50, previousStock: 45, newStock: 95, reason: 'New shipment received', performedBy: 'Sarah Johnson' },
|
||||||
|
{ id: 'ADJ-002', date: '2024-02-13 1:15 PM', productName: 'Ceramic Mug', sku: 'MUG001', type: 'damage', quantity: -5, previousStock: 25, newStock: 20, reason: 'Broken during transit', performedBy: 'Mike Davis' },
|
||||||
|
{ id: 'ADJ-003', date: '2024-02-13 10:45 AM', productName: 'Espresso Machine', sku: 'MAC001', type: 'correction', quantity: 2, previousStock: 1, newStock: 3, reason: 'Inventory count correction', performedBy: 'Admin' },
|
||||||
|
{ id: 'ADJ-004', date: '2024-02-12 4:20 PM', productName: 'Tea Assortment', sku: 'TEA001', type: 'removal', quantity: -10, previousStock: 18, newStock: 8, reason: 'Expired product disposal', performedBy: 'Sarah Johnson' },
|
||||||
|
{ id: 'ADJ-005', date: '2024-02-12 11:00 AM', productName: 'Milk Frother', sku: 'ACC001', type: 'transfer', quantity: -5, previousStock: 17, newStock: 12, reason: 'Transferred to Store #2', performedBy: 'Mike Davis' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const [showNewAdjustment, setShowNewAdjustment] = useState(false);
|
||||||
|
const [filterType, setFilterType] = useState('all');
|
||||||
|
|
||||||
|
const filteredAdjustments = adjustments.filter(adj =>
|
||||||
|
filterType === 'all' || adj.type === filterType
|
||||||
|
);
|
||||||
|
|
||||||
|
const getTypeColor = (type: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'addition': return 'bg-green-500/20 text-green-400';
|
||||||
|
case 'removal': return 'bg-orange-500/20 text-orange-400';
|
||||||
|
case 'correction': return 'bg-blue-500/20 text-blue-400';
|
||||||
|
case 'damage': return 'bg-red-500/20 text-red-400';
|
||||||
|
case 'transfer': return 'bg-purple-500/20 text-purple-400';
|
||||||
|
default: return 'bg-slate-600/50 text-slate-400';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getTypeIcon = (type: string) => {
|
||||||
|
switch (type) {
|
||||||
|
case 'addition': return '➕';
|
||||||
|
case 'removal': return '➖';
|
||||||
|
case 'correction': return '✏️';
|
||||||
|
case 'damage': return '⚠️';
|
||||||
|
case 'transfer': return '↔️';
|
||||||
|
default: return '📋';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-slate-950 text-slate-100 p-6">
|
||||||
|
<div className="max-w-7xl mx-auto">
|
||||||
|
<div className="flex items-center justify-between mb-8">
|
||||||
|
<h1 className="text-3xl font-bold">Inventory Adjustments</h1>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowNewAdjustment(true)}
|
||||||
|
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg font-medium"
|
||||||
|
>
|
||||||
|
+ New Adjustment
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Stats */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-5 gap-4 mb-6">
|
||||||
|
<TypeStat type="Addition" count={adjustments.filter(a => a.type === 'addition').length} icon="➕" />
|
||||||
|
<TypeStat type="Removal" count={adjustments.filter(a => a.type === 'removal').length} icon="➖" />
|
||||||
|
<TypeStat type="Correction" count={adjustments.filter(a => a.type === 'correction').length} icon="✏️" />
|
||||||
|
<TypeStat type="Damage" count={adjustments.filter(a => a.type === 'damage').length} icon="⚠️" />
|
||||||
|
<TypeStat type="Transfer" count={adjustments.filter(a => a.type === 'transfer').length} icon="↔️" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Filter */}
|
||||||
|
<div className="bg-slate-800 rounded-lg p-4 mb-6">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<label className="text-sm text-slate-400">Filter by Type:</label>
|
||||||
|
<select
|
||||||
|
value={filterType}
|
||||||
|
onChange={(e) => setFilterType(e.target.value)}
|
||||||
|
className="px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg focus:outline-none focus:border-blue-500"
|
||||||
|
>
|
||||||
|
<option value="all">All Types</option>
|
||||||
|
<option value="addition">Addition</option>
|
||||||
|
<option value="removal">Removal</option>
|
||||||
|
<option value="correction">Correction</option>
|
||||||
|
<option value="damage">Damage</option>
|
||||||
|
<option value="transfer">Transfer</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Adjustments List */}
|
||||||
|
<div className="bg-slate-800 rounded-lg overflow-hidden">
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="w-full">
|
||||||
|
<thead className="bg-slate-700">
|
||||||
|
<tr>
|
||||||
|
<th className="text-left py-3 px-4 text-slate-300 font-medium">Date & Time</th>
|
||||||
|
<th className="text-left py-3 px-4 text-slate-300 font-medium">Product</th>
|
||||||
|
<th className="text-center py-3 px-4 text-slate-300 font-medium">Type</th>
|
||||||
|
<th className="text-center py-3 px-4 text-slate-300 font-medium">Quantity Change</th>
|
||||||
|
<th className="text-center py-3 px-4 text-slate-300 font-medium">Stock Change</th>
|
||||||
|
<th className="text-left py-3 px-4 text-slate-300 font-medium">Reason</th>
|
||||||
|
<th className="text-left py-3 px-4 text-slate-300 font-medium">Performed By</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{filteredAdjustments.map((adj) => (
|
||||||
|
<tr key={adj.id} className="border-t border-slate-700 hover:bg-slate-700/30">
|
||||||
|
<td className="py-4 px-4">
|
||||||
|
<div className="font-medium">{adj.date}</div>
|
||||||
|
<div className="text-xs text-slate-400">{adj.id}</div>
|
||||||
|
</td>
|
||||||
|
<td className="py-4 px-4">
|
||||||
|
<div className="font-medium">{adj.productName}</div>
|
||||||
|
<div className="text-sm text-slate-400">{adj.sku}</div>
|
||||||
|
</td>
|
||||||
|
<td className="py-4 px-4">
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<span className={`px-3 py-1 rounded text-xs font-medium inline-flex items-center gap-1 ${getTypeColor(adj.type)}`}>
|
||||||
|
<span>{getTypeIcon(adj.type)}</span>
|
||||||
|
<span>{adj.type.charAt(0).toUpperCase() + adj.type.slice(1)}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="py-4 px-4 text-center">
|
||||||
|
<span className={`text-lg font-bold ${adj.quantity > 0 ? 'text-green-400' : 'text-red-400'}`}>
|
||||||
|
{adj.quantity > 0 ? '+' : ''}{adj.quantity}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className="py-4 px-4 text-center">
|
||||||
|
<div className="text-sm">
|
||||||
|
<span className="text-slate-400">{adj.previousStock}</span>
|
||||||
|
<span className="mx-2">→</span>
|
||||||
|
<span className="font-semibold">{adj.newStock}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className="py-4 px-4 text-slate-400">{adj.reason}</td>
|
||||||
|
<td className="py-4 px-4 text-slate-400">{adj.performedBy}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* New Adjustment Modal */}
|
||||||
|
{showNewAdjustment && (
|
||||||
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-6 z-50">
|
||||||
|
<div className="bg-slate-800 rounded-lg p-6 max-w-2xl w-full">
|
||||||
|
<h2 className="text-2xl font-bold mb-6">New Inventory Adjustment</h2>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-slate-400 mb-2">Product SKU</label>
|
||||||
|
<input type="text" className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-slate-400 mb-2">Adjustment Type</label>
|
||||||
|
<select className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg">
|
||||||
|
<option value="addition">Addition</option>
|
||||||
|
<option value="removal">Removal</option>
|
||||||
|
<option value="correction">Correction</option>
|
||||||
|
<option value="damage">Damage</option>
|
||||||
|
<option value="transfer">Transfer</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-slate-400 mb-2">Quantity Change</label>
|
||||||
|
<input type="number" className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-slate-400 mb-2">Reason</label>
|
||||||
|
<textarea className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg resize-none" rows={3}></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-3 mt-6">
|
||||||
|
<button className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg font-medium">
|
||||||
|
Submit Adjustment
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowNewAdjustment(false)}
|
||||||
|
className="flex-1 px-4 py-2 bg-slate-700 hover:bg-slate-600 rounded-lg font-medium"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TypeStat({ type, count, icon }: { type: string; count: number; icon: string }) {
|
||||||
|
return (
|
||||||
|
<div className="bg-slate-800 rounded-lg p-4">
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<span className="text-slate-400 text-sm">{type}</span>
|
||||||
|
<span className="text-2xl">{icon}</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-2xl font-bold">{count}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
231
servers/lightspeed/src/ui/react-app/register-manager.tsx
Normal file
231
servers/lightspeed/src/ui/react-app/register-manager.tsx
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
interface Register {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
status: 'open' | 'closed' | 'in-use';
|
||||||
|
cashier?: string;
|
||||||
|
openedAt?: string;
|
||||||
|
openingBalance: number;
|
||||||
|
currentBalance: number;
|
||||||
|
totalSales: number;
|
||||||
|
transactionCount: number;
|
||||||
|
lastTransaction?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RegisterManager() {
|
||||||
|
const [registers, setRegisters] = useState<Register[]>([
|
||||||
|
{ id: 'REG-001', name: 'Register 1', status: 'in-use', cashier: 'Sarah Johnson', openedAt: '2024-02-13 9:00 AM', openingBalance: 200.00, currentBalance: 847.50, totalSales: 647.50, transactionCount: 24, lastTransaction: '2:45 PM' },
|
||||||
|
{ id: 'REG-002', name: 'Register 2', status: 'in-use', cashier: 'Mike Davis', openedAt: '2024-02-13 9:15 AM', openingBalance: 200.00, currentBalance: 1123.75, totalSales: 923.75, transactionCount: 18, lastTransaction: '2:38 PM' },
|
||||||
|
{ id: 'REG-003', name: 'Register 3', status: 'closed', openingBalance: 200.00, currentBalance: 200.00, totalSales: 0, transactionCount: 0 },
|
||||||
|
{ id: 'REG-004', name: 'Register 4', status: 'open', cashier: 'Emily Chen', openedAt: '2024-02-13 12:00 PM', openingBalance: 150.00, currentBalance: 378.25, totalSales: 228.25, transactionCount: 9, lastTransaction: '2:22 PM' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const [showOpenRegister, setShowOpenRegister] = useState(false);
|
||||||
|
const [showCloseRegister, setShowCloseRegister] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const getStatusColor = (status: string) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'in-use': return 'bg-green-500/20 text-green-400';
|
||||||
|
case 'open': return 'bg-blue-500/20 text-blue-400';
|
||||||
|
case 'closed': return 'bg-slate-600/50 text-slate-400';
|
||||||
|
default: return 'bg-slate-600/50 text-slate-400';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const totalActiveSales = registers
|
||||||
|
.filter(r => r.status !== 'closed')
|
||||||
|
.reduce((sum, r) => sum + r.totalSales, 0);
|
||||||
|
|
||||||
|
const totalTransactions = registers
|
||||||
|
.filter(r => r.status !== 'closed')
|
||||||
|
.reduce((sum, r) => sum + r.transactionCount, 0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-slate-950 text-slate-100 p-6">
|
||||||
|
<div className="max-w-7xl mx-auto">
|
||||||
|
<div className="flex items-center justify-between mb-8">
|
||||||
|
<h1 className="text-3xl font-bold">Register Manager</h1>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowOpenRegister(true)}
|
||||||
|
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg font-medium"
|
||||||
|
>
|
||||||
|
Open Register
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Summary Stats */}
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
|
||||||
|
<StatCard title="Active Registers" value={registers.filter(r => r.status !== 'closed').length} icon="🏪" />
|
||||||
|
<StatCard title="Total Sales Today" value={`$${totalActiveSales.toFixed(2)}`} icon="💰" />
|
||||||
|
<StatCard title="Total Transactions" value={totalTransactions} icon="🧾" />
|
||||||
|
<StatCard title="Avg Transaction" value={`$${totalTransactions > 0 ? (totalActiveSales / totalTransactions).toFixed(2) : '0.00'}`} icon="📊" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Register Grid */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||||
|
{registers.map((register) => (
|
||||||
|
<div key={register.id} className="bg-slate-800 rounded-lg p-6 border-2 border-transparent hover:border-blue-500/50 transition-all">
|
||||||
|
{/* Header */}
|
||||||
|
<div className="flex items-center justify-between mb-4">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-xl font-bold">{register.name}</h3>
|
||||||
|
<p className="text-sm text-slate-400">{register.id}</p>
|
||||||
|
</div>
|
||||||
|
<span className={`px-3 py-1 rounded text-sm font-medium ${getStatusColor(register.status)}`}>
|
||||||
|
{register.status.toUpperCase().replace('-', ' ')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Cashier Info */}
|
||||||
|
{register.cashier && (
|
||||||
|
<div className="mb-4 p-3 bg-slate-700 rounded-lg">
|
||||||
|
<div className="flex items-center gap-2 mb-1">
|
||||||
|
<span className="text-2xl">👤</span>
|
||||||
|
<span className="font-medium">{register.cashier}</span>
|
||||||
|
</div>
|
||||||
|
{register.openedAt && (
|
||||||
|
<div className="text-sm text-slate-400">Opened at {register.openedAt}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Financial Stats */}
|
||||||
|
<div className="grid grid-cols-2 gap-4 mb-4">
|
||||||
|
<div className="p-3 bg-slate-700 rounded-lg">
|
||||||
|
<div className="text-xs text-slate-400 mb-1">Opening Balance</div>
|
||||||
|
<div className="text-lg font-bold">${register.openingBalance.toFixed(2)}</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-3 bg-slate-700 rounded-lg">
|
||||||
|
<div className="text-xs text-slate-400 mb-1">Current Balance</div>
|
||||||
|
<div className="text-lg font-bold text-green-400">${register.currentBalance.toFixed(2)}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4 mb-4">
|
||||||
|
<div className="p-3 bg-slate-700 rounded-lg">
|
||||||
|
<div className="text-xs text-slate-400 mb-1">Total Sales</div>
|
||||||
|
<div className="text-lg font-bold text-blue-400">${register.totalSales.toFixed(2)}</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-3 bg-slate-700 rounded-lg">
|
||||||
|
<div className="text-xs text-slate-400 mb-1">Transactions</div>
|
||||||
|
<div className="text-lg font-bold">{register.transactionCount}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{register.lastTransaction && (
|
||||||
|
<div className="text-sm text-slate-400 mb-4">
|
||||||
|
Last transaction: {register.lastTransaction}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Actions */}
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{register.status === 'closed' ? (
|
||||||
|
<button className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg text-sm font-medium">
|
||||||
|
Open Register
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<button className="flex-1 px-4 py-2 bg-slate-700 hover:bg-slate-600 rounded-lg text-sm font-medium">
|
||||||
|
View Details
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowCloseRegister(register.id)}
|
||||||
|
className="flex-1 px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg text-sm font-medium"
|
||||||
|
>
|
||||||
|
Close Register
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Open Register Modal */}
|
||||||
|
{showOpenRegister && (
|
||||||
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-6 z-50">
|
||||||
|
<div className="bg-slate-800 rounded-lg p-6 max-w-md w-full">
|
||||||
|
<h2 className="text-2xl font-bold mb-6">Open Register</h2>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-slate-400 mb-2">Select Register</label>
|
||||||
|
<select className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg">
|
||||||
|
{registers.filter(r => r.status === 'closed').map(r => (
|
||||||
|
<option key={r.id} value={r.id}>{r.name}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-slate-400 mb-2">Cashier Name</label>
|
||||||
|
<input type="text" className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg" placeholder="Enter cashier name" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-slate-400 mb-2">Opening Balance</label>
|
||||||
|
<input type="number" step="0.01" className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg" placeholder="200.00" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-3 mt-6">
|
||||||
|
<button className="flex-1 px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded-lg font-medium">
|
||||||
|
Open Register
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowOpenRegister(false)}
|
||||||
|
className="flex-1 px-4 py-2 bg-slate-700 hover:bg-slate-600 rounded-lg font-medium"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Close Register Modal */}
|
||||||
|
{showCloseRegister && (
|
||||||
|
<div className="fixed inset-0 bg-black/50 flex items-center justify-center p-6 z-50">
|
||||||
|
<div className="bg-slate-800 rounded-lg p-6 max-w-md w-full">
|
||||||
|
<h2 className="text-2xl font-bold mb-6">Close Register</h2>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="p-4 bg-slate-700 rounded-lg">
|
||||||
|
<div className="text-sm text-slate-400">Register to Close</div>
|
||||||
|
<div className="font-bold">{registers.find(r => r.id === showCloseRegister)?.name}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm text-slate-400 mb-2">Actual Cash Count</label>
|
||||||
|
<input type="number" step="0.01" className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg" placeholder="Enter counted cash" />
|
||||||
|
</div>
|
||||||
|
<div className="p-4 bg-yellow-500/10 border border-yellow-500/50 rounded-lg">
|
||||||
|
<div className="text-sm text-yellow-400">⚠️ This will close the register and end the current session</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-3 mt-6">
|
||||||
|
<button className="flex-1 px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg font-medium">
|
||||||
|
Close Register
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={() => setShowCloseRegister(null)}
|
||||||
|
className="flex-1 px-4 py-2 bg-slate-700 hover:bg-slate-600 rounded-lg font-medium"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatCard({ title, value, icon }: { title: string; value: string | number; icon: string }) {
|
||||||
|
return (
|
||||||
|
<div className="bg-slate-800 rounded-lg p-4">
|
||||||
|
<div className="flex items-center justify-between mb-2">
|
||||||
|
<span className="text-slate-400 text-sm">{title}</span>
|
||||||
|
<span className="text-2xl">{icon}</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-2xl font-bold">{value}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
420
servers/squarespace/README.md
Normal file
420
servers/squarespace/README.md
Normal file
@ -0,0 +1,420 @@
|
|||||||
|
# Squarespace MCP Server
|
||||||
|
|
||||||
|
A comprehensive Model Context Protocol (MCP) server for Squarespace, providing complete integration with the Squarespace platform for website management, e-commerce operations, content creation, and analytics.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This MCP server enables AI assistants to interact with Squarespace sites through a rich set of **67 tools** covering all major platform features. Built with TypeScript and the official MCP SDK, it provides type-safe, reliable access to Squarespace's v1.0 API with OAuth2 authentication, automatic token refresh, pagination, error handling, and retry logic.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### 🛍️ Complete API Coverage (67 Tools)
|
||||||
|
|
||||||
|
#### Commerce - Orders (8 tools)
|
||||||
|
- List, search, and filter orders by date range, status, email
|
||||||
|
- Get detailed order information with line items and fulfillment
|
||||||
|
- Create orders (for importing from external sources)
|
||||||
|
- Fulfill orders with optional shipping notifications and tracking
|
||||||
|
- Add internal notes to orders
|
||||||
|
- Get pending orders and recent orders
|
||||||
|
|
||||||
|
#### Commerce - Products (10 tools)
|
||||||
|
- List all products with pagination
|
||||||
|
- Get detailed product information with variants and images
|
||||||
|
- Create new products with variants
|
||||||
|
- Update products (name, description, pricing, SEO)
|
||||||
|
- Delete products
|
||||||
|
- Create, update, and delete product variants
|
||||||
|
- Search products by name or tag
|
||||||
|
- Filter products by tags
|
||||||
|
|
||||||
|
#### Commerce - Inventory (5 tools)
|
||||||
|
- Get inventory levels for variants
|
||||||
|
- Update inventory quantities
|
||||||
|
- Adjust inventory by relative amounts
|
||||||
|
- Check for low stock items (configurable threshold)
|
||||||
|
- List out-of-stock items
|
||||||
|
|
||||||
|
#### Commerce - Transactions (3 tools)
|
||||||
|
- Get all transactions for an order
|
||||||
|
- Process refunds
|
||||||
|
- Get transaction summaries (total paid, refunded, net)
|
||||||
|
|
||||||
|
#### Profiles (7 tools)
|
||||||
|
- List all profiles (customers, subscribers, donors)
|
||||||
|
- Get detailed profile information
|
||||||
|
- List customers with purchase history
|
||||||
|
- List mailing list subscribers
|
||||||
|
- List donors
|
||||||
|
- Search profiles by email
|
||||||
|
- Get top customers by lifetime value
|
||||||
|
|
||||||
|
#### Webhooks (7 tools)
|
||||||
|
- List all webhook subscriptions
|
||||||
|
- Get webhook details
|
||||||
|
- Create new webhooks for events (order.create, inventory.update, etc.)
|
||||||
|
- Update webhook configurations
|
||||||
|
- Delete webhooks
|
||||||
|
- Send test notifications
|
||||||
|
- Rotate webhook secrets for security
|
||||||
|
|
||||||
|
#### Pages & Website (8 tools)
|
||||||
|
- Get website information
|
||||||
|
- List and get collections
|
||||||
|
- List, get, create, update, and delete pages
|
||||||
|
- Manage page SEO settings
|
||||||
|
|
||||||
|
#### Forms (5 tools)
|
||||||
|
- List all forms
|
||||||
|
- Get form details with fields
|
||||||
|
- List form submissions with filtering
|
||||||
|
- Get specific submission details
|
||||||
|
- Export form submissions as CSV
|
||||||
|
|
||||||
|
#### Blog (9 tools)
|
||||||
|
- List all blog collections
|
||||||
|
- Get blog details
|
||||||
|
- List blog posts with pagination
|
||||||
|
- Get specific blog post
|
||||||
|
- Create new blog posts
|
||||||
|
- Update blog posts
|
||||||
|
- Delete blog posts
|
||||||
|
- Publish and unpublish posts
|
||||||
|
|
||||||
|
#### Analytics (5 tools)
|
||||||
|
- Get revenue metrics (total revenue, order count, AOV)
|
||||||
|
- Get top-selling products by revenue
|
||||||
|
- Get daily sales breakdowns
|
||||||
|
- Get monthly revenue summary
|
||||||
|
- Get yearly revenue summary
|
||||||
|
|
||||||
|
### 🎨 15 React MCP Apps (Dark Theme)
|
||||||
|
|
||||||
|
All apps are standalone Vite-based React applications with dark theme:
|
||||||
|
|
||||||
|
1. **Orders Dashboard** - Order management and fulfillment
|
||||||
|
2. **Products Manager** - Product catalog management
|
||||||
|
3. **Inventory Tracker** - Real-time inventory monitoring
|
||||||
|
4. **Customer Profiles** - Customer LTV and history
|
||||||
|
5. **Analytics Dashboard** - Revenue and sales insights
|
||||||
|
6. **Blog Editor** - Blog post management
|
||||||
|
7. **Forms Viewer** - Form submissions and export
|
||||||
|
8. **Webhook Manager** - Webhook configuration and testing
|
||||||
|
9. **Page Manager** - Website page management
|
||||||
|
10. **Bulk Editor** - Bulk product updates
|
||||||
|
11. **SEO Optimizer** - SEO settings and optimization
|
||||||
|
12. **Reports** - Generate and download reports
|
||||||
|
13. **Shipping Manager** - Fulfillment tracking
|
||||||
|
14. **Discount Manager** - Discount code management
|
||||||
|
15. **Settings** - Server and API configuration
|
||||||
|
|
||||||
|
### 🔒 Enterprise-Grade Features
|
||||||
|
|
||||||
|
- **OAuth2 Authentication** - Full OAuth2 support with refresh tokens
|
||||||
|
- **Automatic Token Refresh** - Seamless token renewal before expiration
|
||||||
|
- **Retry Logic** - Automatic retry with exponential backoff for rate limits and errors
|
||||||
|
- **Pagination Support** - Handle large datasets efficiently
|
||||||
|
- **Error Handling** - Comprehensive error messages with details
|
||||||
|
- **Type Safety** - Full TypeScript types for all API entities
|
||||||
|
- **Rate Limit Management** - Built-in rate limit handling
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @mcpengine/squarespace-server
|
||||||
|
```
|
||||||
|
|
||||||
|
Or install from source:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd servers/squarespace
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
The server requires at minimum a Squarespace access token:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export SQUARESPACE_ACCESS_TOKEN="your_access_token_here"
|
||||||
|
```
|
||||||
|
|
||||||
|
For long-term access with automatic token refresh:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export SQUARESPACE_ACCESS_TOKEN="your_access_token"
|
||||||
|
export SQUARESPACE_REFRESH_TOKEN="your_refresh_token"
|
||||||
|
export SQUARESPACE_CLIENT_ID="your_client_id"
|
||||||
|
export SQUARESPACE_CLIENT_SECRET="your_client_secret"
|
||||||
|
```
|
||||||
|
|
||||||
|
### MCP Configuration
|
||||||
|
|
||||||
|
Add to your MCP settings file (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"squarespace": {
|
||||||
|
"command": "squarespace-mcp",
|
||||||
|
"env": {
|
||||||
|
"SQUARESPACE_ACCESS_TOKEN": "your_access_token",
|
||||||
|
"SQUARESPACE_REFRESH_TOKEN": "your_refresh_token",
|
||||||
|
"SQUARESPACE_CLIENT_ID": "your_client_id",
|
||||||
|
"SQUARESPACE_CLIENT_SECRET": "your_client_secret"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Getting Squarespace API Credentials
|
||||||
|
|
||||||
|
### 1. Register Your OAuth Application
|
||||||
|
|
||||||
|
Submit a request to Squarespace to register your OAuth application:
|
||||||
|
- [Squarespace Developer Portal](https://developers.squarespace.com/)
|
||||||
|
- Provide: App name, icon, redirect URI, terms & privacy links
|
||||||
|
|
||||||
|
You'll receive:
|
||||||
|
- `client_id`
|
||||||
|
- `client_secret`
|
||||||
|
|
||||||
|
### 2. OAuth Flow
|
||||||
|
|
||||||
|
Implement the OAuth2 authorization code flow:
|
||||||
|
|
||||||
|
1. **Authorization URL:**
|
||||||
|
```
|
||||||
|
https://login.squarespace.com/api/1/login/oauth/provider/authorize?
|
||||||
|
client_id=YOUR_CLIENT_ID&
|
||||||
|
redirect_uri=YOUR_REDIRECT_URI&
|
||||||
|
scope=website.orders,website.products,website.inventory&
|
||||||
|
state=RANDOM_STATE&
|
||||||
|
access_type=offline
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Token Exchange:**
|
||||||
|
```bash
|
||||||
|
curl -X POST https://login.squarespace.com/api/1/login/oauth/provider/tokens \
|
||||||
|
-H "Authorization: Basic BASE64(client_id:client_secret)" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"grant_type": "authorization_code",
|
||||||
|
"code": "AUTHORIZATION_CODE",
|
||||||
|
"redirect_uri": "YOUR_REDIRECT_URI"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### API Scopes
|
||||||
|
|
||||||
|
Request these scopes for full functionality:
|
||||||
|
- `website.orders` - Read and manage orders
|
||||||
|
- `website.orders.read` - Read-only order access
|
||||||
|
- `website.products` - Manage products
|
||||||
|
- `website.products.read` - Read-only product access
|
||||||
|
- `website.inventory` - Manage inventory
|
||||||
|
- `website.inventory.read` - Read-only inventory access
|
||||||
|
- `website.transactions.read` - Read transaction data
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### List Recent Orders
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
"name": "squarespace_list_orders",
|
||||||
|
"arguments": {
|
||||||
|
"modifiedAfter": "2024-01-01T00:00:00Z",
|
||||||
|
"fulfillmentStatus": "PENDING"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create a Product
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
"name": "squarespace_create_product",
|
||||||
|
"arguments": {
|
||||||
|
"product": {
|
||||||
|
"type": "PHYSICAL",
|
||||||
|
"storePageId": "store_page_id",
|
||||||
|
"name": "New Product",
|
||||||
|
"description": "Product description",
|
||||||
|
"variants": [{
|
||||||
|
"sku": "SKU-001",
|
||||||
|
"pricing": {
|
||||||
|
"basePrice": {
|
||||||
|
"value": "29.99",
|
||||||
|
"currency": "USD"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"stock": {
|
||||||
|
"quantity": 100,
|
||||||
|
"unlimited": false
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fulfill an Order
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
"name": "squarespace_fulfill_order",
|
||||||
|
"arguments": {
|
||||||
|
"orderId": "order_id_here",
|
||||||
|
"shouldSendNotification": true,
|
||||||
|
"shipments": [{
|
||||||
|
"carrierName": "USPS",
|
||||||
|
"trackingNumber": "1234567890",
|
||||||
|
"trackingUrl": "https://tools.usps.com/go/TrackConfirmAction?tLabels=1234567890"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get Revenue Analytics
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
"name": "squarespace_get_revenue_metrics",
|
||||||
|
"arguments": {
|
||||||
|
"startDate": "2024-01-01T00:00:00Z",
|
||||||
|
"endDate": "2024-01-31T23:59:59Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Check Low Stock Items
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
"name": "squarespace_check_low_stock",
|
||||||
|
"arguments": {
|
||||||
|
"threshold": 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
squarespace/
|
||||||
|
├── src/
|
||||||
|
│ ├── clients/
|
||||||
|
│ │ └── squarespace.ts # API client with auth & retry logic
|
||||||
|
│ ├── types/
|
||||||
|
│ │ └── index.ts # Complete TypeScript types
|
||||||
|
│ ├── tools/
|
||||||
|
│ │ ├── commerce-orders.ts # Order management tools
|
||||||
|
│ │ ├── commerce-products.ts # Product management tools
|
||||||
|
│ │ ├── commerce-inventory.ts# Inventory management tools
|
||||||
|
│ │ ├── commerce-transactions.ts # Transaction tools
|
||||||
|
│ │ ├── profiles.ts # Customer/subscriber tools
|
||||||
|
│ │ ├── webhooks.ts # Webhook management tools
|
||||||
|
│ │ ├── pages.ts # Page management tools
|
||||||
|
│ │ ├── forms.ts # Form submission tools
|
||||||
|
│ │ ├── blog.ts # Blog management tools
|
||||||
|
│ │ └── analytics.ts # Analytics tools
|
||||||
|
│ ├── ui/
|
||||||
|
│ │ └── react-app/ # 15 React MCP apps
|
||||||
|
│ ├── server.ts # MCP server implementation
|
||||||
|
│ └── main.ts # Entry point
|
||||||
|
├── package.json
|
||||||
|
├── tsconfig.json
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
Full documentation: [Squarespace Developer Docs](https://developers.squarespace.com/)
|
||||||
|
|
||||||
|
### Rate Limits
|
||||||
|
|
||||||
|
Squarespace enforces varying rate limits per endpoint with 1-minute cooldowns. The client automatically handles rate limiting with retry logic.
|
||||||
|
|
||||||
|
### Webhooks
|
||||||
|
|
||||||
|
Subscribe to real-time events:
|
||||||
|
- `order.create` - New order created
|
||||||
|
- `order.update` - Order updated
|
||||||
|
- `transaction.create` - New transaction
|
||||||
|
- `transaction.update` - Transaction updated
|
||||||
|
- `inventory.update` - Inventory changed
|
||||||
|
- `product.create` - Product created
|
||||||
|
- `product.update` - Product updated
|
||||||
|
- `product.delete` - Product deleted
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Type Check
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run type-check
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development Mode
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Authentication Errors
|
||||||
|
|
||||||
|
- **401 Unauthorized**: Token expired or invalid - refresh your token
|
||||||
|
- **403 Forbidden**: Insufficient scopes - request additional permissions
|
||||||
|
- **Token refresh fails**: Verify client credentials are correct
|
||||||
|
|
||||||
|
### Rate Limiting
|
||||||
|
|
||||||
|
- **429 Too Many Requests**: Built-in retry handles this automatically
|
||||||
|
- Implement delays between bulk operations for best performance
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
1. **Missing environment variables**: Ensure `SQUARESPACE_ACCESS_TOKEN` is set
|
||||||
|
2. **TypeScript errors**: Run `npm run type-check` to diagnose
|
||||||
|
3. **Module resolution**: Verify `package.json` has `"type": "module"`
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions welcome! Please:
|
||||||
|
1. Follow existing code structure
|
||||||
|
2. Add tests for new tools
|
||||||
|
3. Update documentation
|
||||||
|
4. Run type checking before submitting
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - see LICENSE file for details
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
- [Squarespace API Documentation](https://developers.squarespace.com/)
|
||||||
|
- [MCP Protocol Specification](https://modelcontextprotocol.io/)
|
||||||
|
- [GitHub Issues](https://github.com/BusyBee3333/mcpengine/issues)
|
||||||
|
|
||||||
|
## Changelog
|
||||||
|
|
||||||
|
### v1.0.0 (2024-02-12)
|
||||||
|
- Initial release
|
||||||
|
- 67 tools covering all major Squarespace features
|
||||||
|
- 15 React MCP apps with dark theme
|
||||||
|
- OAuth2 authentication with auto-refresh
|
||||||
|
- Comprehensive error handling and retry logic
|
||||||
|
- Full TypeScript support
|
||||||
51
servers/squarespace/package.json
Normal file
51
servers/squarespace/package.json
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"name": "@mcpengine/squarespace-server",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Complete MCP server for Squarespace website builder and ecommerce platform",
|
||||||
|
"type": "module",
|
||||||
|
"main": "dist/main.js",
|
||||||
|
"bin": {
|
||||||
|
"squarespace-mcp": "./dist/main.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc && chmod +x dist/main.js",
|
||||||
|
"dev": "tsc --watch",
|
||||||
|
"start": "node dist/main.js",
|
||||||
|
"prepublishOnly": "npm run build",
|
||||||
|
"type-check": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"mcp",
|
||||||
|
"squarespace",
|
||||||
|
"ecommerce",
|
||||||
|
"website-builder",
|
||||||
|
"api",
|
||||||
|
"commerce",
|
||||||
|
"blog",
|
||||||
|
"cms"
|
||||||
|
],
|
||||||
|
"author": "MCP Engine",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@modelcontextprotocol/sdk": "^0.6.0",
|
||||||
|
"axios": "^1.6.7"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.11.17",
|
||||||
|
"@types/react": "^18.2.55",
|
||||||
|
"@types/react-dom": "^18.2.19",
|
||||||
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
|
"react": "^18.2.0",
|
||||||
|
"react-dom": "^18.2.0",
|
||||||
|
"typescript": "^5.3.3",
|
||||||
|
"vite": "^5.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18.0.0"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/BusyBee3333/mcpengine.git",
|
||||||
|
"directory": "servers/squarespace"
|
||||||
|
}
|
||||||
|
}
|
||||||
26
servers/squarespace/tsconfig.json
Normal file
26
servers/squarespace/tsconfig.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ES2022",
|
||||||
|
"lib": ["ES2022", "DOM"],
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist", "src/ui/react-app/*/dist"]
|
||||||
|
}
|
||||||
199
servers/toast/src/tools/cash.ts
Normal file
199
servers/toast/src/tools/cash.ts
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
import { ToastClient } from '../clients/toast.js';
|
||||||
|
import type { CashDrawer, CashEntry, CashDeposit } from '../types/index.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cash Management Tools
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function registerCashTools(client: ToastClient) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'toast_list_cash_drawers',
|
||||||
|
description: 'List all cash drawers for a business date',
|
||||||
|
inputSchema: z.object({
|
||||||
|
businessDate: z.number().describe('Business date in YYYYMMDD format'),
|
||||||
|
restaurantGuid: z.string().optional(),
|
||||||
|
}),
|
||||||
|
handler: async (args: { businessDate: number; restaurantGuid?: string }) => {
|
||||||
|
const restGuid = args.restaurantGuid || client.getRestaurantGuid();
|
||||||
|
const drawers = await client.get<CashDrawer[]>(
|
||||||
|
`/cashmgmt/v1/drawers`,
|
||||||
|
{
|
||||||
|
restaurantGuid: restGuid,
|
||||||
|
businessDate: args.businessDate,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return { drawers, count: drawers.length };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'toast_get_cash_drawer',
|
||||||
|
description: 'Get detailed information about a specific cash drawer',
|
||||||
|
inputSchema: z.object({
|
||||||
|
drawerGuid: z.string(),
|
||||||
|
restaurantGuid: z.string().optional(),
|
||||||
|
}),
|
||||||
|
handler: async (args: { drawerGuid: string; restaurantGuid?: string }) => {
|
||||||
|
const restGuid = args.restaurantGuid || client.getRestaurantGuid();
|
||||||
|
const drawer = await client.get<CashDrawer>(
|
||||||
|
`/cashmgmt/v1/drawers/${args.drawerGuid}`,
|
||||||
|
{ restaurantGuid: restGuid }
|
||||||
|
);
|
||||||
|
return { drawer };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'toast_list_cash_entries',
|
||||||
|
description: 'List cash entries (paid in/paid out) for a cash drawer or business date',
|
||||||
|
inputSchema: z.object({
|
||||||
|
businessDate: z.number().optional(),
|
||||||
|
drawerGuid: z.string().optional(),
|
||||||
|
employeeGuid: z.string().optional(),
|
||||||
|
restaurantGuid: z.string().optional(),
|
||||||
|
}),
|
||||||
|
handler: async (args: { businessDate?: number; drawerGuid?: string; employeeGuid?: string; restaurantGuid?: string }) => {
|
||||||
|
const restGuid = args.restaurantGuid || client.getRestaurantGuid();
|
||||||
|
const entries = await client.get<CashEntry[]>(
|
||||||
|
`/cashmgmt/v1/entries`,
|
||||||
|
{
|
||||||
|
restaurantGuid: restGuid,
|
||||||
|
businessDate: args.businessDate,
|
||||||
|
drawerGuid: args.drawerGuid,
|
||||||
|
employeeGuid: args.employeeGuid,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return { entries, count: entries.length };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'toast_create_cash_entry',
|
||||||
|
description: 'Record a cash paid in or paid out entry',
|
||||||
|
inputSchema: z.object({
|
||||||
|
drawerGuid: z.string(),
|
||||||
|
amount: z.number().describe('Amount in cents (positive for paid in, negative for paid out)'),
|
||||||
|
type: z.enum(['PAID_IN', 'PAID_OUT']),
|
||||||
|
reason: z.string().optional(),
|
||||||
|
comment: z.string().optional(),
|
||||||
|
restaurantGuid: z.string().optional(),
|
||||||
|
}),
|
||||||
|
handler: async (args: any) => {
|
||||||
|
const restGuid = args.restaurantGuid || client.getRestaurantGuid();
|
||||||
|
const entry = await client.post<CashEntry>(
|
||||||
|
`/cashmgmt/v1/entries`,
|
||||||
|
{
|
||||||
|
drawerGuid: args.drawerGuid,
|
||||||
|
amount: args.amount,
|
||||||
|
type: args.type,
|
||||||
|
reason: args.reason,
|
||||||
|
comment: args.comment,
|
||||||
|
},
|
||||||
|
{ params: { restaurantGuid: restGuid } }
|
||||||
|
);
|
||||||
|
return { entry };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'toast_get_cash_drawer_summary',
|
||||||
|
description: 'Get summary of cash drawer activity (paid in, paid out, net)',
|
||||||
|
inputSchema: z.object({
|
||||||
|
drawerGuid: z.string(),
|
||||||
|
restaurantGuid: z.string().optional(),
|
||||||
|
}),
|
||||||
|
handler: async (args: { drawerGuid: string; restaurantGuid?: string }) => {
|
||||||
|
const restGuid = args.restaurantGuid || client.getRestaurantGuid();
|
||||||
|
|
||||||
|
const entries = await client.get<CashEntry[]>(
|
||||||
|
`/cashmgmt/v1/entries`,
|
||||||
|
{
|
||||||
|
restaurantGuid: restGuid,
|
||||||
|
drawerGuid: args.drawerGuid,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const paidIn = entries
|
||||||
|
.filter(e => e.type === 'PAID_IN' && !e.deleted)
|
||||||
|
.reduce((sum, e) => sum + e.amount, 0);
|
||||||
|
|
||||||
|
const paidOut = entries
|
||||||
|
.filter(e => e.type === 'PAID_OUT' && !e.deleted)
|
||||||
|
.reduce((sum, e) => sum + Math.abs(e.amount), 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
drawerGuid: args.drawerGuid,
|
||||||
|
paidIn,
|
||||||
|
paidOut,
|
||||||
|
netCash: paidIn - paidOut,
|
||||||
|
entryCount: entries.length,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'toast_void_cash_entry',
|
||||||
|
description: 'Void/undo a cash entry',
|
||||||
|
inputSchema: z.object({
|
||||||
|
entryGuid: z.string(),
|
||||||
|
restaurantGuid: z.string().optional(),
|
||||||
|
}),
|
||||||
|
handler: async (args: { entryGuid: string; restaurantGuid?: string }) => {
|
||||||
|
const restGuid = args.restaurantGuid || client.getRestaurantGuid();
|
||||||
|
const result = await client.delete(
|
||||||
|
`/cashmgmt/v1/entries/${args.entryGuid}`,
|
||||||
|
{ params: { restaurantGuid: restGuid } }
|
||||||
|
);
|
||||||
|
return { success: true, entryGuid: args.entryGuid };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'toast_list_cash_deposits',
|
||||||
|
description: 'List cash deposit records',
|
||||||
|
inputSchema: z.object({
|
||||||
|
businessDate: z.number().optional(),
|
||||||
|
startDate: z.string().optional(),
|
||||||
|
endDate: z.string().optional(),
|
||||||
|
restaurantGuid: z.string().optional(),
|
||||||
|
}),
|
||||||
|
handler: async (args: { businessDate?: number; startDate?: string; endDate?: string; restaurantGuid?: string }) => {
|
||||||
|
const restGuid = args.restaurantGuid || client.getRestaurantGuid();
|
||||||
|
const deposits = await client.get<CashDeposit[]>(
|
||||||
|
`/cashmgmt/v1/deposits`,
|
||||||
|
{
|
||||||
|
restaurantGuid: restGuid,
|
||||||
|
businessDate: args.businessDate,
|
||||||
|
startDate: args.startDate,
|
||||||
|
endDate: args.endDate,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return { deposits, count: deposits.length };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'toast_create_cash_deposit',
|
||||||
|
description: 'Record a cash deposit',
|
||||||
|
inputSchema: z.object({
|
||||||
|
amount: z.number().describe('Deposit amount in cents'),
|
||||||
|
date: z.string().optional().describe('ISO 8601 date (defaults to now)'),
|
||||||
|
restaurantGuid: z.string().optional(),
|
||||||
|
}),
|
||||||
|
handler: async (args: { amount: number; date?: string; restaurantGuid?: string }) => {
|
||||||
|
const restGuid = args.restaurantGuid || client.getRestaurantGuid();
|
||||||
|
const deposit = await client.post<CashDeposit>(
|
||||||
|
`/cashmgmt/v1/deposits`,
|
||||||
|
{
|
||||||
|
amount: args.amount,
|
||||||
|
date: args.date || new Date().toISOString(),
|
||||||
|
},
|
||||||
|
{ params: { restaurantGuid: restGuid } }
|
||||||
|
);
|
||||||
|
return { deposit };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
348
servers/toast/src/tools/reporting.ts
Normal file
348
servers/toast/src/tools/reporting.ts
Normal file
@ -0,0 +1,348 @@
|
|||||||
|
import { z } from 'zod';
|
||||||
|
import { ToastClient } from '../clients/toast.js';
|
||||||
|
import type { Order, MenuItemSales } from '../types/index.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reporting & Analytics Tools
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function registerReportingTools(client: ToastClient) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: 'toast_get_sales_summary',
|
||||||
|
description: 'Get comprehensive sales summary for a business date',
|
||||||
|
inputSchema: z.object({
|
||||||
|
businessDate: z.number().describe('Business date in YYYYMMDD format'),
|
||||||
|
restaurantGuid: z.string().optional(),
|
||||||
|
}),
|
||||||
|
handler: async (args: { businessDate: number; restaurantGuid?: string }) => {
|
||||||
|
const restGuid = args.restaurantGuid || client.getRestaurantGuid();
|
||||||
|
|
||||||
|
const orders = await client.getAllPages<Order>(
|
||||||
|
`/orders/v2/orders`,
|
||||||
|
{
|
||||||
|
restaurantGuid: restGuid,
|
||||||
|
businessDate: args.businessDate,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let totalSales = 0;
|
||||||
|
let grossSales = 0;
|
||||||
|
let netSales = 0;
|
||||||
|
let taxAmount = 0;
|
||||||
|
let tipAmount = 0;
|
||||||
|
let discountAmount = 0;
|
||||||
|
let voidAmount = 0;
|
||||||
|
let refundAmount = 0;
|
||||||
|
let guestCount = 0;
|
||||||
|
let checkCount = 0;
|
||||||
|
|
||||||
|
orders.forEach(order => {
|
||||||
|
if (order.voided) {
|
||||||
|
voidAmount += order.checks.reduce((sum, check) => sum + (check.totalAmount || 0), 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
guestCount += order.numberOfGuests || 0;
|
||||||
|
checkCount += order.checks.length;
|
||||||
|
|
||||||
|
order.checks.forEach(check => {
|
||||||
|
grossSales += check.amount || 0;
|
||||||
|
taxAmount += check.taxAmount || 0;
|
||||||
|
totalSales += check.totalAmount || 0;
|
||||||
|
|
||||||
|
check.payments?.forEach(payment => {
|
||||||
|
tipAmount += payment.tipAmount || 0;
|
||||||
|
if (payment.refund) {
|
||||||
|
refundAmount += payment.refund.refundAmount || 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
check.appliedDiscounts?.forEach(discount => {
|
||||||
|
discountAmount += discount.discountAmount || 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
netSales = grossSales - discountAmount;
|
||||||
|
|
||||||
|
return {
|
||||||
|
businessDate: args.businessDate,
|
||||||
|
totalSales,
|
||||||
|
grossSales,
|
||||||
|
netSales,
|
||||||
|
taxAmount,
|
||||||
|
tipAmount,
|
||||||
|
discountAmount,
|
||||||
|
voidAmount,
|
||||||
|
refundAmount,
|
||||||
|
guestCount,
|
||||||
|
checkCount,
|
||||||
|
averageCheck: checkCount > 0 ? netSales / checkCount : 0,
|
||||||
|
averageGuestSpend: guestCount > 0 ? netSales / guestCount : 0,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'toast_get_hourly_sales',
|
||||||
|
description: 'Get sales broken down by hour for a business date',
|
||||||
|
inputSchema: z.object({
|
||||||
|
businessDate: z.number(),
|
||||||
|
restaurantGuid: z.string().optional(),
|
||||||
|
}),
|
||||||
|
handler: async (args: { businessDate: number; restaurantGuid?: string }) => {
|
||||||
|
const restGuid = args.restaurantGuid || client.getRestaurantGuid();
|
||||||
|
|
||||||
|
const orders = await client.getAllPages<Order>(
|
||||||
|
`/orders/v2/orders`,
|
||||||
|
{
|
||||||
|
restaurantGuid: restGuid,
|
||||||
|
businessDate: args.businessDate,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const hourlyData: Record<number, { sales: number; orders: number; guests: number }> = {};
|
||||||
|
|
||||||
|
orders.forEach(order => {
|
||||||
|
if (order.voided) return;
|
||||||
|
|
||||||
|
const hour = new Date(order.openedDate).getHours();
|
||||||
|
if (!hourlyData[hour]) {
|
||||||
|
hourlyData[hour] = { sales: 0, orders: 0, guests: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
hourlyData[hour].orders++;
|
||||||
|
hourlyData[hour].guests += order.numberOfGuests || 0;
|
||||||
|
|
||||||
|
order.checks.forEach(check => {
|
||||||
|
hourlyData[hour].sales += check.totalAmount || 0;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const hourlyArray = Array.from({ length: 24 }, (_, hour) => ({
|
||||||
|
hour,
|
||||||
|
...( hourlyData[hour] || { sales: 0, orders: 0, guests: 0 }),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
businessDate: args.businessDate,
|
||||||
|
hourlyBreakdown: hourlyArray,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'toast_get_item_sales_report',
|
||||||
|
description: 'Get sales report for menu items',
|
||||||
|
inputSchema: z.object({
|
||||||
|
businessDate: z.number().optional(),
|
||||||
|
startDate: z.string().optional(),
|
||||||
|
endDate: z.string().optional(),
|
||||||
|
limit: z.number().optional().describe('Return top N items'),
|
||||||
|
restaurantGuid: z.string().optional(),
|
||||||
|
}),
|
||||||
|
handler: async (args: { businessDate?: number; startDate?: string; endDate?: string; limit?: number; restaurantGuid?: string }) => {
|
||||||
|
const restGuid = args.restaurantGuid || client.getRestaurantGuid();
|
||||||
|
|
||||||
|
const orders = await client.getAllPages<Order>(
|
||||||
|
`/orders/v2/orders`,
|
||||||
|
{
|
||||||
|
restaurantGuid: restGuid,
|
||||||
|
businessDate: args.businessDate,
|
||||||
|
startDate: args.startDate,
|
||||||
|
endDate: args.endDate,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const itemSales = new Map<string, MenuItemSales>();
|
||||||
|
|
||||||
|
orders.forEach(order => {
|
||||||
|
if (order.voided) return;
|
||||||
|
|
||||||
|
order.checks.forEach(check => {
|
||||||
|
check.selections.forEach(selection => {
|
||||||
|
if (selection.voided) return;
|
||||||
|
|
||||||
|
const existing = itemSales.get(selection.itemGuid);
|
||||||
|
if (existing) {
|
||||||
|
existing.quantity += selection.quantity;
|
||||||
|
existing.grossSales += selection.price;
|
||||||
|
existing.netSales += selection.price - (selection.appliedDiscounts?.reduce((sum, d) => sum + d.discountAmount, 0) || 0);
|
||||||
|
} else {
|
||||||
|
itemSales.set(selection.itemGuid, {
|
||||||
|
itemGuid: selection.itemGuid,
|
||||||
|
itemName: selection.displayName,
|
||||||
|
quantity: selection.quantity,
|
||||||
|
grossSales: selection.price,
|
||||||
|
netSales: selection.price - (selection.appliedDiscounts?.reduce((sum, d) => sum + d.discountAmount, 0) || 0),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let items = Array.from(itemSales.values());
|
||||||
|
items.sort((a, b) => b.netSales - a.netSales);
|
||||||
|
|
||||||
|
if (args.limit) {
|
||||||
|
items = items.slice(0, args.limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
items,
|
||||||
|
totalItems: items.length,
|
||||||
|
totalQuantity: items.reduce((sum, item) => sum + item.quantity, 0),
|
||||||
|
totalSales: items.reduce((sum, item) => sum + item.netSales, 0),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'toast_get_payment_type_report',
|
||||||
|
description: 'Get breakdown of sales by payment type',
|
||||||
|
inputSchema: z.object({
|
||||||
|
businessDate: z.number(),
|
||||||
|
restaurantGuid: z.string().optional(),
|
||||||
|
}),
|
||||||
|
handler: async (args: { businessDate: number; restaurantGuid?: string }) => {
|
||||||
|
const restGuid = args.restaurantGuid || client.getRestaurantGuid();
|
||||||
|
|
||||||
|
const orders = await client.getAllPages<Order>(
|
||||||
|
`/orders/v2/orders`,
|
||||||
|
{
|
||||||
|
restaurantGuid: restGuid,
|
||||||
|
businessDate: args.businessDate,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const paymentTypes: Record<string, { amount: number; tipAmount: number; count: number }> = {};
|
||||||
|
|
||||||
|
orders.forEach(order => {
|
||||||
|
order.checks.forEach(check => {
|
||||||
|
check.payments?.forEach(payment => {
|
||||||
|
const type = payment.type || 'UNKNOWN';
|
||||||
|
if (!paymentTypes[type]) {
|
||||||
|
paymentTypes[type] = { amount: 0, tipAmount: 0, count: 0 };
|
||||||
|
}
|
||||||
|
paymentTypes[type].amount += payment.amount;
|
||||||
|
paymentTypes[type].tipAmount += payment.tipAmount || 0;
|
||||||
|
paymentTypes[type].count++;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
businessDate: args.businessDate,
|
||||||
|
paymentTypes,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'toast_get_discount_report',
|
||||||
|
description: 'Get report on discounts applied',
|
||||||
|
inputSchema: z.object({
|
||||||
|
businessDate: z.number(),
|
||||||
|
restaurantGuid: z.string().optional(),
|
||||||
|
}),
|
||||||
|
handler: async (args: { businessDate: number; restaurantGuid?: string }) => {
|
||||||
|
const restGuid = args.restaurantGuid || client.getRestaurantGuid();
|
||||||
|
|
||||||
|
const orders = await client.getAllPages<Order>(
|
||||||
|
`/orders/v2/orders`,
|
||||||
|
{
|
||||||
|
restaurantGuid: restGuid,
|
||||||
|
businessDate: args.businessDate,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const discounts: Record<string, { name: string; amount: number; count: number }> = {};
|
||||||
|
|
||||||
|
orders.forEach(order => {
|
||||||
|
order.checks.forEach(check => {
|
||||||
|
check.appliedDiscounts?.forEach(discount => {
|
||||||
|
const key = discount.discountGuid;
|
||||||
|
if (!discounts[key]) {
|
||||||
|
discounts[key] = { name: discount.name, amount: 0, count: 0 };
|
||||||
|
}
|
||||||
|
discounts[key].amount += discount.discountAmount;
|
||||||
|
discounts[key].count++;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const discountArray = Object.entries(discounts).map(([guid, data]) => ({
|
||||||
|
discountGuid: guid,
|
||||||
|
...data,
|
||||||
|
}));
|
||||||
|
|
||||||
|
discountArray.sort((a, b) => b.amount - a.amount);
|
||||||
|
|
||||||
|
return {
|
||||||
|
businessDate: args.businessDate,
|
||||||
|
discounts: discountArray,
|
||||||
|
totalDiscountAmount: discountArray.reduce((sum, d) => sum + d.amount, 0),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'toast_get_void_report',
|
||||||
|
description: 'Get report on voided orders and items',
|
||||||
|
inputSchema: z.object({
|
||||||
|
businessDate: z.number(),
|
||||||
|
restaurantGuid: z.string().optional(),
|
||||||
|
}),
|
||||||
|
handler: async (args: { businessDate: number; restaurantGuid?: string }) => {
|
||||||
|
const restGuid = args.restaurantGuid || client.getRestaurantGuid();
|
||||||
|
|
||||||
|
const orders = await client.getAllPages<Order>(
|
||||||
|
`/orders/v2/orders`,
|
||||||
|
{
|
||||||
|
restaurantGuid: restGuid,
|
||||||
|
businessDate: args.businessDate,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const voidedOrders = orders.filter(o => o.voided);
|
||||||
|
const voidedSelections: any[] = [];
|
||||||
|
|
||||||
|
orders.forEach(order => {
|
||||||
|
order.checks.forEach(check => {
|
||||||
|
check.selections.forEach(selection => {
|
||||||
|
if (selection.voided) {
|
||||||
|
voidedSelections.push({
|
||||||
|
orderGuid: order.guid,
|
||||||
|
itemName: selection.displayName,
|
||||||
|
quantity: selection.quantity,
|
||||||
|
amount: selection.price,
|
||||||
|
voidDate: selection.voidDate,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const totalVoidedAmount =
|
||||||
|
voidedOrders.reduce((sum, order) =>
|
||||||
|
sum + order.checks.reduce((checkSum, check) => checkSum + (check.totalAmount || 0), 0),
|
||||||
|
0) +
|
||||||
|
voidedSelections.reduce((sum, sel) => sum + sel.amount, 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
businessDate: args.businessDate,
|
||||||
|
voidedOrderCount: voidedOrders.length,
|
||||||
|
voidedSelectionCount: voidedSelections.length,
|
||||||
|
totalVoidedAmount,
|
||||||
|
voidedOrders: voidedOrders.map(o => ({
|
||||||
|
orderGuid: o.guid,
|
||||||
|
voidDate: o.voidDate,
|
||||||
|
amount: o.checks.reduce((sum, c) => sum + (c.totalAmount || 0), 0),
|
||||||
|
})),
|
||||||
|
voidedSelections,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
60
servers/touchbistro/package.json
Normal file
60
servers/touchbistro/package.json
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"name": "touchbistro-mcp-server",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Model Context Protocol server for TouchBistro restaurant management platform",
|
||||||
|
"author": "MCP Engine",
|
||||||
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
|
"bin": {
|
||||||
|
"touchbistro-mcp-server": "./dist/main.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc && npm run build:apps",
|
||||||
|
"build:apps": "npm run build:orders-app && npm run build:menu-app && npm run build:reservations-app && npm run build:tables-app && npm run build:customers-app && npm run build:employees-app && npm run build:payments-app && npm run build:loyalty-app && npm run build:giftcards-app && npm run build:inventory-app && npm run build:reports-app && npm run build:analytics-app && npm run build:discounts-app && npm run build:settings-app && npm run build:dashboard-app",
|
||||||
|
"build:orders-app": "cd src/ui/orders-app && vite build",
|
||||||
|
"build:menu-app": "cd src/ui/menu-app && vite build",
|
||||||
|
"build:reservations-app": "cd src/ui/reservations-app && vite build",
|
||||||
|
"build:tables-app": "cd src/ui/tables-app && vite build",
|
||||||
|
"build:customers-app": "cd src/ui/customers-app && vite build",
|
||||||
|
"build:employees-app": "cd src/ui/employees-app && vite build",
|
||||||
|
"build:payments-app": "cd src/ui/payments-app && vite build",
|
||||||
|
"build:loyalty-app": "cd src/ui/loyalty-app && vite build",
|
||||||
|
"build:giftcards-app": "cd src/ui/giftcards-app && vite build",
|
||||||
|
"build:inventory-app": "cd src/ui/inventory-app && vite build",
|
||||||
|
"build:reports-app": "cd src/ui/reports-app && vite build",
|
||||||
|
"build:analytics-app": "cd src/ui/analytics-app && vite build",
|
||||||
|
"build:discounts-app": "cd src/ui/discounts-app && vite build",
|
||||||
|
"build:settings-app": "cd src/ui/settings-app && vite build",
|
||||||
|
"build:dashboard-app": "cd src/ui/dashboard-app && vite build",
|
||||||
|
"dev": "tsc --watch",
|
||||||
|
"start": "node dist/main.js",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@modelcontextprotocol/sdk": "^1.0.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.10.2",
|
||||||
|
"@types/react": "^18.3.18",
|
||||||
|
"@types/react-dom": "^18.3.5",
|
||||||
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"typescript": "^5.7.2",
|
||||||
|
"vite": "^6.0.5"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"mcp",
|
||||||
|
"model-context-protocol",
|
||||||
|
"touchbistro",
|
||||||
|
"restaurant",
|
||||||
|
"pos",
|
||||||
|
"point-of-sale",
|
||||||
|
"hospitality",
|
||||||
|
"food-service"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/BusyBee3333/mcpengine"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,235 +0,0 @@
|
|||||||
/**
|
|
||||||
* TouchBistro API Client
|
|
||||||
* Handles authentication, HTTP requests, pagination, and error handling
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
TouchBistroConfig,
|
|
||||||
TouchBistroAuthHeaders,
|
|
||||||
TouchBistroError,
|
|
||||||
PaginatedResponse,
|
|
||||||
PaginationParams,
|
|
||||||
} from '../types/index.js';
|
|
||||||
|
|
||||||
export class TouchBistroClient {
|
|
||||||
private config: TouchBistroConfig;
|
|
||||||
private baseUrl: string;
|
|
||||||
|
|
||||||
constructor(config: TouchBistroConfig) {
|
|
||||||
this.config = config;
|
|
||||||
this.baseUrl = config.baseUrl || 'https://api.touchbistro.com/v1';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get authentication headers
|
|
||||||
*/
|
|
||||||
private getHeaders(): TouchBistroAuthHeaders {
|
|
||||||
return {
|
|
||||||
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'X-Restaurant-ID': this.config.restaurantId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make HTTP request
|
|
||||||
*/
|
|
||||||
private async request<T>(
|
|
||||||
method: string,
|
|
||||||
endpoint: string,
|
|
||||||
body?: any,
|
|
||||||
queryParams?: Record<string, any>
|
|
||||||
): Promise<T> {
|
|
||||||
const url = new URL(`${this.baseUrl}${endpoint}`);
|
|
||||||
|
|
||||||
// Add query parameters
|
|
||||||
if (queryParams) {
|
|
||||||
Object.entries(queryParams).forEach(([key, value]) => {
|
|
||||||
if (value !== undefined && value !== null) {
|
|
||||||
url.searchParams.append(key, String(value));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const options: RequestInit = {
|
|
||||||
method,
|
|
||||||
headers: this.getHeaders(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (body && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
|
|
||||||
options.body = JSON.stringify(body);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(url.toString(), options);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
await this.handleErrorResponse(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle 204 No Content
|
|
||||||
if (response.status === 204) {
|
|
||||||
return {} as T;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
return data as T;
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Error && 'statusCode' in error) {
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
throw this.createError('NETWORK_ERROR', `Network error: ${(error as Error).message}`, 500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle error responses
|
|
||||||
*/
|
|
||||||
private async handleErrorResponse(response: Response): Promise<never> {
|
|
||||||
let errorData: any;
|
|
||||||
try {
|
|
||||||
errorData = await response.json();
|
|
||||||
} catch {
|
|
||||||
errorData = { message: response.statusText };
|
|
||||||
}
|
|
||||||
|
|
||||||
const error: TouchBistroError = {
|
|
||||||
code: errorData.code || `HTTP_${response.status}`,
|
|
||||||
message: errorData.message || errorData.error || response.statusText,
|
|
||||||
details: errorData.details || errorData,
|
|
||||||
statusCode: response.status,
|
|
||||||
};
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a TouchBistro error
|
|
||||||
*/
|
|
||||||
private createError(code: string, message: string, statusCode: number): TouchBistroError {
|
|
||||||
return { code, message, statusCode };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GET request
|
|
||||||
*/
|
|
||||||
async get<T>(endpoint: string, queryParams?: Record<string, any>): Promise<T> {
|
|
||||||
return this.request<T>('GET', endpoint, undefined, queryParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* POST request
|
|
||||||
*/
|
|
||||||
async post<T>(endpoint: string, body?: any, queryParams?: Record<string, any>): Promise<T> {
|
|
||||||
return this.request<T>('POST', endpoint, body, queryParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PUT request
|
|
||||||
*/
|
|
||||||
async put<T>(endpoint: string, body?: any, queryParams?: Record<string, any>): Promise<T> {
|
|
||||||
return this.request<T>('PUT', endpoint, body, queryParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PATCH request
|
|
||||||
*/
|
|
||||||
async patch<T>(endpoint: string, body?: any, queryParams?: Record<string, any>): Promise<T> {
|
|
||||||
return this.request<T>('PATCH', endpoint, body, queryParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DELETE request
|
|
||||||
*/
|
|
||||||
async delete<T>(endpoint: string, queryParams?: Record<string, any>): Promise<T> {
|
|
||||||
return this.request<T>('DELETE', endpoint, undefined, queryParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get paginated results
|
|
||||||
*/
|
|
||||||
async getPaginated<T>(
|
|
||||||
endpoint: string,
|
|
||||||
params?: PaginationParams & Record<string, any>
|
|
||||||
): Promise<PaginatedResponse<T>> {
|
|
||||||
const { page = 1, limit = 50, offset, ...otherParams } = params || {};
|
|
||||||
|
|
||||||
const queryParams: Record<string, any> = {
|
|
||||||
...otherParams,
|
|
||||||
page,
|
|
||||||
limit,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (offset !== undefined) {
|
|
||||||
queryParams.offset = offset;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.get<PaginatedResponse<T>>(endpoint, queryParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all pages of a paginated endpoint
|
|
||||||
*/
|
|
||||||
async getAllPaginated<T>(
|
|
||||||
endpoint: string,
|
|
||||||
params?: Record<string, any>,
|
|
||||||
maxPages: number = 100
|
|
||||||
): Promise<T[]> {
|
|
||||||
const results: T[] = [];
|
|
||||||
let page = 1;
|
|
||||||
let hasMore = true;
|
|
||||||
|
|
||||||
while (hasMore && page <= maxPages) {
|
|
||||||
const response = await this.getPaginated<T>(endpoint, { ...params, page, limit: 100 });
|
|
||||||
results.push(...response.data);
|
|
||||||
|
|
||||||
hasMore = page < response.pagination.totalPages;
|
|
||||||
page++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Batch request helper
|
|
||||||
*/
|
|
||||||
async batch<T>(requests: Array<() => Promise<T>>): Promise<T[]> {
|
|
||||||
return Promise.all(requests.map(req => req()));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test API connection
|
|
||||||
*/
|
|
||||||
async testConnection(): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
await this.get('/health');
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get API configuration
|
|
||||||
*/
|
|
||||||
getConfig(): Readonly<TouchBistroConfig> {
|
|
||||||
return Object.freeze({ ...this.config });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update API configuration
|
|
||||||
*/
|
|
||||||
updateConfig(config: Partial<TouchBistroConfig>): void {
|
|
||||||
this.config = { ...this.config, ...config };
|
|
||||||
if (config.baseUrl) {
|
|
||||||
this.baseUrl = config.baseUrl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create TouchBistro client instance
|
|
||||||
*/
|
|
||||||
export function createTouchBistroClient(config: TouchBistroConfig): TouchBistroClient {
|
|
||||||
return new TouchBistroClient(config);
|
|
||||||
}
|
|
||||||
@ -1,188 +0,0 @@
|
|||||||
/**
|
|
||||||
* TouchBistro Customer Tools
|
|
||||||
* CRUD operations for customer management
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { TouchBistroClient } from '../clients/touchbistro.js';
|
|
||||||
import { Customer } from '../types/index.js';
|
|
||||||
|
|
||||||
export function createCustomerTools(client: TouchBistroClient) {
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* List all customers
|
|
||||||
*/
|
|
||||||
list_customers: async (args: {
|
|
||||||
search?: string;
|
|
||||||
vip?: boolean;
|
|
||||||
tags?: string[];
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<Customer>('/customers', args);
|
|
||||||
return {
|
|
||||||
customers: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a specific customer
|
|
||||||
*/
|
|
||||||
get_customer: async (args: { customerId: string }) => {
|
|
||||||
const customer = await client.get<Customer>(`/customers/${args.customerId}`);
|
|
||||||
return { customer };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new customer
|
|
||||||
*/
|
|
||||||
create_customer: async (args: {
|
|
||||||
firstName: string;
|
|
||||||
lastName: string;
|
|
||||||
email?: string;
|
|
||||||
phone?: string;
|
|
||||||
dateOfBirth?: string;
|
|
||||||
addresses?: Array<{
|
|
||||||
type: 'home' | 'work' | 'other';
|
|
||||||
street: string;
|
|
||||||
city: string;
|
|
||||||
state: string;
|
|
||||||
zipCode: string;
|
|
||||||
country?: string;
|
|
||||||
isDefault?: boolean;
|
|
||||||
}>;
|
|
||||||
allergens?: string[];
|
|
||||||
notes?: string;
|
|
||||||
tags?: string[];
|
|
||||||
vip?: boolean;
|
|
||||||
}) => {
|
|
||||||
const customer = await client.post<Customer>('/customers', args);
|
|
||||||
return { customer, message: 'Customer created successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a customer
|
|
||||||
*/
|
|
||||||
update_customer: async (args: {
|
|
||||||
customerId: string;
|
|
||||||
firstName?: string;
|
|
||||||
lastName?: string;
|
|
||||||
email?: string;
|
|
||||||
phone?: string;
|
|
||||||
dateOfBirth?: string;
|
|
||||||
allergens?: string[];
|
|
||||||
notes?: string;
|
|
||||||
tags?: string[];
|
|
||||||
vip?: boolean;
|
|
||||||
}) => {
|
|
||||||
const { customerId, ...updateData } = args;
|
|
||||||
const customer = await client.patch<Customer>(`/customers/${customerId}`, updateData);
|
|
||||||
return { customer, message: 'Customer updated successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a customer
|
|
||||||
*/
|
|
||||||
delete_customer: async (args: { customerId: string }) => {
|
|
||||||
await client.delete(`/customers/${args.customerId}`);
|
|
||||||
return { message: 'Customer deleted successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search customers
|
|
||||||
*/
|
|
||||||
search_customers: async (args: {
|
|
||||||
query: string;
|
|
||||||
searchBy?: 'name' | 'email' | 'phone';
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<Customer>('/customers/search', args);
|
|
||||||
return {
|
|
||||||
customers: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add customer address
|
|
||||||
*/
|
|
||||||
add_customer_address: async (args: {
|
|
||||||
customerId: string;
|
|
||||||
type: 'home' | 'work' | 'other';
|
|
||||||
street: string;
|
|
||||||
city: string;
|
|
||||||
state: string;
|
|
||||||
zipCode: string;
|
|
||||||
country?: string;
|
|
||||||
isDefault?: boolean;
|
|
||||||
}) => {
|
|
||||||
const { customerId, ...addressData } = args;
|
|
||||||
const customer = await client.post<Customer>(
|
|
||||||
`/customers/${customerId}/addresses`,
|
|
||||||
addressData
|
|
||||||
);
|
|
||||||
return { customer, message: 'Address added successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update customer preferences
|
|
||||||
*/
|
|
||||||
update_customer_preferences: async (args: {
|
|
||||||
customerId: string;
|
|
||||||
favoriteItems?: string[];
|
|
||||||
dietaryRestrictions?: string[];
|
|
||||||
seatingPreference?: string;
|
|
||||||
communicationPreference?: 'email' | 'sms' | 'phone' | 'none';
|
|
||||||
}) => {
|
|
||||||
const { customerId, ...preferences } = args;
|
|
||||||
const customer = await client.patch<Customer>(
|
|
||||||
`/customers/${customerId}/preferences`,
|
|
||||||
preferences
|
|
||||||
);
|
|
||||||
return { customer, message: 'Customer preferences updated successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get customer statistics
|
|
||||||
*/
|
|
||||||
get_customer_stats: async (args: { customerId: string }) => {
|
|
||||||
const stats = await client.get(`/customers/${args.customerId}/stats`);
|
|
||||||
return { stats };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get VIP customers
|
|
||||||
*/
|
|
||||||
get_vip_customers: async (args: { page?: number; limit?: number }) => {
|
|
||||||
const response = await client.getPaginated<Customer>('/customers', {
|
|
||||||
vip: true,
|
|
||||||
...args,
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
customers: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tag customers
|
|
||||||
*/
|
|
||||||
tag_customer: async (args: { customerId: string; tags: string[] }) => {
|
|
||||||
const customer = await client.post<Customer>(`/customers/${args.customerId}/tags`, {
|
|
||||||
tags: args.tags,
|
|
||||||
});
|
|
||||||
return { customer, message: 'Customer tagged successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove customer tags
|
|
||||||
*/
|
|
||||||
untag_customer: async (args: { customerId: string; tags: string[] }) => {
|
|
||||||
const customer = await client.delete<Customer>(`/customers/${args.customerId}/tags`, {
|
|
||||||
tags: args.tags,
|
|
||||||
});
|
|
||||||
return { customer, message: 'Tags removed successfully' };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,200 +0,0 @@
|
|||||||
/**
|
|
||||||
* TouchBistro Discount & Promotion Tools
|
|
||||||
* CRUD operations for discounts and promotions
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { TouchBistroClient } from '../clients/touchbistro.js';
|
|
||||||
import { Discount, Promotion } from '../types/index.js';
|
|
||||||
|
|
||||||
export function createDiscountTools(client: TouchBistroClient) {
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* List all discounts
|
|
||||||
*/
|
|
||||||
list_discounts: async (args: {
|
|
||||||
enabled?: boolean;
|
|
||||||
type?: string;
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<Discount>('/discounts', args);
|
|
||||||
return {
|
|
||||||
discounts: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a specific discount
|
|
||||||
*/
|
|
||||||
get_discount: async (args: { discountId: string }) => {
|
|
||||||
const discount = await client.get<Discount>(`/discounts/${args.discountId}`);
|
|
||||||
return { discount };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new discount
|
|
||||||
*/
|
|
||||||
create_discount: async (args: {
|
|
||||||
name: string;
|
|
||||||
code?: string;
|
|
||||||
type: 'percentage' | 'fixed_amount' | 'buy_x_get_y' | 'free_item';
|
|
||||||
value: number;
|
|
||||||
minPurchase?: number;
|
|
||||||
maxDiscount?: number;
|
|
||||||
applicableItems?: string[];
|
|
||||||
applicableCategories?: string[];
|
|
||||||
startDate?: string;
|
|
||||||
endDate?: string;
|
|
||||||
usageLimit?: number;
|
|
||||||
enabled?: boolean;
|
|
||||||
autoApply?: boolean;
|
|
||||||
stackable?: boolean;
|
|
||||||
}) => {
|
|
||||||
const discount = await client.post<Discount>('/discounts', args);
|
|
||||||
return { discount, message: 'Discount created successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a discount
|
|
||||||
*/
|
|
||||||
update_discount: async (args: {
|
|
||||||
discountId: string;
|
|
||||||
name?: string;
|
|
||||||
code?: string;
|
|
||||||
value?: number;
|
|
||||||
minPurchase?: number;
|
|
||||||
maxDiscount?: number;
|
|
||||||
applicableItems?: string[];
|
|
||||||
applicableCategories?: string[];
|
|
||||||
startDate?: string;
|
|
||||||
endDate?: string;
|
|
||||||
usageLimit?: number;
|
|
||||||
enabled?: boolean;
|
|
||||||
autoApply?: boolean;
|
|
||||||
stackable?: boolean;
|
|
||||||
}) => {
|
|
||||||
const { discountId, ...updateData } = args;
|
|
||||||
const discount = await client.patch<Discount>(`/discounts/${discountId}`, updateData);
|
|
||||||
return { discount, message: 'Discount updated successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a discount
|
|
||||||
*/
|
|
||||||
delete_discount: async (args: { discountId: string }) => {
|
|
||||||
await client.delete(`/discounts/${args.discountId}`);
|
|
||||||
return { message: 'Discount deleted successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate discount code
|
|
||||||
*/
|
|
||||||
validate_discount_code: async (args: { code: string; orderId?: string }) => {
|
|
||||||
const result = await client.post('/discounts/validate', {
|
|
||||||
code: args.code,
|
|
||||||
orderId: args.orderId,
|
|
||||||
});
|
|
||||||
return { result };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply discount to order
|
|
||||||
*/
|
|
||||||
apply_discount: async (args: { orderId: string; discountId: string }) => {
|
|
||||||
const result = await client.post(`/orders/${args.orderId}/apply-discount`, {
|
|
||||||
discountId: args.discountId,
|
|
||||||
});
|
|
||||||
return { result, message: 'Discount applied successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove discount from order
|
|
||||||
*/
|
|
||||||
remove_discount: async (args: { orderId: string; discountId: string }) => {
|
|
||||||
const result = await client.post(`/orders/${args.orderId}/remove-discount`, {
|
|
||||||
discountId: args.discountId,
|
|
||||||
});
|
|
||||||
return { result, message: 'Discount removed successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List all promotions
|
|
||||||
*/
|
|
||||||
list_promotions: async (args: {
|
|
||||||
enabled?: boolean;
|
|
||||||
type?: string;
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<Promotion>('/promotions', args);
|
|
||||||
return {
|
|
||||||
promotions: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a specific promotion
|
|
||||||
*/
|
|
||||||
get_promotion: async (args: { promotionId: string }) => {
|
|
||||||
const promotion = await client.get<Promotion>(`/promotions/${args.promotionId}`);
|
|
||||||
return { promotion };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new promotion
|
|
||||||
*/
|
|
||||||
create_promotion: async (args: {
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
type: 'happy_hour' | 'daily_special' | 'seasonal' | 'event';
|
|
||||||
discountId?: string;
|
|
||||||
daysOfWeek?: number[];
|
|
||||||
startTime?: string;
|
|
||||||
endTime?: string;
|
|
||||||
startDate?: string;
|
|
||||||
endDate?: string;
|
|
||||||
enabled?: boolean;
|
|
||||||
}) => {
|
|
||||||
const promotion = await client.post<Promotion>('/promotions', args);
|
|
||||||
return { promotion, message: 'Promotion created successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a promotion
|
|
||||||
*/
|
|
||||||
update_promotion: async (args: {
|
|
||||||
promotionId: string;
|
|
||||||
name?: string;
|
|
||||||
description?: string;
|
|
||||||
discountId?: string;
|
|
||||||
daysOfWeek?: number[];
|
|
||||||
startTime?: string;
|
|
||||||
endTime?: string;
|
|
||||||
startDate?: string;
|
|
||||||
endDate?: string;
|
|
||||||
enabled?: boolean;
|
|
||||||
}) => {
|
|
||||||
const { promotionId, ...updateData } = args;
|
|
||||||
const promotion = await client.patch<Promotion>(`/promotions/${promotionId}`, updateData);
|
|
||||||
return { promotion, message: 'Promotion updated successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a promotion
|
|
||||||
*/
|
|
||||||
delete_promotion: async (args: { promotionId: string }) => {
|
|
||||||
await client.delete(`/promotions/${args.promotionId}`);
|
|
||||||
return { message: 'Promotion deleted successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get active promotions
|
|
||||||
*/
|
|
||||||
get_active_promotions: async () => {
|
|
||||||
const promotions = await client.get<Promotion[]>('/promotions/active');
|
|
||||||
return { promotions };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,272 +0,0 @@
|
|||||||
/**
|
|
||||||
* TouchBistro Employee Tools
|
|
||||||
* CRUD operations for employee and shift management
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { TouchBistroClient } from '../clients/touchbistro.js';
|
|
||||||
import { Employee, Shift, TimeClockEntry } from '../types/index.js';
|
|
||||||
|
|
||||||
export function createEmployeeTools(client: TouchBistroClient) {
|
|
||||||
return {
|
|
||||||
// ========================================================================
|
|
||||||
// Employee Management
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List all employees
|
|
||||||
*/
|
|
||||||
list_employees: async (args: {
|
|
||||||
role?: string;
|
|
||||||
active?: boolean;
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<Employee>('/employees', args);
|
|
||||||
return {
|
|
||||||
employees: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a specific employee
|
|
||||||
*/
|
|
||||||
get_employee: async (args: { employeeId: string }) => {
|
|
||||||
const employee = await client.get<Employee>(`/employees/${args.employeeId}`);
|
|
||||||
return { employee };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new employee
|
|
||||||
*/
|
|
||||||
create_employee: async (args: {
|
|
||||||
firstName: string;
|
|
||||||
lastName: string;
|
|
||||||
email: string;
|
|
||||||
phone?: string;
|
|
||||||
role: 'server' | 'bartender' | 'host' | 'manager' | 'chef' | 'busser' | 'admin';
|
|
||||||
pin?: string;
|
|
||||||
employeeNumber?: string;
|
|
||||||
hourlyRate?: number;
|
|
||||||
hireDate?: string;
|
|
||||||
dateOfBirth?: string;
|
|
||||||
address?: string;
|
|
||||||
city?: string;
|
|
||||||
state?: string;
|
|
||||||
zipCode?: string;
|
|
||||||
emergencyContact?: {
|
|
||||||
name: string;
|
|
||||||
relationship: string;
|
|
||||||
phone: string;
|
|
||||||
};
|
|
||||||
active?: boolean;
|
|
||||||
}) => {
|
|
||||||
const employee = await client.post<Employee>('/employees', args);
|
|
||||||
return { employee, message: 'Employee created successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update an employee
|
|
||||||
*/
|
|
||||||
update_employee: async (args: {
|
|
||||||
employeeId: string;
|
|
||||||
firstName?: string;
|
|
||||||
lastName?: string;
|
|
||||||
email?: string;
|
|
||||||
phone?: string;
|
|
||||||
role?: string;
|
|
||||||
pin?: string;
|
|
||||||
employeeNumber?: string;
|
|
||||||
hourlyRate?: number;
|
|
||||||
hireDate?: string;
|
|
||||||
active?: boolean;
|
|
||||||
}) => {
|
|
||||||
const { employeeId, ...updateData } = args;
|
|
||||||
const employee = await client.patch<Employee>(`/employees/${employeeId}`, updateData);
|
|
||||||
return { employee, message: 'Employee updated successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete an employee
|
|
||||||
*/
|
|
||||||
delete_employee: async (args: { employeeId: string }) => {
|
|
||||||
await client.delete(`/employees/${args.employeeId}`);
|
|
||||||
return { message: 'Employee deleted successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deactivate an employee
|
|
||||||
*/
|
|
||||||
deactivate_employee: async (args: { employeeId: string }) => {
|
|
||||||
const employee = await client.patch<Employee>(`/employees/${args.employeeId}`, {
|
|
||||||
active: false,
|
|
||||||
});
|
|
||||||
return { employee, message: 'Employee deactivated successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update employee permissions
|
|
||||||
*/
|
|
||||||
update_employee_permissions: async (args: {
|
|
||||||
employeeId: string;
|
|
||||||
canVoidOrders?: boolean;
|
|
||||||
canRefund?: boolean;
|
|
||||||
canDiscountOrders?: boolean;
|
|
||||||
canAccessReports?: boolean;
|
|
||||||
canManageInventory?: boolean;
|
|
||||||
canManageEmployees?: boolean;
|
|
||||||
canManageMenu?: boolean;
|
|
||||||
}) => {
|
|
||||||
const { employeeId, ...permissions } = args;
|
|
||||||
const employee = await client.patch<Employee>(
|
|
||||||
`/employees/${employeeId}/permissions`,
|
|
||||||
permissions
|
|
||||||
);
|
|
||||||
return { employee, message: 'Employee permissions updated successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
// ========================================================================
|
|
||||||
// Shift Management
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List shifts
|
|
||||||
*/
|
|
||||||
list_shifts: async (args: {
|
|
||||||
employeeId?: string;
|
|
||||||
status?: string;
|
|
||||||
startDate?: string;
|
|
||||||
endDate?: string;
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<Shift>('/shifts', args);
|
|
||||||
return {
|
|
||||||
shifts: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a specific shift
|
|
||||||
*/
|
|
||||||
get_shift: async (args: { shiftId: string }) => {
|
|
||||||
const shift = await client.get<Shift>(`/shifts/${args.shiftId}`);
|
|
||||||
return { shift };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new shift
|
|
||||||
*/
|
|
||||||
create_shift: async (args: {
|
|
||||||
employeeId: string;
|
|
||||||
scheduledStart: string;
|
|
||||||
scheduledEnd: string;
|
|
||||||
notes?: string;
|
|
||||||
}) => {
|
|
||||||
const shift = await client.post<Shift>('/shifts', args);
|
|
||||||
return { shift, message: 'Shift created successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start a shift
|
|
||||||
*/
|
|
||||||
start_shift: async (args: { shiftId: string }) => {
|
|
||||||
const shift = await client.post<Shift>(`/shifts/${args.shiftId}/start`);
|
|
||||||
return { shift, message: 'Shift started successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* End a shift
|
|
||||||
*/
|
|
||||||
end_shift: async (args: { shiftId: string }) => {
|
|
||||||
const shift = await client.post<Shift>(`/shifts/${args.shiftId}/end`);
|
|
||||||
return { shift, message: 'Shift ended successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current shifts
|
|
||||||
*/
|
|
||||||
get_current_shifts: async (args: { page?: number; limit?: number }) => {
|
|
||||||
const response = await client.getPaginated<Shift>('/shifts/current', args);
|
|
||||||
return {
|
|
||||||
shifts: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
// ========================================================================
|
|
||||||
// Time Clock
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clock in
|
|
||||||
*/
|
|
||||||
clock_in: async (args: { employeeId: string; shiftId?: string }) => {
|
|
||||||
const entry = await client.post<TimeClockEntry>('/timeclock/clock-in', args);
|
|
||||||
return { entry, message: 'Clocked in successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clock out
|
|
||||||
*/
|
|
||||||
clock_out: async (args: { employeeId: string }) => {
|
|
||||||
const entry = await client.post<TimeClockEntry>('/timeclock/clock-out', {
|
|
||||||
employeeId: args.employeeId,
|
|
||||||
});
|
|
||||||
return { entry, message: 'Clocked out successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Start break
|
|
||||||
*/
|
|
||||||
start_break: async (args: { employeeId: string }) => {
|
|
||||||
const entry = await client.post<TimeClockEntry>('/timeclock/start-break', {
|
|
||||||
employeeId: args.employeeId,
|
|
||||||
});
|
|
||||||
return { entry, message: 'Break started successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* End break
|
|
||||||
*/
|
|
||||||
end_break: async (args: { employeeId: string }) => {
|
|
||||||
const entry = await client.post<TimeClockEntry>('/timeclock/end-break', {
|
|
||||||
employeeId: args.employeeId,
|
|
||||||
});
|
|
||||||
return { entry, message: 'Break ended successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get time clock entries
|
|
||||||
*/
|
|
||||||
get_time_clock_entries: async (args: {
|
|
||||||
employeeId?: string;
|
|
||||||
startDate?: string;
|
|
||||||
endDate?: string;
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<TimeClockEntry>('/timeclock/entries', args);
|
|
||||||
return {
|
|
||||||
entries: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get employee hours
|
|
||||||
*/
|
|
||||||
get_employee_hours: async (args: {
|
|
||||||
employeeId: string;
|
|
||||||
startDate: string;
|
|
||||||
endDate: string;
|
|
||||||
}) => {
|
|
||||||
const hours = await client.get(`/employees/${args.employeeId}/hours`, {
|
|
||||||
startDate: args.startDate,
|
|
||||||
endDate: args.endDate,
|
|
||||||
});
|
|
||||||
return { hours };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,170 +0,0 @@
|
|||||||
/**
|
|
||||||
* TouchBistro Gift Card Tools
|
|
||||||
* CRUD operations for gift card management
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { TouchBistroClient } from '../clients/touchbistro.js';
|
|
||||||
import { GiftCard, GiftCardTransaction } from '../types/index.js';
|
|
||||||
|
|
||||||
export function createGiftCardTools(client: TouchBistroClient) {
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* List all gift cards
|
|
||||||
*/
|
|
||||||
list_gift_cards: async (args: {
|
|
||||||
status?: string;
|
|
||||||
customerId?: string;
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<GiftCard>('/giftcards', args);
|
|
||||||
return {
|
|
||||||
giftCards: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a specific gift card
|
|
||||||
*/
|
|
||||||
get_gift_card: async (args: { giftCardId: string }) => {
|
|
||||||
const giftCard = await client.get<GiftCard>(`/giftcards/${args.giftCardId}`);
|
|
||||||
return { giftCard };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get gift card by card number
|
|
||||||
*/
|
|
||||||
get_gift_card_by_number: async (args: { cardNumber: string }) => {
|
|
||||||
const giftCard = await client.get<GiftCard>(`/giftcards/lookup/${args.cardNumber}`);
|
|
||||||
return { giftCard };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new gift card
|
|
||||||
*/
|
|
||||||
create_gift_card: async (args: {
|
|
||||||
initialBalance: number;
|
|
||||||
customerId?: string;
|
|
||||||
purchasedBy?: string;
|
|
||||||
recipientName?: string;
|
|
||||||
recipientEmail?: string;
|
|
||||||
message?: string;
|
|
||||||
}) => {
|
|
||||||
const giftCard = await client.post<GiftCard>('/giftcards', args);
|
|
||||||
return { giftCard, message: 'Gift card created successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Purchase a gift card
|
|
||||||
*/
|
|
||||||
purchase_gift_card: async (args: {
|
|
||||||
amount: number;
|
|
||||||
customerId?: string;
|
|
||||||
recipientName?: string;
|
|
||||||
recipientEmail?: string;
|
|
||||||
message?: string;
|
|
||||||
paymentMethod: string;
|
|
||||||
}) => {
|
|
||||||
const giftCard = await client.post<GiftCard>('/giftcards/purchase', args);
|
|
||||||
return { giftCard, message: 'Gift card purchased successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reload gift card balance
|
|
||||||
*/
|
|
||||||
reload_gift_card: async (args: {
|
|
||||||
giftCardId: string;
|
|
||||||
amount: number;
|
|
||||||
paymentMethod: string;
|
|
||||||
}) => {
|
|
||||||
const giftCard = await client.post<GiftCard>(`/giftcards/${args.giftCardId}/reload`, {
|
|
||||||
amount: args.amount,
|
|
||||||
paymentMethod: args.paymentMethod,
|
|
||||||
});
|
|
||||||
return { giftCard, message: 'Gift card reloaded successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redeem gift card
|
|
||||||
*/
|
|
||||||
redeem_gift_card: async (args: {
|
|
||||||
giftCardId: string;
|
|
||||||
amount: number;
|
|
||||||
orderId: string;
|
|
||||||
}) => {
|
|
||||||
const transaction = await client.post<GiftCardTransaction>(
|
|
||||||
`/giftcards/${args.giftCardId}/redeem`,
|
|
||||||
{
|
|
||||||
amount: args.amount,
|
|
||||||
orderId: args.orderId,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return { transaction, message: 'Gift card redeemed successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Void gift card
|
|
||||||
*/
|
|
||||||
void_gift_card: async (args: { giftCardId: string; reason?: string }) => {
|
|
||||||
const giftCard = await client.post<GiftCard>(`/giftcards/${args.giftCardId}/void`, {
|
|
||||||
reason: args.reason,
|
|
||||||
});
|
|
||||||
return { giftCard, message: 'Gift card voided successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Activate gift card
|
|
||||||
*/
|
|
||||||
activate_gift_card: async (args: { giftCardId: string }) => {
|
|
||||||
const giftCard = await client.post<GiftCard>(`/giftcards/${args.giftCardId}/activate`);
|
|
||||||
return { giftCard, message: 'Gift card activated successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check gift card balance
|
|
||||||
*/
|
|
||||||
check_balance: async (args: { cardNumber: string; pin?: string }) => {
|
|
||||||
const balance = await client.get(`/giftcards/balance/${args.cardNumber}`, {
|
|
||||||
pin: args.pin,
|
|
||||||
});
|
|
||||||
return { balance };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get gift card transaction history
|
|
||||||
*/
|
|
||||||
get_gift_card_transactions: async (args: {
|
|
||||||
giftCardId: string;
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<GiftCardTransaction>(
|
|
||||||
`/giftcards/${args.giftCardId}/transactions`,
|
|
||||||
{ page: args.page, limit: args.limit }
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
transactions: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send gift card email
|
|
||||||
*/
|
|
||||||
send_gift_card_email: async (args: { giftCardId: string; recipientEmail: string }) => {
|
|
||||||
await client.post(`/giftcards/${args.giftCardId}/send-email`, {
|
|
||||||
recipientEmail: args.recipientEmail,
|
|
||||||
});
|
|
||||||
return { message: 'Gift card email sent successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get gift card sales summary
|
|
||||||
*/
|
|
||||||
get_gift_card_sales: async (args: { startDate?: string; endDate?: string }) => {
|
|
||||||
const summary = await client.get('/giftcards/sales-summary', args);
|
|
||||||
return { summary };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,267 +0,0 @@
|
|||||||
/**
|
|
||||||
* TouchBistro Inventory Tools
|
|
||||||
* CRUD operations for inventory management
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { TouchBistroClient } from '../clients/touchbistro.js';
|
|
||||||
import { InventoryItem, StockAdjustment, PurchaseOrder } from '../types/index.js';
|
|
||||||
|
|
||||||
export function createInventoryTools(client: TouchBistroClient) {
|
|
||||||
return {
|
|
||||||
// ========================================================================
|
|
||||||
// Inventory Items
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List all inventory items
|
|
||||||
*/
|
|
||||||
list_inventory_items: async (args: {
|
|
||||||
category?: string;
|
|
||||||
lowStock?: boolean;
|
|
||||||
supplierId?: string;
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<InventoryItem>('/inventory/items', args);
|
|
||||||
return {
|
|
||||||
items: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a specific inventory item
|
|
||||||
*/
|
|
||||||
get_inventory_item: async (args: { itemId: string }) => {
|
|
||||||
const item = await client.get<InventoryItem>(`/inventory/items/${args.itemId}`);
|
|
||||||
return { item };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new inventory item
|
|
||||||
*/
|
|
||||||
create_inventory_item: async (args: {
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
sku?: string;
|
|
||||||
category?: string;
|
|
||||||
unit: string;
|
|
||||||
currentStock: number;
|
|
||||||
parLevel?: number;
|
|
||||||
reorderPoint?: number;
|
|
||||||
reorderQuantity?: number;
|
|
||||||
cost: number;
|
|
||||||
supplier?: string;
|
|
||||||
supplierId?: string;
|
|
||||||
location?: string;
|
|
||||||
notes?: string;
|
|
||||||
}) => {
|
|
||||||
const item = await client.post<InventoryItem>('/inventory/items', args);
|
|
||||||
return { item, message: 'Inventory item created successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update an inventory item
|
|
||||||
*/
|
|
||||||
update_inventory_item: async (args: {
|
|
||||||
itemId: string;
|
|
||||||
name?: string;
|
|
||||||
description?: string;
|
|
||||||
sku?: string;
|
|
||||||
category?: string;
|
|
||||||
unit?: string;
|
|
||||||
parLevel?: number;
|
|
||||||
reorderPoint?: number;
|
|
||||||
reorderQuantity?: number;
|
|
||||||
cost?: number;
|
|
||||||
supplier?: string;
|
|
||||||
supplierId?: string;
|
|
||||||
location?: string;
|
|
||||||
notes?: string;
|
|
||||||
}) => {
|
|
||||||
const { itemId, ...updateData } = args;
|
|
||||||
const item = await client.patch<InventoryItem>(`/inventory/items/${itemId}`, updateData);
|
|
||||||
return { item, message: 'Inventory item updated successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete an inventory item
|
|
||||||
*/
|
|
||||||
delete_inventory_item: async (args: { itemId: string }) => {
|
|
||||||
await client.delete(`/inventory/items/${args.itemId}`);
|
|
||||||
return { message: 'Inventory item deleted successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adjust inventory stock
|
|
||||||
*/
|
|
||||||
adjust_stock: async (args: {
|
|
||||||
itemId: string;
|
|
||||||
quantity: number;
|
|
||||||
type: 'add' | 'remove' | 'count' | 'waste' | 'transfer';
|
|
||||||
reason?: string;
|
|
||||||
notes?: string;
|
|
||||||
}) => {
|
|
||||||
const { itemId, ...adjustmentData } = args;
|
|
||||||
const adjustment = await client.post<StockAdjustment>(
|
|
||||||
`/inventory/items/${itemId}/adjust`,
|
|
||||||
adjustmentData
|
|
||||||
);
|
|
||||||
return { adjustment, message: 'Stock adjusted successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get stock adjustment history
|
|
||||||
*/
|
|
||||||
get_stock_adjustments: async (args: {
|
|
||||||
itemId?: string;
|
|
||||||
type?: string;
|
|
||||||
startDate?: string;
|
|
||||||
endDate?: string;
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<StockAdjustment>(
|
|
||||||
'/inventory/adjustments',
|
|
||||||
args
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
adjustments: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get low stock items
|
|
||||||
*/
|
|
||||||
get_low_stock_items: async (args: { page?: number; limit?: number }) => {
|
|
||||||
const response = await client.getPaginated<InventoryItem>('/inventory/items/low-stock', args);
|
|
||||||
return {
|
|
||||||
items: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get items to reorder
|
|
||||||
*/
|
|
||||||
get_items_to_reorder: async (args: { page?: number; limit?: number }) => {
|
|
||||||
const response = await client.getPaginated<InventoryItem>('/inventory/items/reorder', args);
|
|
||||||
return {
|
|
||||||
items: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
// ========================================================================
|
|
||||||
// Purchase Orders
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List purchase orders
|
|
||||||
*/
|
|
||||||
list_purchase_orders: async (args: {
|
|
||||||
status?: string;
|
|
||||||
supplierId?: string;
|
|
||||||
startDate?: string;
|
|
||||||
endDate?: string;
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<PurchaseOrder>('/inventory/purchase-orders', args);
|
|
||||||
return {
|
|
||||||
purchaseOrders: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a specific purchase order
|
|
||||||
*/
|
|
||||||
get_purchase_order: async (args: { orderId: string }) => {
|
|
||||||
const order = await client.get<PurchaseOrder>(`/inventory/purchase-orders/${args.orderId}`);
|
|
||||||
return { purchaseOrder: order };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new purchase order
|
|
||||||
*/
|
|
||||||
create_purchase_order: async (args: {
|
|
||||||
supplierId?: string;
|
|
||||||
supplierName: string;
|
|
||||||
items: Array<{
|
|
||||||
inventoryItemId: string;
|
|
||||||
quantity: number;
|
|
||||||
unitCost: number;
|
|
||||||
}>;
|
|
||||||
expectedDelivery?: string;
|
|
||||||
notes?: string;
|
|
||||||
}) => {
|
|
||||||
const order = await client.post<PurchaseOrder>('/inventory/purchase-orders', args);
|
|
||||||
return { purchaseOrder: order, message: 'Purchase order created successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a purchase order
|
|
||||||
*/
|
|
||||||
update_purchase_order: async (args: {
|
|
||||||
orderId: string;
|
|
||||||
status?: string;
|
|
||||||
expectedDelivery?: string;
|
|
||||||
notes?: string;
|
|
||||||
}) => {
|
|
||||||
const { orderId, ...updateData } = args;
|
|
||||||
const order = await client.patch<PurchaseOrder>(
|
|
||||||
`/inventory/purchase-orders/${orderId}`,
|
|
||||||
updateData
|
|
||||||
);
|
|
||||||
return { purchaseOrder: order, message: 'Purchase order updated successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send purchase order to supplier
|
|
||||||
*/
|
|
||||||
send_purchase_order: async (args: { orderId: string; email?: string }) => {
|
|
||||||
await client.post(`/inventory/purchase-orders/${args.orderId}/send`, {
|
|
||||||
email: args.email,
|
|
||||||
});
|
|
||||||
return { message: 'Purchase order sent successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Receive purchase order
|
|
||||||
*/
|
|
||||||
receive_purchase_order: async (args: {
|
|
||||||
orderId: string;
|
|
||||||
items?: Array<{
|
|
||||||
itemId: string;
|
|
||||||
receivedQuantity: number;
|
|
||||||
}>;
|
|
||||||
}) => {
|
|
||||||
const order = await client.post<PurchaseOrder>(
|
|
||||||
`/inventory/purchase-orders/${args.orderId}/receive`,
|
|
||||||
{ items: args.items }
|
|
||||||
);
|
|
||||||
return { purchaseOrder: order, message: 'Purchase order received successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancel purchase order
|
|
||||||
*/
|
|
||||||
cancel_purchase_order: async (args: { orderId: string; reason?: string }) => {
|
|
||||||
const order = await client.post<PurchaseOrder>(
|
|
||||||
`/inventory/purchase-orders/${args.orderId}/cancel`,
|
|
||||||
{ reason: args.reason }
|
|
||||||
);
|
|
||||||
return { purchaseOrder: order, message: 'Purchase order cancelled successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get inventory valuation
|
|
||||||
*/
|
|
||||||
get_inventory_valuation: async () => {
|
|
||||||
const valuation = await client.get('/inventory/valuation');
|
|
||||||
return { valuation };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,140 +0,0 @@
|
|||||||
/**
|
|
||||||
* TouchBistro Loyalty Tools
|
|
||||||
* CRUD operations for loyalty programs and rewards
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { TouchBistroClient } from '../clients/touchbistro.js';
|
|
||||||
import { LoyaltyProgram, LoyaltyTransaction } from '../types/index.js';
|
|
||||||
|
|
||||||
export function createLoyaltyTools(client: TouchBistroClient) {
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* Get loyalty program settings
|
|
||||||
*/
|
|
||||||
get_loyalty_program: async () => {
|
|
||||||
const program = await client.get<LoyaltyProgram>('/loyalty/program');
|
|
||||||
return { program };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update loyalty program settings
|
|
||||||
*/
|
|
||||||
update_loyalty_program: async (args: {
|
|
||||||
name?: string;
|
|
||||||
description?: string;
|
|
||||||
pointsPerDollar?: number;
|
|
||||||
dollarPerPoint?: number;
|
|
||||||
enabled?: boolean;
|
|
||||||
}) => {
|
|
||||||
const program = await client.patch<LoyaltyProgram>('/loyalty/program', args);
|
|
||||||
return { program, message: 'Loyalty program updated successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get customer loyalty points
|
|
||||||
*/
|
|
||||||
get_customer_points: async (args: { customerId: string }) => {
|
|
||||||
const points = await client.get(`/loyalty/customers/${args.customerId}/points`);
|
|
||||||
return { points };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add loyalty points
|
|
||||||
*/
|
|
||||||
add_loyalty_points: async (args: {
|
|
||||||
customerId: string;
|
|
||||||
points: number;
|
|
||||||
description?: string;
|
|
||||||
orderId?: string;
|
|
||||||
}) => {
|
|
||||||
const transaction = await client.post<LoyaltyTransaction>(
|
|
||||||
`/loyalty/customers/${args.customerId}/points/add`,
|
|
||||||
{
|
|
||||||
points: args.points,
|
|
||||||
description: args.description,
|
|
||||||
orderId: args.orderId,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return { transaction, message: 'Loyalty points added successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redeem loyalty points
|
|
||||||
*/
|
|
||||||
redeem_loyalty_points: async (args: {
|
|
||||||
customerId: string;
|
|
||||||
points: number;
|
|
||||||
orderId?: string;
|
|
||||||
}) => {
|
|
||||||
const transaction = await client.post<LoyaltyTransaction>(
|
|
||||||
`/loyalty/customers/${args.customerId}/points/redeem`,
|
|
||||||
{
|
|
||||||
points: args.points,
|
|
||||||
orderId: args.orderId,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return { transaction, message: 'Loyalty points redeemed successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get loyalty transaction history
|
|
||||||
*/
|
|
||||||
get_loyalty_transactions: async (args: {
|
|
||||||
customerId: string;
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<LoyaltyTransaction>(
|
|
||||||
`/loyalty/customers/${args.customerId}/transactions`,
|
|
||||||
{ page: args.page, limit: args.limit }
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
transactions: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all loyalty members
|
|
||||||
*/
|
|
||||||
get_loyalty_members: async (args: {
|
|
||||||
minPoints?: number;
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated('/loyalty/members', args);
|
|
||||||
return {
|
|
||||||
members: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get top loyalty members
|
|
||||||
*/
|
|
||||||
get_top_loyalty_members: async (args: { limit?: number }) => {
|
|
||||||
const members = await client.get('/loyalty/members/top', {
|
|
||||||
limit: args.limit || 10,
|
|
||||||
});
|
|
||||||
return { members };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Award birthday points
|
|
||||||
*/
|
|
||||||
award_birthday_points: async (args: { customerId: string }) => {
|
|
||||||
const transaction = await client.post<LoyaltyTransaction>(
|
|
||||||
`/loyalty/customers/${args.customerId}/birthday-bonus`
|
|
||||||
);
|
|
||||||
return { transaction, message: 'Birthday points awarded successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get loyalty analytics
|
|
||||||
*/
|
|
||||||
get_loyalty_analytics: async (args: { startDate?: string; endDate?: string }) => {
|
|
||||||
const analytics = await client.get('/loyalty/analytics', args);
|
|
||||||
return { analytics };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,334 +0,0 @@
|
|||||||
/**
|
|
||||||
* TouchBistro Menu Tools
|
|
||||||
* CRUD operations for menu items, categories, and modifiers
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { TouchBistroClient } from '../clients/touchbistro.js';
|
|
||||||
import { MenuItem, MenuCategory, ModifierGroup } from '../types/index.js';
|
|
||||||
|
|
||||||
export function createMenuTools(client: TouchBistroClient) {
|
|
||||||
return {
|
|
||||||
// ========================================================================
|
|
||||||
// Menu Items
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List all menu items
|
|
||||||
*/
|
|
||||||
list_menu_items: async (args: {
|
|
||||||
categoryId?: string;
|
|
||||||
enabled?: boolean;
|
|
||||||
available?: boolean;
|
|
||||||
search?: string;
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<MenuItem>('/menu/items', args);
|
|
||||||
return {
|
|
||||||
items: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a specific menu item
|
|
||||||
*/
|
|
||||||
get_menu_item: async (args: { itemId: string }) => {
|
|
||||||
const item = await client.get<MenuItem>(`/menu/items/${args.itemId}`);
|
|
||||||
return { item };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new menu item
|
|
||||||
*/
|
|
||||||
create_menu_item: async (args: {
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
categoryId: string;
|
|
||||||
price: number;
|
|
||||||
cost?: number;
|
|
||||||
sku?: string;
|
|
||||||
barcode?: string;
|
|
||||||
enabled?: boolean;
|
|
||||||
available?: boolean;
|
|
||||||
preparationTime?: number;
|
|
||||||
calories?: number;
|
|
||||||
allergens?: string[];
|
|
||||||
dietary?: string[];
|
|
||||||
modifierGroupIds?: string[];
|
|
||||||
imageUrl?: string;
|
|
||||||
sortOrder?: number;
|
|
||||||
tags?: string[];
|
|
||||||
}) => {
|
|
||||||
const item = await client.post<MenuItem>('/menu/items', args);
|
|
||||||
return { item, message: 'Menu item created successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a menu item
|
|
||||||
*/
|
|
||||||
update_menu_item: async (args: {
|
|
||||||
itemId: string;
|
|
||||||
name?: string;
|
|
||||||
description?: string;
|
|
||||||
categoryId?: string;
|
|
||||||
price?: number;
|
|
||||||
cost?: number;
|
|
||||||
sku?: string;
|
|
||||||
barcode?: string;
|
|
||||||
enabled?: boolean;
|
|
||||||
available?: boolean;
|
|
||||||
preparationTime?: number;
|
|
||||||
calories?: number;
|
|
||||||
allergens?: string[];
|
|
||||||
dietary?: string[];
|
|
||||||
modifierGroupIds?: string[];
|
|
||||||
imageUrl?: string;
|
|
||||||
sortOrder?: number;
|
|
||||||
tags?: string[];
|
|
||||||
}) => {
|
|
||||||
const { itemId, ...updateData } = args;
|
|
||||||
const item = await client.patch<MenuItem>(`/menu/items/${itemId}`, updateData);
|
|
||||||
return { item, message: 'Menu item updated successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a menu item
|
|
||||||
*/
|
|
||||||
delete_menu_item: async (args: { itemId: string }) => {
|
|
||||||
await client.delete(`/menu/items/${args.itemId}`);
|
|
||||||
return { message: 'Menu item deleted successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bulk update menu items
|
|
||||||
*/
|
|
||||||
bulk_update_menu_items: async (args: {
|
|
||||||
updates: Array<{
|
|
||||||
itemId: string;
|
|
||||||
enabled?: boolean;
|
|
||||||
available?: boolean;
|
|
||||||
price?: number;
|
|
||||||
}>;
|
|
||||||
}) => {
|
|
||||||
const result = await client.post('/menu/items/bulk-update', args.updates);
|
|
||||||
return { result, message: 'Menu items updated successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set menu item availability
|
|
||||||
*/
|
|
||||||
set_item_availability: async (args: {
|
|
||||||
itemId: string;
|
|
||||||
available: boolean;
|
|
||||||
}) => {
|
|
||||||
const item = await client.patch<MenuItem>(`/menu/items/${args.itemId}`, {
|
|
||||||
available: args.available,
|
|
||||||
});
|
|
||||||
return { item, message: `Menu item ${args.available ? 'enabled' : 'disabled'} successfully` };
|
|
||||||
},
|
|
||||||
|
|
||||||
// ========================================================================
|
|
||||||
// Menu Categories
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List all menu categories
|
|
||||||
*/
|
|
||||||
list_menu_categories: async (args: {
|
|
||||||
parentCategoryId?: string;
|
|
||||||
enabled?: boolean;
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<MenuCategory>('/menu/categories', args);
|
|
||||||
return {
|
|
||||||
categories: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a specific menu category
|
|
||||||
*/
|
|
||||||
get_menu_category: async (args: { categoryId: string }) => {
|
|
||||||
const category = await client.get<MenuCategory>(`/menu/categories/${args.categoryId}`);
|
|
||||||
return { category };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new menu category
|
|
||||||
*/
|
|
||||||
create_menu_category: async (args: {
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
parentCategoryId?: string;
|
|
||||||
enabled?: boolean;
|
|
||||||
sortOrder?: number;
|
|
||||||
imageUrl?: string;
|
|
||||||
color?: string;
|
|
||||||
}) => {
|
|
||||||
const category = await client.post<MenuCategory>('/menu/categories', args);
|
|
||||||
return { category, message: 'Menu category created successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a menu category
|
|
||||||
*/
|
|
||||||
update_menu_category: async (args: {
|
|
||||||
categoryId: string;
|
|
||||||
name?: string;
|
|
||||||
description?: string;
|
|
||||||
parentCategoryId?: string;
|
|
||||||
enabled?: boolean;
|
|
||||||
sortOrder?: number;
|
|
||||||
imageUrl?: string;
|
|
||||||
color?: string;
|
|
||||||
}) => {
|
|
||||||
const { categoryId, ...updateData } = args;
|
|
||||||
const category = await client.patch<MenuCategory>(
|
|
||||||
`/menu/categories/${categoryId}`,
|
|
||||||
updateData
|
|
||||||
);
|
|
||||||
return { category, message: 'Menu category updated successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a menu category
|
|
||||||
*/
|
|
||||||
delete_menu_category: async (args: { categoryId: string }) => {
|
|
||||||
await client.delete(`/menu/categories/${args.categoryId}`);
|
|
||||||
return { message: 'Menu category deleted successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reorder menu categories
|
|
||||||
*/
|
|
||||||
reorder_menu_categories: async (args: {
|
|
||||||
categoryIds: string[];
|
|
||||||
}) => {
|
|
||||||
await client.post('/menu/categories/reorder', { categoryIds: args.categoryIds });
|
|
||||||
return { message: 'Menu categories reordered successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
// ========================================================================
|
|
||||||
// Modifier Groups & Modifiers
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List all modifier groups
|
|
||||||
*/
|
|
||||||
list_modifier_groups: async (args: { page?: number; limit?: number }) => {
|
|
||||||
const response = await client.getPaginated<ModifierGroup>('/menu/modifier-groups', args);
|
|
||||||
return {
|
|
||||||
modifierGroups: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a specific modifier group
|
|
||||||
*/
|
|
||||||
get_modifier_group: async (args: { groupId: string }) => {
|
|
||||||
const group = await client.get<ModifierGroup>(`/menu/modifier-groups/${args.groupId}`);
|
|
||||||
return { modifierGroup: group };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new modifier group
|
|
||||||
*/
|
|
||||||
create_modifier_group: async (args: {
|
|
||||||
name: string;
|
|
||||||
minSelection?: number;
|
|
||||||
maxSelection?: number;
|
|
||||||
required?: boolean;
|
|
||||||
multiSelect?: boolean;
|
|
||||||
modifiers: Array<{
|
|
||||||
name: string;
|
|
||||||
price: number;
|
|
||||||
enabled?: boolean;
|
|
||||||
default?: boolean;
|
|
||||||
sortOrder?: number;
|
|
||||||
}>;
|
|
||||||
sortOrder?: number;
|
|
||||||
}) => {
|
|
||||||
const group = await client.post<ModifierGroup>('/menu/modifier-groups', args);
|
|
||||||
return { modifierGroup: group, message: 'Modifier group created successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a modifier group
|
|
||||||
*/
|
|
||||||
update_modifier_group: async (args: {
|
|
||||||
groupId: string;
|
|
||||||
name?: string;
|
|
||||||
minSelection?: number;
|
|
||||||
maxSelection?: number;
|
|
||||||
required?: boolean;
|
|
||||||
multiSelect?: boolean;
|
|
||||||
sortOrder?: number;
|
|
||||||
}) => {
|
|
||||||
const { groupId, ...updateData } = args;
|
|
||||||
const group = await client.patch<ModifierGroup>(
|
|
||||||
`/menu/modifier-groups/${groupId}`,
|
|
||||||
updateData
|
|
||||||
);
|
|
||||||
return { modifierGroup: group, message: 'Modifier group updated successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a modifier group
|
|
||||||
*/
|
|
||||||
delete_modifier_group: async (args: { groupId: string }) => {
|
|
||||||
await client.delete(`/menu/modifier-groups/${args.groupId}`);
|
|
||||||
return { message: 'Modifier group deleted successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add modifier to group
|
|
||||||
*/
|
|
||||||
add_modifier: async (args: {
|
|
||||||
groupId: string;
|
|
||||||
name: string;
|
|
||||||
price: number;
|
|
||||||
enabled?: boolean;
|
|
||||||
default?: boolean;
|
|
||||||
sortOrder?: number;
|
|
||||||
}) => {
|
|
||||||
const { groupId, ...modifierData } = args;
|
|
||||||
const modifier = await client.post(
|
|
||||||
`/menu/modifier-groups/${groupId}/modifiers`,
|
|
||||||
modifierData
|
|
||||||
);
|
|
||||||
return { modifier, message: 'Modifier added successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a modifier
|
|
||||||
*/
|
|
||||||
update_modifier: async (args: {
|
|
||||||
groupId: string;
|
|
||||||
modifierId: string;
|
|
||||||
name?: string;
|
|
||||||
price?: number;
|
|
||||||
enabled?: boolean;
|
|
||||||
default?: boolean;
|
|
||||||
sortOrder?: number;
|
|
||||||
}) => {
|
|
||||||
const { groupId, modifierId, ...updateData } = args;
|
|
||||||
const modifier = await client.patch(
|
|
||||||
`/menu/modifier-groups/${groupId}/modifiers/${modifierId}`,
|
|
||||||
updateData
|
|
||||||
);
|
|
||||||
return { modifier, message: 'Modifier updated successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a modifier
|
|
||||||
*/
|
|
||||||
delete_modifier: async (args: { groupId: string; modifierId: string }) => {
|
|
||||||
await client.delete(`/menu/modifier-groups/${args.groupId}/modifiers/${args.modifierId}`);
|
|
||||||
return { message: 'Modifier deleted successfully' };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,227 +0,0 @@
|
|||||||
/**
|
|
||||||
* TouchBistro Orders Tools
|
|
||||||
* CRUD operations for orders and order management
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { TouchBistroClient } from '../clients/touchbistro.js';
|
|
||||||
import { Order, OrderItem, PaginatedResponse, PaginationParams } from '../types/index.js';
|
|
||||||
|
|
||||||
export function createOrdersTools(client: TouchBistroClient) {
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* List all orders with optional filtering
|
|
||||||
*/
|
|
||||||
list_orders: async (args: {
|
|
||||||
status?: string;
|
|
||||||
tableId?: string;
|
|
||||||
customerId?: string;
|
|
||||||
employeeId?: string;
|
|
||||||
orderType?: string;
|
|
||||||
startDate?: string;
|
|
||||||
endDate?: string;
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<Order>('/orders', args);
|
|
||||||
return {
|
|
||||||
orders: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a specific order by ID
|
|
||||||
*/
|
|
||||||
get_order: async (args: { orderId: string }) => {
|
|
||||||
const order = await client.get<Order>(`/orders/${args.orderId}`);
|
|
||||||
return { order };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new order
|
|
||||||
*/
|
|
||||||
create_order: async (args: {
|
|
||||||
tableId?: string;
|
|
||||||
customerId?: string;
|
|
||||||
employeeId: string;
|
|
||||||
orderType: 'dine_in' | 'takeout' | 'delivery' | 'catering';
|
|
||||||
items: Array<{
|
|
||||||
menuItemId: string;
|
|
||||||
quantity: number;
|
|
||||||
modifiers?: Array<{ modifierId: string }>;
|
|
||||||
specialInstructions?: string;
|
|
||||||
}>;
|
|
||||||
notes?: string;
|
|
||||||
specialInstructions?: string;
|
|
||||||
guestCount?: number;
|
|
||||||
scheduledFor?: string;
|
|
||||||
}) => {
|
|
||||||
const order = await client.post<Order>('/orders', args);
|
|
||||||
return { order, message: 'Order created successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update an existing order
|
|
||||||
*/
|
|
||||||
update_order: async (args: {
|
|
||||||
orderId: string;
|
|
||||||
status?: string;
|
|
||||||
notes?: string;
|
|
||||||
specialInstructions?: string;
|
|
||||||
guestCount?: number;
|
|
||||||
}) => {
|
|
||||||
const { orderId, ...updateData } = args;
|
|
||||||
const order = await client.patch<Order>(`/orders/${orderId}`, updateData);
|
|
||||||
return { order, message: 'Order updated successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add items to an existing order
|
|
||||||
*/
|
|
||||||
add_order_items: async (args: {
|
|
||||||
orderId: string;
|
|
||||||
items: Array<{
|
|
||||||
menuItemId: string;
|
|
||||||
quantity: number;
|
|
||||||
modifiers?: Array<{ modifierId: string }>;
|
|
||||||
specialInstructions?: string;
|
|
||||||
}>;
|
|
||||||
}) => {
|
|
||||||
const { orderId, items } = args;
|
|
||||||
const order = await client.post<Order>(`/orders/${orderId}/items`, { items });
|
|
||||||
return { order, message: 'Items added to order successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove an item from an order
|
|
||||||
*/
|
|
||||||
remove_order_item: async (args: {
|
|
||||||
orderId: string;
|
|
||||||
itemId: string;
|
|
||||||
}) => {
|
|
||||||
await client.delete(`/orders/${args.orderId}/items/${args.itemId}`);
|
|
||||||
return { message: 'Item removed from order successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update an order item
|
|
||||||
*/
|
|
||||||
update_order_item: async (args: {
|
|
||||||
orderId: string;
|
|
||||||
itemId: string;
|
|
||||||
quantity?: number;
|
|
||||||
specialInstructions?: string;
|
|
||||||
}) => {
|
|
||||||
const { orderId, itemId, ...updateData } = args;
|
|
||||||
const item = await client.patch<OrderItem>(
|
|
||||||
`/orders/${orderId}/items/${itemId}`,
|
|
||||||
updateData
|
|
||||||
);
|
|
||||||
return { item, message: 'Order item updated successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send order to kitchen
|
|
||||||
*/
|
|
||||||
send_to_kitchen: async (args: { orderId: string; itemIds?: string[] }) => {
|
|
||||||
const order = await client.post<Order>(`/orders/${args.orderId}/send-to-kitchen`, {
|
|
||||||
itemIds: args.itemIds,
|
|
||||||
});
|
|
||||||
return { order, message: 'Order sent to kitchen successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mark order as completed
|
|
||||||
*/
|
|
||||||
complete_order: async (args: { orderId: string }) => {
|
|
||||||
const order = await client.post<Order>(`/orders/${args.orderId}/complete`);
|
|
||||||
return { order, message: 'Order completed successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancel an order
|
|
||||||
*/
|
|
||||||
cancel_order: async (args: { orderId: string; reason?: string }) => {
|
|
||||||
const order = await client.post<Order>(`/orders/${args.orderId}/cancel`, {
|
|
||||||
reason: args.reason,
|
|
||||||
});
|
|
||||||
return { order, message: 'Order cancelled successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Void an order
|
|
||||||
*/
|
|
||||||
void_order: async (args: { orderId: string; reason: string }) => {
|
|
||||||
const order = await client.post<Order>(`/orders/${args.orderId}/void`, {
|
|
||||||
reason: args.reason,
|
|
||||||
});
|
|
||||||
return { order, message: 'Order voided successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get orders for a specific table
|
|
||||||
*/
|
|
||||||
get_table_orders: async (args: {
|
|
||||||
tableId: string;
|
|
||||||
status?: string;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<Order>('/orders', {
|
|
||||||
tableId: args.tableId,
|
|
||||||
status: args.status,
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
orders: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get open orders
|
|
||||||
*/
|
|
||||||
get_open_orders: async (args: { page?: number; limit?: number }) => {
|
|
||||||
const response = await client.getPaginated<Order>('/orders', {
|
|
||||||
status: 'pending,preparing,ready,served',
|
|
||||||
...args,
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
orders: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search orders
|
|
||||||
*/
|
|
||||||
search_orders: async (args: {
|
|
||||||
query: string;
|
|
||||||
searchBy?: 'orderNumber' | 'customerName' | 'tableNumber';
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<Order>('/orders/search', args);
|
|
||||||
return {
|
|
||||||
orders: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get order history for a customer
|
|
||||||
*/
|
|
||||||
get_customer_orders: async (args: {
|
|
||||||
customerId: string;
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<Order>('/orders', {
|
|
||||||
customerId: args.customerId,
|
|
||||||
page: args.page,
|
|
||||||
limit: args.limit,
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
orders: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,177 +0,0 @@
|
|||||||
/**
|
|
||||||
* TouchBistro Payment Tools
|
|
||||||
* CRUD operations for payments, refunds, and transactions
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { TouchBistroClient } from '../clients/touchbistro.js';
|
|
||||||
import { Payment, Refund } from '../types/index.js';
|
|
||||||
|
|
||||||
export function createPaymentTools(client: TouchBistroClient) {
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* List all payments
|
|
||||||
*/
|
|
||||||
list_payments: async (args: {
|
|
||||||
orderId?: string;
|
|
||||||
status?: string;
|
|
||||||
method?: string;
|
|
||||||
startDate?: string;
|
|
||||||
endDate?: string;
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<Payment>('/payments', args);
|
|
||||||
return {
|
|
||||||
payments: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a specific payment
|
|
||||||
*/
|
|
||||||
get_payment: async (args: { paymentId: string }) => {
|
|
||||||
const payment = await client.get<Payment>(`/payments/${args.paymentId}`);
|
|
||||||
return { payment };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Process a payment
|
|
||||||
*/
|
|
||||||
process_payment: async (args: {
|
|
||||||
orderId: string;
|
|
||||||
amount: number;
|
|
||||||
method: 'cash' | 'credit_card' | 'debit_card' | 'gift_card' | 'mobile_payment' | 'other';
|
|
||||||
tip?: number;
|
|
||||||
cardLast4?: string;
|
|
||||||
cardBrand?: string;
|
|
||||||
notes?: string;
|
|
||||||
}) => {
|
|
||||||
const payment = await client.post<Payment>('/payments', args);
|
|
||||||
return { payment, message: 'Payment processed successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authorize a payment
|
|
||||||
*/
|
|
||||||
authorize_payment: async (args: {
|
|
||||||
orderId: string;
|
|
||||||
amount: number;
|
|
||||||
method: string;
|
|
||||||
cardLast4?: string;
|
|
||||||
cardBrand?: string;
|
|
||||||
}) => {
|
|
||||||
const payment = await client.post<Payment>('/payments/authorize', args);
|
|
||||||
return { payment, message: 'Payment authorized successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Capture an authorized payment
|
|
||||||
*/
|
|
||||||
capture_payment: async (args: {
|
|
||||||
paymentId: string;
|
|
||||||
amount?: number;
|
|
||||||
tip?: number;
|
|
||||||
}) => {
|
|
||||||
const payment = await client.post<Payment>(`/payments/${args.paymentId}/capture`, {
|
|
||||||
amount: args.amount,
|
|
||||||
tip: args.tip,
|
|
||||||
});
|
|
||||||
return { payment, message: 'Payment captured successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Void a payment
|
|
||||||
*/
|
|
||||||
void_payment: async (args: { paymentId: string; reason?: string }) => {
|
|
||||||
const payment = await client.post<Payment>(`/payments/${args.paymentId}/void`, {
|
|
||||||
reason: args.reason,
|
|
||||||
});
|
|
||||||
return { payment, message: 'Payment voided successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refund a payment
|
|
||||||
*/
|
|
||||||
refund_payment: async (args: {
|
|
||||||
paymentId: string;
|
|
||||||
amount?: number;
|
|
||||||
reason?: string;
|
|
||||||
notes?: string;
|
|
||||||
}) => {
|
|
||||||
const refund = await client.post<Refund>(`/payments/${args.paymentId}/refund`, {
|
|
||||||
amount: args.amount,
|
|
||||||
reason: args.reason,
|
|
||||||
notes: args.notes,
|
|
||||||
});
|
|
||||||
return { refund, message: 'Payment refunded successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get refunds for a payment
|
|
||||||
*/
|
|
||||||
get_payment_refunds: async (args: { paymentId: string }) => {
|
|
||||||
const refunds = await client.get<Refund[]>(`/payments/${args.paymentId}/refunds`);
|
|
||||||
return { refunds };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List all refunds
|
|
||||||
*/
|
|
||||||
list_refunds: async (args: {
|
|
||||||
startDate?: string;
|
|
||||||
endDate?: string;
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<Refund>('/refunds', args);
|
|
||||||
return {
|
|
||||||
refunds: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Split payment
|
|
||||||
*/
|
|
||||||
split_payment: async (args: {
|
|
||||||
orderId: string;
|
|
||||||
payments: Array<{
|
|
||||||
amount: number;
|
|
||||||
method: string;
|
|
||||||
tip?: number;
|
|
||||||
}>;
|
|
||||||
}) => {
|
|
||||||
const result = await client.post(`/orders/${args.orderId}/split-payment`, {
|
|
||||||
payments: args.payments,
|
|
||||||
});
|
|
||||||
return { result, message: 'Payment split successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add tip to payment
|
|
||||||
*/
|
|
||||||
add_tip: async (args: { paymentId: string; tipAmount: number }) => {
|
|
||||||
const payment = await client.patch<Payment>(`/payments/${args.paymentId}`, {
|
|
||||||
tip: args.tipAmount,
|
|
||||||
});
|
|
||||||
return { payment, message: 'Tip added successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get payment summary
|
|
||||||
*/
|
|
||||||
get_payment_summary: async (args: { startDate?: string; endDate?: string }) => {
|
|
||||||
const summary = await client.get('/payments/summary', args);
|
|
||||||
return { summary };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get payment methods summary
|
|
||||||
*/
|
|
||||||
get_payment_methods_summary: async (args: { startDate?: string; endDate?: string }) => {
|
|
||||||
const summary = await client.get('/payments/methods-summary', args);
|
|
||||||
return { summary };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,236 +0,0 @@
|
|||||||
/**
|
|
||||||
* TouchBistro Reports Tools
|
|
||||||
* Analytics and reporting operations
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { TouchBistroClient } from '../clients/touchbistro.js';
|
|
||||||
import { SalesReport, EmployeePerformance, MenuPerformanceReport } from '../types/index.js';
|
|
||||||
|
|
||||||
export function createReportsTools(client: TouchBistroClient) {
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* Get sales report
|
|
||||||
*/
|
|
||||||
get_sales_report: async (args: { startDate: string; endDate: string }) => {
|
|
||||||
const report = await client.get<SalesReport>('/reports/sales', {
|
|
||||||
startDate: args.startDate,
|
|
||||||
endDate: args.endDate,
|
|
||||||
});
|
|
||||||
return { report };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get daily sales summary
|
|
||||||
*/
|
|
||||||
get_daily_sales: async (args: { date?: string }) => {
|
|
||||||
const date = args.date || new Date().toISOString().split('T')[0];
|
|
||||||
const summary = await client.get('/reports/sales/daily', { date });
|
|
||||||
return { summary };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get sales by hour
|
|
||||||
*/
|
|
||||||
get_sales_by_hour: async (args: { startDate: string; endDate: string }) => {
|
|
||||||
const data = await client.get('/reports/sales/by-hour', args);
|
|
||||||
return { data };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get sales by day of week
|
|
||||||
*/
|
|
||||||
get_sales_by_day: async (args: { startDate: string; endDate: string }) => {
|
|
||||||
const data = await client.get('/reports/sales/by-day', args);
|
|
||||||
return { data };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get sales by category
|
|
||||||
*/
|
|
||||||
get_sales_by_category: async (args: { startDate: string; endDate: string }) => {
|
|
||||||
const data = await client.get('/reports/sales/by-category', args);
|
|
||||||
return { data };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get sales by payment method
|
|
||||||
*/
|
|
||||||
get_sales_by_payment_method: async (args: { startDate: string; endDate: string }) => {
|
|
||||||
const data = await client.get('/reports/sales/by-payment-method', args);
|
|
||||||
return { data };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get top selling items
|
|
||||||
*/
|
|
||||||
get_top_selling_items: async (args: {
|
|
||||||
startDate: string;
|
|
||||||
endDate: string;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const items = await client.get('/reports/menu/top-selling', {
|
|
||||||
...args,
|
|
||||||
limit: args.limit || 20,
|
|
||||||
});
|
|
||||||
return { items };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get menu performance report
|
|
||||||
*/
|
|
||||||
get_menu_performance: async (args: { startDate: string; endDate: string }) => {
|
|
||||||
const report = await client.get<MenuPerformanceReport>('/reports/menu/performance', args);
|
|
||||||
return { report };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get menu item analytics
|
|
||||||
*/
|
|
||||||
get_menu_item_analytics: async (args: {
|
|
||||||
itemId: string;
|
|
||||||
startDate: string;
|
|
||||||
endDate: string;
|
|
||||||
}) => {
|
|
||||||
const analytics = await client.get(`/reports/menu/items/${args.itemId}/analytics`, {
|
|
||||||
startDate: args.startDate,
|
|
||||||
endDate: args.endDate,
|
|
||||||
});
|
|
||||||
return { analytics };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get employee performance report
|
|
||||||
*/
|
|
||||||
get_employee_performance: async (args: { startDate: string; endDate: string }) => {
|
|
||||||
const report = await client.get<EmployeePerformance[]>('/reports/employees/performance', args);
|
|
||||||
return { report };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get employee sales report
|
|
||||||
*/
|
|
||||||
get_employee_sales: async (args: {
|
|
||||||
employeeId: string;
|
|
||||||
startDate: string;
|
|
||||||
endDate: string;
|
|
||||||
}) => {
|
|
||||||
const report = await client.get(`/reports/employees/${args.employeeId}/sales`, {
|
|
||||||
startDate: args.startDate,
|
|
||||||
endDate: args.endDate,
|
|
||||||
});
|
|
||||||
return { report };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get labor cost report
|
|
||||||
*/
|
|
||||||
get_labor_cost_report: async (args: { startDate: string; endDate: string }) => {
|
|
||||||
const report = await client.get('/reports/labor/costs', args);
|
|
||||||
return { report };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get customer analytics
|
|
||||||
*/
|
|
||||||
get_customer_analytics: async (args: { startDate: string; endDate: string }) => {
|
|
||||||
const analytics = await client.get('/reports/customers/analytics', args);
|
|
||||||
return { analytics };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get customer acquisition report
|
|
||||||
*/
|
|
||||||
get_customer_acquisition: async (args: { startDate: string; endDate: string }) => {
|
|
||||||
const report = await client.get('/reports/customers/acquisition', args);
|
|
||||||
return { report };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get customer retention report
|
|
||||||
*/
|
|
||||||
get_customer_retention: async (args: { startDate: string; endDate: string }) => {
|
|
||||||
const report = await client.get('/reports/customers/retention', args);
|
|
||||||
return { report };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get tax report
|
|
||||||
*/
|
|
||||||
get_tax_report: async (args: { startDate: string; endDate: string }) => {
|
|
||||||
const report = await client.get('/reports/tax', args);
|
|
||||||
return { report };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get discount usage report
|
|
||||||
*/
|
|
||||||
get_discount_usage: async (args: { startDate: string; endDate: string }) => {
|
|
||||||
const report = await client.get('/reports/discounts/usage', args);
|
|
||||||
return { report };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get tip report
|
|
||||||
*/
|
|
||||||
get_tip_report: async (args: { startDate: string; endDate: string }) => {
|
|
||||||
const report = await client.get('/reports/tips', args);
|
|
||||||
return { report };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get order type distribution
|
|
||||||
*/
|
|
||||||
get_order_type_distribution: async (args: { startDate: string; endDate: string }) => {
|
|
||||||
const distribution = await client.get('/reports/orders/type-distribution', args);
|
|
||||||
return { distribution };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get table turnover report
|
|
||||||
*/
|
|
||||||
get_table_turnover: async (args: { startDate: string; endDate: string }) => {
|
|
||||||
const report = await client.get('/reports/tables/turnover', args);
|
|
||||||
return { report };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get average order value
|
|
||||||
*/
|
|
||||||
get_average_order_value: async (args: { startDate: string; endDate: string }) => {
|
|
||||||
const aov = await client.get('/reports/aov', args);
|
|
||||||
return { aov };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Export report to CSV
|
|
||||||
*/
|
|
||||||
export_report_csv: async (args: {
|
|
||||||
reportType: string;
|
|
||||||
startDate: string;
|
|
||||||
endDate: string;
|
|
||||||
}) => {
|
|
||||||
const csv = await client.get(`/reports/${args.reportType}/export`, {
|
|
||||||
format: 'csv',
|
|
||||||
startDate: args.startDate,
|
|
||||||
endDate: args.endDate,
|
|
||||||
});
|
|
||||||
return { csv };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get real-time dashboard
|
|
||||||
*/
|
|
||||||
get_realtime_dashboard: async () => {
|
|
||||||
const dashboard = await client.get('/reports/realtime/dashboard');
|
|
||||||
return { dashboard };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get profit and loss report
|
|
||||||
*/
|
|
||||||
get_profit_loss_report: async (args: { startDate: string; endDate: string }) => {
|
|
||||||
const report = await client.get('/reports/profit-loss', args);
|
|
||||||
return { report };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,216 +0,0 @@
|
|||||||
/**
|
|
||||||
* TouchBistro Reservation Tools
|
|
||||||
* CRUD operations for reservation management
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { TouchBistroClient } from '../clients/touchbistro.js';
|
|
||||||
import { Reservation } from '../types/index.js';
|
|
||||||
|
|
||||||
export function createReservationTools(client: TouchBistroClient) {
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
* List all reservations
|
|
||||||
*/
|
|
||||||
list_reservations: async (args: {
|
|
||||||
status?: string;
|
|
||||||
date?: string;
|
|
||||||
customerId?: string;
|
|
||||||
startDate?: string;
|
|
||||||
endDate?: string;
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<Reservation>('/reservations', args);
|
|
||||||
return {
|
|
||||||
reservations: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a specific reservation
|
|
||||||
*/
|
|
||||||
get_reservation: async (args: { reservationId: string }) => {
|
|
||||||
const reservation = await client.get<Reservation>(`/reservations/${args.reservationId}`);
|
|
||||||
return { reservation };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new reservation
|
|
||||||
*/
|
|
||||||
create_reservation: async (args: {
|
|
||||||
customerId?: string;
|
|
||||||
customerName: string;
|
|
||||||
customerPhone: string;
|
|
||||||
customerEmail?: string;
|
|
||||||
partySize: number;
|
|
||||||
date: string;
|
|
||||||
time: string;
|
|
||||||
duration?: number;
|
|
||||||
tableId?: string;
|
|
||||||
notes?: string;
|
|
||||||
specialOccasion?: string;
|
|
||||||
preferences?: string;
|
|
||||||
}) => {
|
|
||||||
const reservation = await client.post<Reservation>('/reservations', args);
|
|
||||||
return { reservation, message: 'Reservation created successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a reservation
|
|
||||||
*/
|
|
||||||
update_reservation: async (args: {
|
|
||||||
reservationId: string;
|
|
||||||
customerName?: string;
|
|
||||||
customerPhone?: string;
|
|
||||||
customerEmail?: string;
|
|
||||||
partySize?: number;
|
|
||||||
date?: string;
|
|
||||||
time?: string;
|
|
||||||
duration?: number;
|
|
||||||
tableId?: string;
|
|
||||||
notes?: string;
|
|
||||||
specialOccasion?: string;
|
|
||||||
preferences?: string;
|
|
||||||
}) => {
|
|
||||||
const { reservationId, ...updateData } = args;
|
|
||||||
const reservation = await client.patch<Reservation>(
|
|
||||||
`/reservations/${reservationId}`,
|
|
||||||
updateData
|
|
||||||
);
|
|
||||||
return { reservation, message: 'Reservation updated successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a reservation
|
|
||||||
*/
|
|
||||||
delete_reservation: async (args: { reservationId: string }) => {
|
|
||||||
await client.delete(`/reservations/${args.reservationId}`);
|
|
||||||
return { message: 'Reservation deleted successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Confirm a reservation
|
|
||||||
*/
|
|
||||||
confirm_reservation: async (args: { reservationId: string }) => {
|
|
||||||
const reservation = await client.post<Reservation>(
|
|
||||||
`/reservations/${args.reservationId}/confirm`
|
|
||||||
);
|
|
||||||
return { reservation, message: 'Reservation confirmed successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancel a reservation
|
|
||||||
*/
|
|
||||||
cancel_reservation: async (args: { reservationId: string; reason?: string }) => {
|
|
||||||
const reservation = await client.post<Reservation>(
|
|
||||||
`/reservations/${args.reservationId}/cancel`,
|
|
||||||
{ reason: args.reason }
|
|
||||||
);
|
|
||||||
return { reservation, message: 'Reservation cancelled successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Seat a reservation
|
|
||||||
*/
|
|
||||||
seat_reservation: async (args: { reservationId: string; tableId: string }) => {
|
|
||||||
const reservation = await client.post<Reservation>(
|
|
||||||
`/reservations/${args.reservationId}/seat`,
|
|
||||||
{ tableId: args.tableId }
|
|
||||||
);
|
|
||||||
return { reservation, message: 'Reservation seated successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mark as no-show
|
|
||||||
*/
|
|
||||||
mark_no_show: async (args: { reservationId: string }) => {
|
|
||||||
const reservation = await client.post<Reservation>(
|
|
||||||
`/reservations/${args.reservationId}/no-show`
|
|
||||||
);
|
|
||||||
return { reservation, message: 'Reservation marked as no-show' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Complete a reservation
|
|
||||||
*/
|
|
||||||
complete_reservation: async (args: { reservationId: string }) => {
|
|
||||||
const reservation = await client.post<Reservation>(
|
|
||||||
`/reservations/${args.reservationId}/complete`
|
|
||||||
);
|
|
||||||
return { reservation, message: 'Reservation completed successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get reservations for a date
|
|
||||||
*/
|
|
||||||
get_reservations_by_date: async (args: { date: string; page?: number; limit?: number }) => {
|
|
||||||
const response = await client.getPaginated<Reservation>('/reservations', {
|
|
||||||
date: args.date,
|
|
||||||
page: args.page,
|
|
||||||
limit: args.limit,
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
reservations: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get upcoming reservations
|
|
||||||
*/
|
|
||||||
get_upcoming_reservations: async (args: { days?: number; page?: number; limit?: number }) => {
|
|
||||||
const today = new Date();
|
|
||||||
const endDate = new Date(today);
|
|
||||||
endDate.setDate(endDate.getDate() + (args.days || 7));
|
|
||||||
|
|
||||||
const response = await client.getPaginated<Reservation>('/reservations', {
|
|
||||||
startDate: today.toISOString().split('T')[0],
|
|
||||||
endDate: endDate.toISOString().split('T')[0],
|
|
||||||
page: args.page,
|
|
||||||
limit: args.limit,
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
reservations: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search reservations
|
|
||||||
*/
|
|
||||||
search_reservations: async (args: {
|
|
||||||
query: string;
|
|
||||||
searchBy?: 'customerName' | 'phone' | 'email';
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<Reservation>('/reservations/search', args);
|
|
||||||
return {
|
|
||||||
reservations: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send reservation reminder
|
|
||||||
*/
|
|
||||||
send_reminder: async (args: { reservationId: string; method?: 'email' | 'sms' }) => {
|
|
||||||
await client.post(`/reservations/${args.reservationId}/send-reminder`, {
|
|
||||||
method: args.method,
|
|
||||||
});
|
|
||||||
return { message: 'Reminder sent successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get available time slots
|
|
||||||
*/
|
|
||||||
get_available_slots: async (args: { date: string; partySize: number }) => {
|
|
||||||
const slots = await client.get('/reservations/available-slots', {
|
|
||||||
date: args.date,
|
|
||||||
partySize: args.partySize,
|
|
||||||
});
|
|
||||||
return { slots };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,234 +0,0 @@
|
|||||||
/**
|
|
||||||
* TouchBistro Table Tools
|
|
||||||
* CRUD operations for table and floor management
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { TouchBistroClient } from '../clients/touchbistro.js';
|
|
||||||
import { Table, Floor } from '../types/index.js';
|
|
||||||
|
|
||||||
export function createTableTools(client: TouchBistroClient) {
|
|
||||||
return {
|
|
||||||
// ========================================================================
|
|
||||||
// Table Management
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List all tables
|
|
||||||
*/
|
|
||||||
list_tables: async (args: {
|
|
||||||
status?: string;
|
|
||||||
section?: string;
|
|
||||||
floorId?: string;
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<Table>('/tables', args);
|
|
||||||
return {
|
|
||||||
tables: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a specific table
|
|
||||||
*/
|
|
||||||
get_table: async (args: { tableId: string }) => {
|
|
||||||
const table = await client.get<Table>(`/tables/${args.tableId}`);
|
|
||||||
return { table };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new table
|
|
||||||
*/
|
|
||||||
create_table: async (args: {
|
|
||||||
tableNumber: string;
|
|
||||||
name?: string;
|
|
||||||
capacity: number;
|
|
||||||
minCapacity?: number;
|
|
||||||
section?: string;
|
|
||||||
floorId?: string;
|
|
||||||
shape?: 'round' | 'square' | 'rectangular';
|
|
||||||
x?: number;
|
|
||||||
y?: number;
|
|
||||||
notes?: string;
|
|
||||||
}) => {
|
|
||||||
const table = await client.post<Table>('/tables', args);
|
|
||||||
return { table, message: 'Table created successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a table
|
|
||||||
*/
|
|
||||||
update_table: async (args: {
|
|
||||||
tableId: string;
|
|
||||||
tableNumber?: string;
|
|
||||||
name?: string;
|
|
||||||
capacity?: number;
|
|
||||||
minCapacity?: number;
|
|
||||||
section?: string;
|
|
||||||
floorId?: string;
|
|
||||||
shape?: 'round' | 'square' | 'rectangular';
|
|
||||||
x?: number;
|
|
||||||
y?: number;
|
|
||||||
notes?: string;
|
|
||||||
}) => {
|
|
||||||
const { tableId, ...updateData } = args;
|
|
||||||
const table = await client.patch<Table>(`/tables/${tableId}`, updateData);
|
|
||||||
return { table, message: 'Table updated successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a table
|
|
||||||
*/
|
|
||||||
delete_table: async (args: { tableId: string }) => {
|
|
||||||
await client.delete(`/tables/${args.tableId}`);
|
|
||||||
return { message: 'Table deleted successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set table status
|
|
||||||
*/
|
|
||||||
set_table_status: async (args: {
|
|
||||||
tableId: string;
|
|
||||||
status: 'available' | 'occupied' | 'reserved' | 'cleaning';
|
|
||||||
}) => {
|
|
||||||
const table = await client.patch<Table>(`/tables/${args.tableId}`, {
|
|
||||||
status: args.status,
|
|
||||||
});
|
|
||||||
return { table, message: `Table status set to ${args.status}` };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assign server to table
|
|
||||||
*/
|
|
||||||
assign_server: async (args: { tableId: string; serverId: string }) => {
|
|
||||||
const table = await client.patch<Table>(`/tables/${args.tableId}`, {
|
|
||||||
assignedServerId: args.serverId,
|
|
||||||
});
|
|
||||||
return { table, message: 'Server assigned to table successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get available tables
|
|
||||||
*/
|
|
||||||
get_available_tables: async (args: {
|
|
||||||
partySize?: number;
|
|
||||||
floorId?: string;
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<Table>('/tables', {
|
|
||||||
status: 'available',
|
|
||||||
...args,
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
tables: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get occupied tables
|
|
||||||
*/
|
|
||||||
get_occupied_tables: async (args: {
|
|
||||||
floorId?: string;
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
}) => {
|
|
||||||
const response = await client.getPaginated<Table>('/tables', {
|
|
||||||
status: 'occupied',
|
|
||||||
...args,
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
tables: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merge tables
|
|
||||||
*/
|
|
||||||
merge_tables: async (args: { tableIds: string[]; name?: string }) => {
|
|
||||||
const result = await client.post('/tables/merge', {
|
|
||||||
tableIds: args.tableIds,
|
|
||||||
name: args.name,
|
|
||||||
});
|
|
||||||
return { result, message: 'Tables merged successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Split tables
|
|
||||||
*/
|
|
||||||
split_tables: async (args: { tableId: string }) => {
|
|
||||||
const result = await client.post(`/tables/${args.tableId}/split`);
|
|
||||||
return { result, message: 'Tables split successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
// ========================================================================
|
|
||||||
// Floor Management
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List all floors
|
|
||||||
*/
|
|
||||||
list_floors: async (args: { enabled?: boolean; page?: number; limit?: number }) => {
|
|
||||||
const response = await client.getPaginated<Floor>('/floors', args);
|
|
||||||
return {
|
|
||||||
floors: response.data,
|
|
||||||
pagination: response.pagination,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a specific floor
|
|
||||||
*/
|
|
||||||
get_floor: async (args: { floorId: string }) => {
|
|
||||||
const floor = await client.get<Floor>(`/floors/${args.floorId}`);
|
|
||||||
return { floor };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new floor
|
|
||||||
*/
|
|
||||||
create_floor: async (args: {
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
sortOrder?: number;
|
|
||||||
enabled?: boolean;
|
|
||||||
}) => {
|
|
||||||
const floor = await client.post<Floor>('/floors', args);
|
|
||||||
return { floor, message: 'Floor created successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a floor
|
|
||||||
*/
|
|
||||||
update_floor: async (args: {
|
|
||||||
floorId: string;
|
|
||||||
name?: string;
|
|
||||||
description?: string;
|
|
||||||
sortOrder?: number;
|
|
||||||
enabled?: boolean;
|
|
||||||
}) => {
|
|
||||||
const { floorId, ...updateData } = args;
|
|
||||||
const floor = await client.patch<Floor>(`/floors/${floorId}`, updateData);
|
|
||||||
return { floor, message: 'Floor updated successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a floor
|
|
||||||
*/
|
|
||||||
delete_floor: async (args: { floorId: string }) => {
|
|
||||||
await client.delete(`/floors/${args.floorId}`);
|
|
||||||
return { message: 'Floor deleted successfully' };
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get floor layout
|
|
||||||
*/
|
|
||||||
get_floor_layout: async (args: { floorId: string }) => {
|
|
||||||
const layout = await client.get(`/floors/${args.floorId}/layout`);
|
|
||||||
return { layout };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,743 +0,0 @@
|
|||||||
/**
|
|
||||||
* TouchBistro MCP Server - TypeScript Type Definitions
|
|
||||||
* Comprehensive types for TouchBistro restaurant management platform
|
|
||||||
*/
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// API Configuration & Authentication
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
export interface TouchBistroConfig {
|
|
||||||
apiKey: string;
|
|
||||||
restaurantId: string;
|
|
||||||
baseUrl?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TouchBistroAuthHeaders {
|
|
||||||
'Authorization': string;
|
|
||||||
'Content-Type': string;
|
|
||||||
'X-Restaurant-ID': string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Common Types
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
export interface PaginationParams {
|
|
||||||
page?: number;
|
|
||||||
limit?: number;
|
|
||||||
offset?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PaginatedResponse<T> {
|
|
||||||
data: T[];
|
|
||||||
pagination: {
|
|
||||||
page: number;
|
|
||||||
limit: number;
|
|
||||||
total: number;
|
|
||||||
totalPages: number;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DateRange {
|
|
||||||
startDate: string;
|
|
||||||
endDate: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type OrderStatus = 'pending' | 'preparing' | 'ready' | 'served' | 'completed' | 'cancelled' | 'voided';
|
|
||||||
export type PaymentStatus = 'pending' | 'authorized' | 'captured' | 'refunded' | 'failed' | 'cancelled';
|
|
||||||
export type PaymentMethod = 'cash' | 'credit_card' | 'debit_card' | 'gift_card' | 'mobile_payment' | 'other';
|
|
||||||
export type TableStatus = 'available' | 'occupied' | 'reserved' | 'cleaning';
|
|
||||||
export type ReservationStatus = 'pending' | 'confirmed' | 'seated' | 'completed' | 'cancelled' | 'no_show';
|
|
||||||
export type EmployeeRole = 'server' | 'bartender' | 'host' | 'manager' | 'chef' | 'busser' | 'admin';
|
|
||||||
export type ShiftStatus = 'scheduled' | 'started' | 'break' | 'ended' | 'no_show';
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Menu Types
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
export interface MenuItem {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
categoryId: string;
|
|
||||||
price: number;
|
|
||||||
cost?: number;
|
|
||||||
sku?: string;
|
|
||||||
barcode?: string;
|
|
||||||
enabled: boolean;
|
|
||||||
available: boolean;
|
|
||||||
preparationTime?: number; // in minutes
|
|
||||||
calories?: number;
|
|
||||||
allergens?: string[];
|
|
||||||
dietary?: string[]; // vegan, vegetarian, gluten-free, etc.
|
|
||||||
modifierGroupIds?: string[];
|
|
||||||
imageUrl?: string;
|
|
||||||
sortOrder?: number;
|
|
||||||
tags?: string[];
|
|
||||||
variants?: MenuItemVariant[];
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MenuItemVariant {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
price: number;
|
|
||||||
sku?: string;
|
|
||||||
enabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MenuCategory {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
parentCategoryId?: string;
|
|
||||||
enabled: boolean;
|
|
||||||
sortOrder: number;
|
|
||||||
imageUrl?: string;
|
|
||||||
color?: string;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ModifierGroup {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
minSelection: number;
|
|
||||||
maxSelection: number;
|
|
||||||
required: boolean;
|
|
||||||
multiSelect: boolean;
|
|
||||||
modifiers: Modifier[];
|
|
||||||
sortOrder?: number;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Modifier {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
price: number;
|
|
||||||
enabled: boolean;
|
|
||||||
default?: boolean;
|
|
||||||
sortOrder?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Order Types
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
export interface Order {
|
|
||||||
id: string;
|
|
||||||
orderNumber: string;
|
|
||||||
restaurantId: string;
|
|
||||||
tableId?: string;
|
|
||||||
customerId?: string;
|
|
||||||
employeeId: string;
|
|
||||||
status: OrderStatus;
|
|
||||||
orderType: 'dine_in' | 'takeout' | 'delivery' | 'catering';
|
|
||||||
items: OrderItem[];
|
|
||||||
subtotal: number;
|
|
||||||
tax: number;
|
|
||||||
tip?: number;
|
|
||||||
discount?: number;
|
|
||||||
total: number;
|
|
||||||
payments?: Payment[];
|
|
||||||
notes?: string;
|
|
||||||
specialInstructions?: string;
|
|
||||||
guestCount?: number;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
completedAt?: string;
|
|
||||||
scheduledFor?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OrderItem {
|
|
||||||
id: string;
|
|
||||||
menuItemId: string;
|
|
||||||
menuItemName: string;
|
|
||||||
quantity: number;
|
|
||||||
unitPrice: number;
|
|
||||||
totalPrice: number;
|
|
||||||
modifiers?: OrderItemModifier[];
|
|
||||||
specialInstructions?: string;
|
|
||||||
sentToKitchen: boolean;
|
|
||||||
preparedAt?: string;
|
|
||||||
servedAt?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OrderItemModifier {
|
|
||||||
id: string;
|
|
||||||
modifierId: string;
|
|
||||||
name: string;
|
|
||||||
price: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Payment Types
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
export interface Payment {
|
|
||||||
id: string;
|
|
||||||
orderId: string;
|
|
||||||
amount: number;
|
|
||||||
method: PaymentMethod;
|
|
||||||
status: PaymentStatus;
|
|
||||||
transactionId?: string;
|
|
||||||
cardLast4?: string;
|
|
||||||
cardBrand?: string;
|
|
||||||
tip?: number;
|
|
||||||
processedBy?: string;
|
|
||||||
processedAt?: string;
|
|
||||||
refundedAmount?: number;
|
|
||||||
refundedAt?: string;
|
|
||||||
notes?: string;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Refund {
|
|
||||||
id: string;
|
|
||||||
paymentId: string;
|
|
||||||
orderId: string;
|
|
||||||
amount: number;
|
|
||||||
reason?: string;
|
|
||||||
processedBy: string;
|
|
||||||
processedAt: string;
|
|
||||||
notes?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Customer Types
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
export interface Customer {
|
|
||||||
id: string;
|
|
||||||
firstName: string;
|
|
||||||
lastName: string;
|
|
||||||
email?: string;
|
|
||||||
phone?: string;
|
|
||||||
dateOfBirth?: string;
|
|
||||||
addresses?: CustomerAddress[];
|
|
||||||
preferences?: CustomerPreferences;
|
|
||||||
allergens?: string[];
|
|
||||||
notes?: string;
|
|
||||||
loyaltyPoints?: number;
|
|
||||||
totalSpent?: number;
|
|
||||||
visitCount?: number;
|
|
||||||
averageOrderValue?: number;
|
|
||||||
lastVisit?: string;
|
|
||||||
tags?: string[];
|
|
||||||
vip?: boolean;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CustomerAddress {
|
|
||||||
id: string;
|
|
||||||
type: 'home' | 'work' | 'other';
|
|
||||||
street: string;
|
|
||||||
city: string;
|
|
||||||
state: string;
|
|
||||||
zipCode: string;
|
|
||||||
country?: string;
|
|
||||||
isDefault?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CustomerPreferences {
|
|
||||||
favoriteItems?: string[];
|
|
||||||
dietaryRestrictions?: string[];
|
|
||||||
seatingPreference?: string;
|
|
||||||
communicationPreference?: 'email' | 'sms' | 'phone' | 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Employee Types
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
export interface Employee {
|
|
||||||
id: string;
|
|
||||||
firstName: string;
|
|
||||||
lastName: string;
|
|
||||||
email: string;
|
|
||||||
phone?: string;
|
|
||||||
role: EmployeeRole;
|
|
||||||
pin?: string;
|
|
||||||
employeeNumber?: string;
|
|
||||||
hourlyRate?: number;
|
|
||||||
hireDate?: string;
|
|
||||||
dateOfBirth?: string;
|
|
||||||
address?: string;
|
|
||||||
city?: string;
|
|
||||||
state?: string;
|
|
||||||
zipCode?: string;
|
|
||||||
emergencyContact?: EmergencyContact;
|
|
||||||
active: boolean;
|
|
||||||
permissions?: EmployeePermissions;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EmergencyContact {
|
|
||||||
name: string;
|
|
||||||
relationship: string;
|
|
||||||
phone: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EmployeePermissions {
|
|
||||||
canVoidOrders?: boolean;
|
|
||||||
canRefund?: boolean;
|
|
||||||
canDiscountOrders?: boolean;
|
|
||||||
canAccessReports?: boolean;
|
|
||||||
canManageInventory?: boolean;
|
|
||||||
canManageEmployees?: boolean;
|
|
||||||
canManageMenu?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Shift {
|
|
||||||
id: string;
|
|
||||||
employeeId: string;
|
|
||||||
status: ShiftStatus;
|
|
||||||
scheduledStart: string;
|
|
||||||
scheduledEnd: string;
|
|
||||||
actualStart?: string;
|
|
||||||
actualEnd?: string;
|
|
||||||
breakMinutes?: number;
|
|
||||||
hoursWorked?: number;
|
|
||||||
totalSales?: number;
|
|
||||||
orderCount?: number;
|
|
||||||
notes?: string;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TimeClockEntry {
|
|
||||||
id: string;
|
|
||||||
employeeId: string;
|
|
||||||
shiftId?: string;
|
|
||||||
clockInTime: string;
|
|
||||||
clockOutTime?: string;
|
|
||||||
breakStart?: string;
|
|
||||||
breakEnd?: string;
|
|
||||||
totalHours?: number;
|
|
||||||
notes?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Table & Reservation Types
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
export interface Table {
|
|
||||||
id: string;
|
|
||||||
tableNumber: string;
|
|
||||||
name?: string;
|
|
||||||
capacity: number;
|
|
||||||
minCapacity?: number;
|
|
||||||
section?: string;
|
|
||||||
floorId?: string;
|
|
||||||
status: TableStatus;
|
|
||||||
shape?: 'round' | 'square' | 'rectangular';
|
|
||||||
x?: number; // floor plan position
|
|
||||||
y?: number;
|
|
||||||
currentOrderId?: string;
|
|
||||||
reservationId?: string;
|
|
||||||
assignedServerId?: string;
|
|
||||||
notes?: string;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Floor {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
sortOrder: number;
|
|
||||||
enabled: boolean;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Reservation {
|
|
||||||
id: string;
|
|
||||||
customerId?: string;
|
|
||||||
customerName: string;
|
|
||||||
customerPhone: string;
|
|
||||||
customerEmail?: string;
|
|
||||||
partySize: number;
|
|
||||||
date: string;
|
|
||||||
time: string;
|
|
||||||
duration?: number; // in minutes
|
|
||||||
status: ReservationStatus;
|
|
||||||
tableId?: string;
|
|
||||||
notes?: string;
|
|
||||||
specialOccasion?: string;
|
|
||||||
preferences?: string;
|
|
||||||
confirmedAt?: string;
|
|
||||||
seatedAt?: string;
|
|
||||||
completedAt?: string;
|
|
||||||
cancelledAt?: string;
|
|
||||||
noShowAt?: string;
|
|
||||||
reminderSent?: boolean;
|
|
||||||
createdBy?: string;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Loyalty & Gift Card Types
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
export interface LoyaltyProgram {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
pointsPerDollar: number;
|
|
||||||
dollarPerPoint: number;
|
|
||||||
enabled: boolean;
|
|
||||||
rules?: LoyaltyRule[];
|
|
||||||
tiers?: LoyaltyTier[];
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LoyaltyRule {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
type: 'birthday_bonus' | 'signup_bonus' | 'referral' | 'multiplier' | 'special_event';
|
|
||||||
points?: number;
|
|
||||||
multiplier?: number;
|
|
||||||
conditions?: Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LoyaltyTier {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
minPoints: number;
|
|
||||||
benefits?: string[];
|
|
||||||
discountPercent?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LoyaltyTransaction {
|
|
||||||
id: string;
|
|
||||||
customerId: string;
|
|
||||||
orderId?: string;
|
|
||||||
points: number;
|
|
||||||
type: 'earned' | 'redeemed' | 'expired' | 'adjusted';
|
|
||||||
description?: string;
|
|
||||||
balanceBefore: number;
|
|
||||||
balanceAfter: number;
|
|
||||||
expiresAt?: string;
|
|
||||||
createdAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GiftCard {
|
|
||||||
id: string;
|
|
||||||
cardNumber: string;
|
|
||||||
pin?: string;
|
|
||||||
balance: number;
|
|
||||||
initialBalance: number;
|
|
||||||
status: 'active' | 'inactive' | 'expired' | 'voided';
|
|
||||||
customerId?: string;
|
|
||||||
purchasedBy?: string;
|
|
||||||
recipientName?: string;
|
|
||||||
recipientEmail?: string;
|
|
||||||
message?: string;
|
|
||||||
expiresAt?: string;
|
|
||||||
activatedAt?: string;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GiftCardTransaction {
|
|
||||||
id: string;
|
|
||||||
giftCardId: string;
|
|
||||||
orderId?: string;
|
|
||||||
amount: number;
|
|
||||||
type: 'purchase' | 'redemption' | 'reload' | 'void' | 'adjustment';
|
|
||||||
balanceBefore: number;
|
|
||||||
balanceAfter: number;
|
|
||||||
processedBy?: string;
|
|
||||||
notes?: string;
|
|
||||||
createdAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Inventory Types
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
export interface InventoryItem {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
sku?: string;
|
|
||||||
category?: string;
|
|
||||||
unit: string;
|
|
||||||
currentStock: number;
|
|
||||||
parLevel?: number;
|
|
||||||
reorderPoint?: number;
|
|
||||||
reorderQuantity?: number;
|
|
||||||
cost: number;
|
|
||||||
supplier?: string;
|
|
||||||
supplierId?: string;
|
|
||||||
lastRestocked?: string;
|
|
||||||
expirationDate?: string;
|
|
||||||
location?: string;
|
|
||||||
notes?: string;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StockAdjustment {
|
|
||||||
id: string;
|
|
||||||
inventoryItemId: string;
|
|
||||||
quantity: number;
|
|
||||||
type: 'add' | 'remove' | 'count' | 'waste' | 'transfer';
|
|
||||||
reason?: string;
|
|
||||||
adjustedBy: string;
|
|
||||||
costImpact?: number;
|
|
||||||
notes?: string;
|
|
||||||
createdAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PurchaseOrder {
|
|
||||||
id: string;
|
|
||||||
orderNumber: string;
|
|
||||||
supplierId?: string;
|
|
||||||
supplierName: string;
|
|
||||||
status: 'draft' | 'sent' | 'confirmed' | 'received' | 'cancelled';
|
|
||||||
items: PurchaseOrderItem[];
|
|
||||||
subtotal: number;
|
|
||||||
tax?: number;
|
|
||||||
shipping?: number;
|
|
||||||
total: number;
|
|
||||||
expectedDelivery?: string;
|
|
||||||
receivedAt?: string;
|
|
||||||
notes?: string;
|
|
||||||
createdBy: string;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PurchaseOrderItem {
|
|
||||||
id: string;
|
|
||||||
inventoryItemId: string;
|
|
||||||
itemName: string;
|
|
||||||
quantity: number;
|
|
||||||
unitCost: number;
|
|
||||||
totalCost: number;
|
|
||||||
receivedQuantity?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Report Types
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
export interface SalesReport {
|
|
||||||
period: DateRange;
|
|
||||||
totalSales: number;
|
|
||||||
totalOrders: number;
|
|
||||||
averageOrderValue: number;
|
|
||||||
totalTax: number;
|
|
||||||
totalTips: number;
|
|
||||||
totalDiscounts: number;
|
|
||||||
grossRevenue: number;
|
|
||||||
netRevenue: number;
|
|
||||||
salesByHour?: HourlySales[];
|
|
||||||
salesByDay?: DailySales[];
|
|
||||||
salesByCategory?: CategorySales[];
|
|
||||||
salesByPaymentMethod?: PaymentMethodSales[];
|
|
||||||
topSellingItems?: ItemSales[];
|
|
||||||
guestCount?: number;
|
|
||||||
averageGuestSpend?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HourlySales {
|
|
||||||
hour: number;
|
|
||||||
sales: number;
|
|
||||||
orders: number;
|
|
||||||
averageOrderValue: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DailySales {
|
|
||||||
date: string;
|
|
||||||
sales: number;
|
|
||||||
orders: number;
|
|
||||||
averageOrderValue: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CategorySales {
|
|
||||||
categoryId: string;
|
|
||||||
categoryName: string;
|
|
||||||
sales: number;
|
|
||||||
quantity: number;
|
|
||||||
percentage: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PaymentMethodSales {
|
|
||||||
method: PaymentMethod;
|
|
||||||
amount: number;
|
|
||||||
count: number;
|
|
||||||
percentage: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ItemSales {
|
|
||||||
itemId: string;
|
|
||||||
itemName: string;
|
|
||||||
quantity: number;
|
|
||||||
sales: number;
|
|
||||||
profit?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EmployeePerformance {
|
|
||||||
employeeId: string;
|
|
||||||
employeeName: string;
|
|
||||||
totalSales: number;
|
|
||||||
orderCount: number;
|
|
||||||
averageOrderValue: number;
|
|
||||||
hoursWorked?: number;
|
|
||||||
salesPerHour?: number;
|
|
||||||
tipTotal?: number;
|
|
||||||
averageTip?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MenuPerformanceReport {
|
|
||||||
period: DateRange;
|
|
||||||
items: MenuItemPerformance[];
|
|
||||||
categories: CategoryPerformance[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MenuItemPerformance {
|
|
||||||
itemId: string;
|
|
||||||
itemName: string;
|
|
||||||
quantitySold: number;
|
|
||||||
revenue: number;
|
|
||||||
cost?: number;
|
|
||||||
profit?: number;
|
|
||||||
profitMargin?: number;
|
|
||||||
popularity?: number; // percentage
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CategoryPerformance {
|
|
||||||
categoryId: string;
|
|
||||||
categoryName: string;
|
|
||||||
itemCount: number;
|
|
||||||
totalQuantity: number;
|
|
||||||
revenue: number;
|
|
||||||
profit?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Discount & Promotion Types
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
export interface Discount {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
code?: string;
|
|
||||||
type: 'percentage' | 'fixed_amount' | 'buy_x_get_y' | 'free_item';
|
|
||||||
value: number;
|
|
||||||
minPurchase?: number;
|
|
||||||
maxDiscount?: number;
|
|
||||||
applicableItems?: string[];
|
|
||||||
applicableCategories?: string[];
|
|
||||||
startDate?: string;
|
|
||||||
endDate?: string;
|
|
||||||
usageLimit?: number;
|
|
||||||
usageCount?: number;
|
|
||||||
enabled: boolean;
|
|
||||||
autoApply?: boolean;
|
|
||||||
stackable?: boolean;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Promotion {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
type: 'happy_hour' | 'daily_special' | 'seasonal' | 'event';
|
|
||||||
discountId?: string;
|
|
||||||
daysOfWeek?: number[]; // 0-6, Sunday-Saturday
|
|
||||||
startTime?: string;
|
|
||||||
endTime?: string;
|
|
||||||
startDate?: string;
|
|
||||||
endDate?: string;
|
|
||||||
enabled: boolean;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Settings Types
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
export interface RestaurantSettings {
|
|
||||||
id: string;
|
|
||||||
restaurantId: string;
|
|
||||||
name: string;
|
|
||||||
address: string;
|
|
||||||
city: string;
|
|
||||||
state: string;
|
|
||||||
zipCode: string;
|
|
||||||
country: string;
|
|
||||||
phone: string;
|
|
||||||
email: string;
|
|
||||||
website?: string;
|
|
||||||
timezone: string;
|
|
||||||
currency: string;
|
|
||||||
taxRate: number;
|
|
||||||
autoGratuity?: number;
|
|
||||||
autoGratuityPartySize?: number;
|
|
||||||
tipSuggestions?: number[];
|
|
||||||
openingHours?: OperatingHours[];
|
|
||||||
onlineOrderingEnabled?: boolean;
|
|
||||||
reservationsEnabled?: boolean;
|
|
||||||
loyaltyEnabled?: boolean;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface OperatingHours {
|
|
||||||
dayOfWeek: number; // 0-6, Sunday-Saturday
|
|
||||||
openTime: string;
|
|
||||||
closeTime: string;
|
|
||||||
closed: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Webhook Types
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
export interface Webhook {
|
|
||||||
id: string;
|
|
||||||
url: string;
|
|
||||||
events: WebhookEvent[];
|
|
||||||
secret?: string;
|
|
||||||
enabled: boolean;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type WebhookEvent =
|
|
||||||
| 'order.created'
|
|
||||||
| 'order.updated'
|
|
||||||
| 'order.completed'
|
|
||||||
| 'order.cancelled'
|
|
||||||
| 'payment.processed'
|
|
||||||
| 'payment.refunded'
|
|
||||||
| 'reservation.created'
|
|
||||||
| 'reservation.updated'
|
|
||||||
| 'reservation.cancelled'
|
|
||||||
| 'customer.created'
|
|
||||||
| 'customer.updated'
|
|
||||||
| 'inventory.low_stock';
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Error Types
|
|
||||||
// ============================================================================
|
|
||||||
|
|
||||||
export interface TouchBistroError {
|
|
||||||
code: string;
|
|
||||||
message: string;
|
|
||||||
details?: any;
|
|
||||||
statusCode?: number;
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user