=== UPDATES === - fieldedge: Added apps, tools, main server entry, full rebuild - lightspeed: Added complete src/ directory with tools + apps - squarespace: Full rebuild — new apps, clients, tools, types modules - toast: Full rebuild — api-client, apps, tools, types - touchbistro: Full rebuild — api-client, tools, types, gitignore - servicetitan: Added 4 React UI apps (call-tracking, lead-source-analytics, performance-metrics, schedule-calendar) All servers restructured from single-file to modular architecture.
337 lines
11 KiB
TypeScript
337 lines
11 KiB
TypeScript
/**
|
|
* Lightspeed Inventory Tools
|
|
*/
|
|
|
|
import { z } from 'zod';
|
|
import type { LightspeedClient } from '../client.js';
|
|
import type { InventoryCount, InventoryTransfer, Supplier, PurchaseOrder } from '../types/index.js';
|
|
|
|
export function createInventoryTools(client: LightspeedClient) {
|
|
return {
|
|
lightspeed_list_inventory: {
|
|
description: 'List inventory counts for all products at a shop',
|
|
inputSchema: z.object({
|
|
shopId: z.string().describe('Shop ID'),
|
|
limit: z.number().optional().describe('Max items to return (default 100)'),
|
|
}),
|
|
handler: async (args: { shopId: string; limit?: number }) => {
|
|
try {
|
|
const inventory = await client.getAll<InventoryCount>(
|
|
`/Shop/${args.shopId}/ItemShop`,
|
|
'ItemShop',
|
|
args.limit || 100
|
|
);
|
|
return {
|
|
content: [
|
|
{
|
|
type: 'text' as const,
|
|
text: JSON.stringify({
|
|
success: true,
|
|
count: inventory.length,
|
|
inventory: inventory.map(i => ({
|
|
itemID: i.itemID,
|
|
qoh: i.qoh,
|
|
reorderPoint: i.reorderPoint,
|
|
backorder: i.backorder,
|
|
})),
|
|
}, null, 2),
|
|
},
|
|
],
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
content: [{ type: 'text' as const, text: `Error: ${(error as Error).message}` }],
|
|
isError: true,
|
|
};
|
|
}
|
|
},
|
|
},
|
|
|
|
lightspeed_get_item_inventory: {
|
|
description: 'Get inventory count for a specific product at a shop',
|
|
inputSchema: z.object({
|
|
itemId: z.string().describe('Product item ID'),
|
|
shopId: z.string().describe('Shop ID'),
|
|
}),
|
|
handler: async (args: { itemId: string; shopId: string }) => {
|
|
try {
|
|
const inventory = await client.get<{ ItemShop: InventoryCount }>(
|
|
`/Item/${args.itemId}/ItemShop/${args.shopId}`
|
|
);
|
|
return {
|
|
content: [
|
|
{
|
|
type: 'text' as const,
|
|
text: JSON.stringify({ success: true, inventory: inventory.ItemShop }, null, 2),
|
|
},
|
|
],
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
content: [{ type: 'text' as const, text: `Error: ${(error as Error).message}` }],
|
|
isError: true,
|
|
};
|
|
}
|
|
},
|
|
},
|
|
|
|
lightspeed_update_inventory_count: {
|
|
description: 'Update inventory quantity for a product at a shop',
|
|
inputSchema: z.object({
|
|
itemId: z.string().describe('Product item ID'),
|
|
shopId: z.string().describe('Shop ID'),
|
|
quantity: z.number().describe('New quantity on hand'),
|
|
reorderPoint: z.number().optional().describe('Reorder point threshold'),
|
|
reorderLevel: z.number().optional().describe('Reorder quantity'),
|
|
}),
|
|
handler: async (args: any) => {
|
|
try {
|
|
const updateData: any = {
|
|
qoh: args.quantity.toString(),
|
|
};
|
|
if (args.reorderPoint !== undefined) {
|
|
updateData.reorderPoint = args.reorderPoint.toString();
|
|
}
|
|
if (args.reorderLevel !== undefined) {
|
|
updateData.reorderLevel = args.reorderLevel.toString();
|
|
}
|
|
|
|
const result = await client.put<{ ItemShop: InventoryCount }>(
|
|
`/Item/${args.itemId}/ItemShop`,
|
|
args.shopId,
|
|
{ ItemShop: updateData }
|
|
);
|
|
return {
|
|
content: [
|
|
{
|
|
type: 'text' as const,
|
|
text: JSON.stringify({ success: true, inventory: result.ItemShop }, null, 2),
|
|
},
|
|
],
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
content: [{ type: 'text' as const, text: `Error: ${(error as Error).message}` }],
|
|
isError: true,
|
|
};
|
|
}
|
|
},
|
|
},
|
|
|
|
lightspeed_transfer_stock: {
|
|
description: 'Transfer inventory between shops',
|
|
inputSchema: z.object({
|
|
fromShopId: z.string().describe('Source shop ID'),
|
|
toShopId: z.string().describe('Destination shop ID'),
|
|
items: z.array(z.object({
|
|
itemId: z.string().describe('Product item ID'),
|
|
quantity: z.number().describe('Quantity to transfer'),
|
|
})).describe('Items to transfer'),
|
|
}),
|
|
handler: async (args: any) => {
|
|
try {
|
|
const transferData = {
|
|
fromShopID: args.fromShopId,
|
|
toShopID: args.toShopId,
|
|
TransferItems: {
|
|
TransferItem: args.items.map((item: any) => ({
|
|
itemID: item.itemId,
|
|
quantity: item.quantity.toString(),
|
|
})),
|
|
},
|
|
};
|
|
|
|
const result = await client.post<{ Transfer: InventoryTransfer }>(
|
|
'/Transfer',
|
|
{ Transfer: transferData }
|
|
);
|
|
return {
|
|
content: [
|
|
{
|
|
type: 'text' as const,
|
|
text: JSON.stringify({ success: true, transfer: result.Transfer }, null, 2),
|
|
},
|
|
],
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
content: [{ type: 'text' as const, text: `Error: ${(error as Error).message}` }],
|
|
isError: true,
|
|
};
|
|
}
|
|
},
|
|
},
|
|
|
|
lightspeed_list_inventory_adjustments: {
|
|
description: 'List inventory adjustments (stock changes)',
|
|
inputSchema: z.object({
|
|
itemId: z.string().optional().describe('Filter by product item ID'),
|
|
shopId: z.string().optional().describe('Filter by shop ID'),
|
|
limit: z.number().optional().describe('Max adjustments to return (default 100)'),
|
|
}),
|
|
handler: async (args: any) => {
|
|
try {
|
|
const params: any = {};
|
|
if (args.itemId) params.itemID = args.itemId;
|
|
if (args.shopId) params.shopID = args.shopId;
|
|
|
|
// Note: Lightspeed tracks adjustments through SaleLine with special types
|
|
const adjustments = await client.get<{ SaleLine: any[] }>('/SaleLine', params);
|
|
const results = Array.isArray(adjustments.SaleLine)
|
|
? adjustments.SaleLine
|
|
: adjustments.SaleLine ? [adjustments.SaleLine] : [];
|
|
|
|
return {
|
|
content: [
|
|
{
|
|
type: 'text' as const,
|
|
text: JSON.stringify({
|
|
success: true,
|
|
count: results.length,
|
|
adjustments: results.slice(0, args.limit || 100),
|
|
}, null, 2),
|
|
},
|
|
],
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
content: [{ type: 'text' as const, text: `Error: ${(error as Error).message}` }],
|
|
isError: true,
|
|
};
|
|
}
|
|
},
|
|
},
|
|
|
|
lightspeed_list_suppliers: {
|
|
description: 'List all suppliers/vendors',
|
|
inputSchema: z.object({
|
|
limit: z.number().optional().describe('Max suppliers to return (default 100)'),
|
|
archived: z.boolean().optional().describe('Include archived suppliers'),
|
|
}),
|
|
handler: async (args: { limit?: number; archived?: boolean }) => {
|
|
try {
|
|
const params: any = {};
|
|
if (args.archived !== undefined) {
|
|
params.archived = args.archived;
|
|
}
|
|
|
|
const suppliers = await client.getAll<Supplier>('/Vendor', 'Vendor', args.limit || 100);
|
|
return {
|
|
content: [
|
|
{
|
|
type: 'text' as const,
|
|
text: JSON.stringify({
|
|
success: true,
|
|
count: suppliers.length,
|
|
suppliers: suppliers.map(s => ({
|
|
vendorID: s.vendorID,
|
|
name: s.name,
|
|
accountNumber: s.accountNumber,
|
|
archived: s.archived,
|
|
})),
|
|
}, null, 2),
|
|
},
|
|
],
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
content: [{ type: 'text' as const, text: `Error: ${(error as Error).message}` }],
|
|
isError: true,
|
|
};
|
|
}
|
|
},
|
|
},
|
|
|
|
lightspeed_create_purchase_order: {
|
|
description: 'Create a purchase order for restocking inventory',
|
|
inputSchema: z.object({
|
|
vendorId: z.string().describe('Supplier/vendor ID'),
|
|
shopId: z.string().describe('Shop ID'),
|
|
items: z.array(z.object({
|
|
itemId: z.string().describe('Product item ID'),
|
|
quantity: z.number().describe('Quantity to order'),
|
|
unitCost: z.string().describe('Unit cost'),
|
|
})).describe('Items to order'),
|
|
}),
|
|
handler: async (args: any) => {
|
|
try {
|
|
const poData = {
|
|
vendorID: args.vendorId,
|
|
shopID: args.shopId,
|
|
PurchaseOrderLines: {
|
|
PurchaseOrderLine: args.items.map((item: any) => ({
|
|
itemID: item.itemId,
|
|
quantity: item.quantity.toString(),
|
|
unitCost: item.unitCost,
|
|
})),
|
|
},
|
|
};
|
|
|
|
const result = await client.post<{ PurchaseOrder: PurchaseOrder }>(
|
|
'/PurchaseOrder',
|
|
{ PurchaseOrder: poData }
|
|
);
|
|
return {
|
|
content: [
|
|
{
|
|
type: 'text' as const,
|
|
text: JSON.stringify({ success: true, purchaseOrder: result.PurchaseOrder }, null, 2),
|
|
},
|
|
],
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
content: [{ type: 'text' as const, text: `Error: ${(error as Error).message}` }],
|
|
isError: true,
|
|
};
|
|
}
|
|
},
|
|
},
|
|
|
|
lightspeed_list_purchase_orders: {
|
|
description: 'List purchase orders',
|
|
inputSchema: z.object({
|
|
vendorId: z.string().optional().describe('Filter by vendor ID'),
|
|
status: z.string().optional().describe('Filter by status (e.g., open, complete)'),
|
|
limit: z.number().optional().describe('Max POs to return (default 100)'),
|
|
}),
|
|
handler: async (args: any) => {
|
|
try {
|
|
const params: any = {};
|
|
if (args.vendorId) params.vendorID = args.vendorId;
|
|
if (args.status) params.status = args.status;
|
|
|
|
const pos = await client.getAll<PurchaseOrder>(
|
|
'/PurchaseOrder',
|
|
'PurchaseOrder',
|
|
args.limit || 100
|
|
);
|
|
return {
|
|
content: [
|
|
{
|
|
type: 'text' as const,
|
|
text: JSON.stringify({
|
|
success: true,
|
|
count: pos.length,
|
|
purchaseOrders: pos.map(po => ({
|
|
purchaseOrderID: po.purchaseOrderID,
|
|
vendorID: po.vendorID,
|
|
orderNumber: po.orderNumber,
|
|
status: po.status,
|
|
createTime: po.createTime,
|
|
})),
|
|
}, null, 2),
|
|
},
|
|
],
|
|
};
|
|
} catch (error) {
|
|
return {
|
|
content: [{ type: 'text' as const, text: `Error: ${(error as Error).message}` }],
|
|
isError: true,
|
|
};
|
|
}
|
|
},
|
|
},
|
|
};
|
|
}
|