Add wave-mcp/src/tools/invoices.ts
This commit is contained in:
parent
6913c4e4d9
commit
35392cdee4
1 changed files with 458 additions and 0 deletions
458
wave-mcp/src/tools/invoices.ts
Normal file
458
wave-mcp/src/tools/invoices.ts
Normal file
|
|
@ -0,0 +1,458 @@
|
||||||
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { WaveClient } from "../services/wave-client.js";
|
||||||
|
import { WaveInvoice } from "../types.js";
|
||||||
|
import { DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE } from "../constants.js";
|
||||||
|
|
||||||
|
interface InvoicesData {
|
||||||
|
business: {
|
||||||
|
invoices: {
|
||||||
|
pageInfo: { currentPage: number; totalPages: number; totalCount: number };
|
||||||
|
edges: Array<{ node: WaveInvoice }>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InvoiceData {
|
||||||
|
business: { invoice: WaveInvoice };
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InvoiceCreateData {
|
||||||
|
invoiceCreate: {
|
||||||
|
didSucceed: boolean;
|
||||||
|
inputErrors: Array<{ code: string; message: string; path: string[] }>;
|
||||||
|
invoice?: WaveInvoice;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InvoicePatchData {
|
||||||
|
invoicePatch: {
|
||||||
|
didSucceed: boolean;
|
||||||
|
inputErrors: Array<{ code: string; message: string; path: string[] }>;
|
||||||
|
invoice?: WaveInvoice;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InvoiceActionData {
|
||||||
|
invoiceApprove?: { didSucceed: boolean; inputErrors: Array<{ message: string }> };
|
||||||
|
invoiceSend?: { didSucceed: boolean; inputErrors: Array<{ message: string }>; invoice?: { id: string } };
|
||||||
|
invoiceMarkSent?: { didSucceed: boolean; inputErrors: Array<{ message: string }> };
|
||||||
|
invoiceDelete?: { didSucceed: boolean; inputErrors: Array<{ message: string }> };
|
||||||
|
}
|
||||||
|
|
||||||
|
const INVOICE_FIELDS = `
|
||||||
|
id pdfUrl viewUrl status title subhead invoiceNumber
|
||||||
|
invoiceDate dueDate memo
|
||||||
|
customer { id name email }
|
||||||
|
currency { code }
|
||||||
|
amountDue { value currency { code } }
|
||||||
|
amountPaid { value currency { code } }
|
||||||
|
taxTotal { value currency { code } }
|
||||||
|
subtotal { value currency { code } }
|
||||||
|
total { value currency { code } }
|
||||||
|
items {
|
||||||
|
product { id name }
|
||||||
|
description quantity unitPrice subtotal total
|
||||||
|
account { id name }
|
||||||
|
}
|
||||||
|
createdAt modifiedAt
|
||||||
|
`;
|
||||||
|
|
||||||
|
function formatInvoiceMarkdown(inv: WaveInvoice): string[] {
|
||||||
|
const lines = [
|
||||||
|
`## Invoice #${inv.invoiceNumber} — ${inv.customer?.name ?? "Unknown"} (${inv.id})`,
|
||||||
|
`- **Status**: ${inv.status}`,
|
||||||
|
`- **Date**: ${inv.invoiceDate ?? "N/A"}`,
|
||||||
|
`- **Due**: ${inv.dueDate ?? "N/A"}`,
|
||||||
|
`- **Total**: ${WaveClient.formatCurrency(inv.total?.value ?? 0, inv.currency?.code ?? "")}`,
|
||||||
|
`- **Amount Due**: ${WaveClient.formatCurrency(inv.amountDue?.value ?? 0, inv.currency?.code ?? "")}`,
|
||||||
|
`- **Amount Paid**: ${WaveClient.formatCurrency(inv.amountPaid?.value ?? 0, inv.currency?.code ?? "")}`,
|
||||||
|
];
|
||||||
|
if (inv.pdfUrl) lines.push(`- **PDF**: ${inv.pdfUrl}`);
|
||||||
|
if (inv.viewUrl) lines.push(`- **View**: ${inv.viewUrl}`);
|
||||||
|
lines.push("");
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerInvoiceTools(server: McpServer, client: WaveClient): void {
|
||||||
|
// ── wave_list_invoices ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
server.registerTool(
|
||||||
|
"wave_list_invoices",
|
||||||
|
{
|
||||||
|
title: "List Wave Invoices",
|
||||||
|
description: `List invoices for a Wave business with optional filtering by status or customer.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- businessId (string): Wave business ID
|
||||||
|
- page (number): Page number (default: 1)
|
||||||
|
- pageSize (number): Items per page, 1-100 (default: 20)
|
||||||
|
- customerId (string): Filter by customer ID (optional)
|
||||||
|
- invoiceNumber (string): Filter by invoice number (optional)
|
||||||
|
- status ('DRAFT' | 'APPROVED' | 'SENT' | 'VIEWED' | 'PARTIALLY_PAID' | 'PAID' | 'OVERDUE'): Filter by status (optional)
|
||||||
|
- response_format ('markdown' | 'json'): Output format (default: 'markdown')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Paginated list of invoices with totals, status, and customer info.`,
|
||||||
|
inputSchema: z.object({
|
||||||
|
businessId: z.string().describe("Wave business ID"),
|
||||||
|
page: z.number().int().min(1).default(1).describe("Page number"),
|
||||||
|
pageSize: z.number().int().min(1).max(MAX_PAGE_SIZE).default(DEFAULT_PAGE_SIZE).describe("Items per page"),
|
||||||
|
customerId: z.string().optional().describe("Filter by customer ID"),
|
||||||
|
invoiceNumber: z.string().optional().describe("Filter by invoice number"),
|
||||||
|
status: z.enum(["DRAFT", "APPROVED", "SENT", "VIEWED", "PARTIALLY_PAID", "PAID", "OVERDUE"]).optional().describe("Filter by status"),
|
||||||
|
response_format: z.enum(["markdown", "json"]).default("markdown").describe("Output format"),
|
||||||
|
}),
|
||||||
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
||||||
|
},
|
||||||
|
async ({ businessId, page, pageSize, customerId, invoiceNumber, status, response_format }) => {
|
||||||
|
try {
|
||||||
|
// Build optional filter variables
|
||||||
|
const vars: Record<string, unknown> = { businessId, page, pageSize };
|
||||||
|
if (customerId) vars.customerId = customerId;
|
||||||
|
if (invoiceNumber) vars.invoiceNumber = invoiceNumber;
|
||||||
|
if (status) vars.status = status;
|
||||||
|
|
||||||
|
const data = await client.query<InvoicesData>(`
|
||||||
|
query($businessId: ID!, $page: Int!, $pageSize: Int!) {
|
||||||
|
business(id: $businessId) {
|
||||||
|
invoices(page: $page, pageSize: $pageSize) {
|
||||||
|
pageInfo { currentPage totalPages totalCount }
|
||||||
|
edges { node { ${INVOICE_FIELDS} } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, vars);
|
||||||
|
|
||||||
|
let { pageInfo, edges } = data.business.invoices;
|
||||||
|
let invoices = edges.map((e) => e.node);
|
||||||
|
|
||||||
|
// Client-side filter since Wave API may not support all filter params at query level
|
||||||
|
if (customerId) invoices = invoices.filter((i) => i.customer?.id === customerId);
|
||||||
|
if (status) invoices = invoices.filter((i) => i.status === status);
|
||||||
|
if (invoiceNumber) invoices = invoices.filter((i) => i.invoiceNumber?.includes(invoiceNumber));
|
||||||
|
|
||||||
|
if (!invoices.length) {
|
||||||
|
return { content: [{ type: "text", text: "No invoices found matching the criteria." }] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
total: pageInfo.totalCount,
|
||||||
|
count: invoices.length,
|
||||||
|
page: pageInfo.currentPage,
|
||||||
|
totalPages: pageInfo.totalPages,
|
||||||
|
has_more: pageInfo.currentPage < pageInfo.totalPages,
|
||||||
|
next_page: pageInfo.currentPage < pageInfo.totalPages ? pageInfo.currentPage + 1 : undefined,
|
||||||
|
invoices,
|
||||||
|
};
|
||||||
|
|
||||||
|
let text: string;
|
||||||
|
if (response_format === "json") {
|
||||||
|
text = JSON.stringify(result, null, 2);
|
||||||
|
} else {
|
||||||
|
const lines = [`# Invoices (${pageInfo.totalCount} total, page ${pageInfo.currentPage}/${pageInfo.totalPages})`, ""];
|
||||||
|
for (const inv of invoices) lines.push(...formatInvoiceMarkdown(inv));
|
||||||
|
text = lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: WaveClient.truncate(text) }],
|
||||||
|
structuredContent: result,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── wave_get_invoice ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
server.registerTool(
|
||||||
|
"wave_get_invoice",
|
||||||
|
{
|
||||||
|
title: "Get Wave Invoice",
|
||||||
|
description: `Get full details for a specific invoice including line items.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- businessId (string): Wave business ID
|
||||||
|
- invoiceId (string): Invoice ID
|
||||||
|
- response_format ('markdown' | 'json'): Output format (default: 'markdown')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Full invoice with all line items, totals, dates, and PDF/view links.`,
|
||||||
|
inputSchema: z.object({
|
||||||
|
businessId: z.string().describe("Wave business ID"),
|
||||||
|
invoiceId: z.string().describe("Invoice ID"),
|
||||||
|
response_format: z.enum(["markdown", "json"]).default("markdown").describe("Output format"),
|
||||||
|
}),
|
||||||
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
||||||
|
},
|
||||||
|
async ({ businessId, invoiceId, response_format }) => {
|
||||||
|
try {
|
||||||
|
const data = await client.query<InvoiceData>(`
|
||||||
|
query($businessId: ID!, $invoiceId: ID!) {
|
||||||
|
business(id: $businessId) {
|
||||||
|
invoice(id: $invoiceId) { ${INVOICE_FIELDS} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, { businessId, invoiceId });
|
||||||
|
|
||||||
|
const inv = data.business.invoice;
|
||||||
|
if (!inv) return { content: [{ type: "text", text: `No invoice found with ID: ${invoiceId}` }] };
|
||||||
|
|
||||||
|
let text: string;
|
||||||
|
if (response_format === "json") {
|
||||||
|
text = JSON.stringify(inv, null, 2);
|
||||||
|
} else {
|
||||||
|
const lines = [...formatInvoiceMarkdown(inv)];
|
||||||
|
if (inv.items?.length) {
|
||||||
|
lines.push("### Line Items", "");
|
||||||
|
for (const item of inv.items) {
|
||||||
|
const desc = item.description ?? item.product?.name ?? "Item";
|
||||||
|
lines.push(`- ${desc}: ${item.quantity} × ${WaveClient.formatCurrency(item.unitPrice, inv.currency?.code ?? "")} = ${WaveClient.formatCurrency(item.total, inv.currency?.code ?? "")}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text = lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return { content: [{ type: "text", text }], structuredContent: inv };
|
||||||
|
} catch (error) {
|
||||||
|
return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── wave_create_invoice ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
server.registerTool(
|
||||||
|
"wave_create_invoice",
|
||||||
|
{
|
||||||
|
title: "Create Wave Invoice",
|
||||||
|
description: `Create a new draft invoice in Wave.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- businessId (string): Wave business ID
|
||||||
|
- customerId (string): Customer ID for the invoice
|
||||||
|
- invoiceDate (string): Invoice date in YYYY-MM-DD format (optional, defaults to today)
|
||||||
|
- dueDate (string): Due date in YYYY-MM-DD format (optional)
|
||||||
|
- invoiceNumber (string): Custom invoice number (optional, auto-assigned if omitted)
|
||||||
|
- memo (string): Note to appear on invoice (optional)
|
||||||
|
- items (array): Line items (required, at least 1)
|
||||||
|
- productId (string): Product ID (optional)
|
||||||
|
- description (string): Item description
|
||||||
|
- quantity (number): Quantity
|
||||||
|
- unitPrice (number): Unit price
|
||||||
|
- accountId (string): Revenue account ID (optional)
|
||||||
|
- response_format ('markdown' | 'json'): Output format (default: 'markdown')
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Created invoice details or validation errors.`,
|
||||||
|
inputSchema: z.object({
|
||||||
|
businessId: z.string().describe("Wave business ID"),
|
||||||
|
customerId: z.string().describe("Customer ID"),
|
||||||
|
invoiceDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("Invoice date (YYYY-MM-DD)"),
|
||||||
|
dueDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().describe("Due date (YYYY-MM-DD)"),
|
||||||
|
invoiceNumber: z.string().optional().describe("Custom invoice number"),
|
||||||
|
memo: z.string().optional().describe("Memo/note on invoice"),
|
||||||
|
items: z.array(z.object({
|
||||||
|
productId: z.string().optional().describe("Product ID"),
|
||||||
|
description: z.string().optional().describe("Line item description"),
|
||||||
|
quantity: z.number().min(0.001).describe("Quantity"),
|
||||||
|
unitPrice: z.number().describe("Unit price"),
|
||||||
|
accountId: z.string().optional().describe("Revenue account ID"),
|
||||||
|
})).min(1).describe("Line items (at least 1 required)"),
|
||||||
|
response_format: z.enum(["markdown", "json"]).default("markdown").describe("Output format"),
|
||||||
|
}),
|
||||||
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
||||||
|
},
|
||||||
|
async ({ businessId, customerId, invoiceDate, dueDate, invoiceNumber, memo, items, response_format }) => {
|
||||||
|
try {
|
||||||
|
const input: Record<string, unknown> = {
|
||||||
|
businessId,
|
||||||
|
customerId,
|
||||||
|
items: items.map((item) => {
|
||||||
|
const li: Record<string, unknown> = {
|
||||||
|
quantity: item.quantity,
|
||||||
|
unitPrice: item.unitPrice,
|
||||||
|
};
|
||||||
|
if (item.productId) li.productId = item.productId;
|
||||||
|
if (item.description) li.description = item.description;
|
||||||
|
if (item.accountId) li.accountId = item.accountId;
|
||||||
|
return li;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
if (invoiceDate) input.invoiceDate = invoiceDate;
|
||||||
|
if (dueDate) input.dueDate = dueDate;
|
||||||
|
if (invoiceNumber) input.invoiceNumber = invoiceNumber;
|
||||||
|
if (memo) input.memo = memo;
|
||||||
|
|
||||||
|
const data = await client.query<InvoiceCreateData>(`
|
||||||
|
mutation($input: InvoiceCreateInput!) {
|
||||||
|
invoiceCreate(input: $input) {
|
||||||
|
didSucceed
|
||||||
|
inputErrors { code message path }
|
||||||
|
invoice { ${INVOICE_FIELDS} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, { input });
|
||||||
|
|
||||||
|
const result = data.invoiceCreate;
|
||||||
|
if (!result.didSucceed) {
|
||||||
|
const errs = result.inputErrors.map((e) => ` - ${e.path?.join(".") ?? "field"}: ${e.message}`).join("\n");
|
||||||
|
return { content: [{ type: "text", text: `Failed to create invoice:\n${errs}` }] };
|
||||||
|
}
|
||||||
|
|
||||||
|
let text: string;
|
||||||
|
if (response_format === "json") {
|
||||||
|
text = JSON.stringify(result.invoice, null, 2);
|
||||||
|
} else {
|
||||||
|
text = `Invoice created successfully!\n\n${formatInvoiceMarkdown(result.invoice!).join("\n")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { content: [{ type: "text", text }], structuredContent: result.invoice };
|
||||||
|
} catch (error) {
|
||||||
|
return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── wave_approve_invoice ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
server.registerTool(
|
||||||
|
"wave_approve_invoice",
|
||||||
|
{
|
||||||
|
title: "Approve Wave Invoice",
|
||||||
|
description: `Approve a draft invoice, moving it to APPROVED status so it can be sent.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- invoiceId (string): Invoice ID to approve
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Success confirmation or error details.`,
|
||||||
|
inputSchema: z.object({
|
||||||
|
invoiceId: z.string().describe("Invoice ID to approve"),
|
||||||
|
}),
|
||||||
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
||||||
|
},
|
||||||
|
async ({ invoiceId }) => {
|
||||||
|
try {
|
||||||
|
const data = await client.query<InvoiceActionData>(`
|
||||||
|
mutation($input: InvoiceApproveInput!) {
|
||||||
|
invoiceApprove(input: $input) {
|
||||||
|
didSucceed
|
||||||
|
inputErrors { message }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, { input: { invoiceId } });
|
||||||
|
|
||||||
|
const result = data.invoiceApprove!;
|
||||||
|
if (!result.didSucceed) {
|
||||||
|
const errs = result.inputErrors.map((e) => ` - ${e.message}`).join("\n");
|
||||||
|
return { content: [{ type: "text", text: `Failed to approve invoice:\n${errs}` }] };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { content: [{ type: "text", text: `Invoice ${invoiceId} approved successfully.` }] };
|
||||||
|
} catch (error) {
|
||||||
|
return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── wave_send_invoice ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
server.registerTool(
|
||||||
|
"wave_send_invoice",
|
||||||
|
{
|
||||||
|
title: "Send Wave Invoice",
|
||||||
|
description: `Send an approved invoice to the customer via email.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- invoiceId (string): Invoice ID to send
|
||||||
|
- to (array of strings): Recipient email addresses
|
||||||
|
- subject (string): Email subject (optional)
|
||||||
|
- message (string): Email message body (optional)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Success confirmation or error details.`,
|
||||||
|
inputSchema: z.object({
|
||||||
|
invoiceId: z.string().describe("Invoice ID to send"),
|
||||||
|
to: z.array(z.string().email()).min(1).describe("Recipient email addresses"),
|
||||||
|
subject: z.string().optional().describe("Email subject line"),
|
||||||
|
message: z.string().optional().describe("Email message body"),
|
||||||
|
}),
|
||||||
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
||||||
|
},
|
||||||
|
async ({ invoiceId, to, subject, message }) => {
|
||||||
|
try {
|
||||||
|
const input: Record<string, unknown> = { invoiceId, to };
|
||||||
|
if (subject) input.subject = subject;
|
||||||
|
if (message) input.message = message;
|
||||||
|
|
||||||
|
const data = await client.query<InvoiceActionData>(`
|
||||||
|
mutation($input: InvoiceSendInput!) {
|
||||||
|
invoiceSend(input: $input) {
|
||||||
|
didSucceed
|
||||||
|
inputErrors { message }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, { input });
|
||||||
|
|
||||||
|
const result = data.invoiceSend!;
|
||||||
|
if (!result.didSucceed) {
|
||||||
|
const errs = result.inputErrors.map((e) => ` - ${e.message}`).join("\n");
|
||||||
|
return { content: [{ type: "text", text: `Failed to send invoice:\n${errs}` }] };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { content: [{ type: "text", text: `Invoice ${invoiceId} sent to ${to.join(", ")}.` }] };
|
||||||
|
} catch (error) {
|
||||||
|
return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── wave_delete_invoice ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
server.registerTool(
|
||||||
|
"wave_delete_invoice",
|
||||||
|
{
|
||||||
|
title: "Delete Wave Invoice",
|
||||||
|
description: `Delete an invoice. Only DRAFT invoices can be deleted; paid invoices cannot be deleted.
|
||||||
|
|
||||||
|
⚠️ This action is irreversible.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
- businessId (string): Wave business ID
|
||||||
|
- invoiceId (string): Invoice ID to delete
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Success confirmation or error details.`,
|
||||||
|
inputSchema: z.object({
|
||||||
|
businessId: z.string().describe("Wave business ID"),
|
||||||
|
invoiceId: z.string().describe("Invoice ID to delete"),
|
||||||
|
}),
|
||||||
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
||||||
|
},
|
||||||
|
async ({ businessId, invoiceId }) => {
|
||||||
|
try {
|
||||||
|
const data = await client.query<InvoiceActionData>(`
|
||||||
|
mutation($input: InvoiceDeleteInput!) {
|
||||||
|
invoiceDelete(input: $input) {
|
||||||
|
didSucceed
|
||||||
|
inputErrors { message }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, { input: { businessId, invoiceId } });
|
||||||
|
|
||||||
|
const result = data.invoiceDelete!;
|
||||||
|
if (!result.didSucceed) {
|
||||||
|
const errs = result.inputErrors.map((e) => ` - ${e.message}`).join("\n");
|
||||||
|
return { content: [{ type: "text", text: `Failed to delete invoice:\n${errs}` }] };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { content: [{ type: "text", text: `Invoice ${invoiceId} deleted successfully.` }] };
|
||||||
|
} catch (error) {
|
||||||
|
return { content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue