import { z } from 'zod'; import type { FreshBooksClient } from '../clients/freshbooks.js'; import type { Invoice, Payment } from '../types/index.js'; export const invoicesTools = [ { name: 'freshbooks_list_invoices', description: 'List all invoices with optional filtering (client, status, date range)', inputSchema: z.object({ clientid: z.number().optional().describe('Filter by client ID'), status: z.enum(['draft', 'sent', 'viewed', 'paid', 'partial', 'overdue', 'disputed']).optional(), date_min: z.string().optional().describe('Minimum date (YYYY-MM-DD)'), date_max: z.string().optional().describe('Maximum date (YYYY-MM-DD)'), page: z.number().default(1), per_page: z.number().default(30), }), handler: async (args: any, client: FreshBooksClient) => { const params: Record = {}; if (args.clientid) params.clientid = args.clientid; if (args.status) params.status = args.status; if (args.date_min) params.date_min = args.date_min; if (args.date_max) params.date_max = args.date_max; const response = await client.getPaginated<{ invoices: Invoice[] }>( '/invoices/invoices', args.page, args.per_page, params ); return { invoices: response.response.result.invoices || [], page: response.response.page, pages: response.response.pages, total: response.response.total, }; }, }, { name: 'freshbooks_get_invoice', description: 'Get a single invoice by ID', inputSchema: z.object({ invoice_id: z.number().describe('Invoice ID'), }), handler: async (args: any, client: FreshBooksClient) => { const response = await client.get<{ response: { result: { invoice: Invoice } } }>( `/invoices/invoices/${args.invoice_id}` ); return response.response.result.invoice; }, }, { name: 'freshbooks_create_invoice', description: 'Create a new invoice', inputSchema: z.object({ clientid: z.number().describe('Client ID'), create_date: z.string().optional().describe('Invoice date (YYYY-MM-DD, defaults to today)'), due_date: z.string().optional().describe('Due date (YYYY-MM-DD)'), lines: z.array(z.object({ name: z.string().describe('Line item name'), description: z.string().optional(), qty: z.number().default(1), unit_cost: z.string().describe('Unit cost as string (e.g., "100.00")'), })).describe('Invoice line items'), currency_code: z.string().default('USD'), notes: z.string().optional(), terms: z.string().optional(), status: z.enum(['draft', 'sent']).default('draft'), }), handler: async (args: any, client: FreshBooksClient) => { const lines = args.lines.map((line: any) => ({ ...line, unit_cost: { amount: line.unit_cost, code: args.currency_code }, })); const invoiceData = { invoice: { clientid: args.clientid, create_date: args.create_date || new Date().toISOString().split('T')[0], due_date: args.due_date, currency_code: args.currency_code, lines, notes: args.notes, terms: args.terms, status: args.status === 'sent' ? 2 : 1, }, }; const response = await client.post<{ response: { result: { invoice: Invoice } } }>( '/invoices/invoices', invoiceData ); return response.response.result.invoice; }, }, { name: 'freshbooks_update_invoice', description: 'Update an existing invoice', inputSchema: z.object({ invoice_id: z.number().describe('Invoice ID'), clientid: z.number().optional(), create_date: z.string().optional(), due_date: z.string().optional(), lines: z.array(z.object({ name: z.string(), description: z.string().optional(), qty: z.number(), unit_cost: z.string(), })).optional(), notes: z.string().optional(), terms: z.string().optional(), }), handler: async (args: any, client: FreshBooksClient) => { const updateData: any = { invoice: {} }; if (args.clientid) updateData.invoice.clientid = args.clientid; if (args.create_date) updateData.invoice.create_date = args.create_date; if (args.due_date) updateData.invoice.due_date = args.due_date; if (args.notes) updateData.invoice.notes = args.notes; if (args.terms) updateData.invoice.terms = args.terms; if (args.lines) { updateData.invoice.lines = args.lines.map((line: any) => ({ ...line, unit_cost: { amount: line.unit_cost, code: 'USD' }, })); } const response = await client.put<{ response: { result: { invoice: Invoice } } }>( `/invoices/invoices/${args.invoice_id}`, updateData ); return response.response.result.invoice; }, }, { name: 'freshbooks_delete_invoice', description: 'Delete an invoice (moves to archived)', inputSchema: z.object({ invoice_id: z.number().describe('Invoice ID'), }), handler: async (args: any, client: FreshBooksClient) => { await client.put( `/invoices/invoices/${args.invoice_id}`, { invoice: { vis_state: 1 } } ); return { success: true, message: `Invoice ${args.invoice_id} archived` }; }, }, { name: 'freshbooks_send_invoice', description: 'Send an invoice to the client via email', inputSchema: z.object({ invoice_id: z.number().describe('Invoice ID'), email_subject: z.string().optional(), email_body: z.string().optional(), }), handler: async (args: any, client: FreshBooksClient) => { const emailData: any = { invoice: {} }; if (args.email_subject) emailData.invoice.email_subject = args.email_subject; if (args.email_body) emailData.invoice.email_body = args.email_body; await client.put( `/invoices/invoices/${args.invoice_id}`, { invoice: { action_email: true, ...emailData.invoice } } ); return { success: true, message: `Invoice ${args.invoice_id} sent` }; }, }, { name: 'freshbooks_mark_invoice_paid', description: 'Mark an invoice as paid', inputSchema: z.object({ invoice_id: z.number().describe('Invoice ID'), payment_type: z.string().default('Cash').describe('Payment method'), payment_date: z.string().optional().describe('Payment date (YYYY-MM-DD, defaults to today)'), amount: z.string().optional().describe('Payment amount (defaults to outstanding amount)'), }), handler: async (args: any, client: FreshBooksClient) => { // First get the invoice to know the outstanding amount const invoiceResp = await client.get<{ response: { result: { invoice: Invoice } } }>( `/invoices/invoices/${args.invoice_id}` ); const invoice = invoiceResp.response.result.invoice; const paymentData = { payment: { invoiceid: args.invoice_id, amount: { amount: args.amount || invoice.outstanding.amount, code: invoice.currency_code, }, date: args.payment_date || new Date().toISOString().split('T')[0], type: args.payment_type, }, }; const response = await client.post<{ response: { result: { payment: Payment } } }>( '/payments/payments', paymentData ); return response.response.result.payment; }, }, { name: 'freshbooks_mark_invoice_unpaid', description: 'Mark an invoice as unpaid (reopen it)', inputSchema: z.object({ invoice_id: z.number().describe('Invoice ID'), }), handler: async (args: any, client: FreshBooksClient) => { await client.put( `/invoices/invoices/${args.invoice_id}`, { invoice: { v3_status: 'unpaid' } } ); return { success: true, message: `Invoice ${args.invoice_id} marked unpaid` }; }, }, { name: 'freshbooks_get_invoice_payment', description: 'Get payment details for an invoice', inputSchema: z.object({ invoice_id: z.number().describe('Invoice ID'), }), handler: async (args: any, client: FreshBooksClient) => { const response = await client.get<{ response: { result: { payments: Payment[] } } }>( '/payments/payments', { invoiceid: args.invoice_id } ); return response.response.result.payments || []; }, }, { name: 'freshbooks_create_payment', description: 'Create a payment record for an invoice', inputSchema: z.object({ invoice_id: z.number().describe('Invoice ID'), amount: z.string().describe('Payment amount'), date: z.string().optional().describe('Payment date (YYYY-MM-DD)'), type: z.string().default('Cash').describe('Payment method'), note: z.string().optional(), }), handler: async (args: any, client: FreshBooksClient) => { const paymentData = { payment: { invoiceid: args.invoice_id, amount: { amount: args.amount, code: 'USD' }, date: args.date || new Date().toISOString().split('T')[0], type: args.type, note: args.note, }, }; const response = await client.post<{ response: { result: { payment: Payment } } }>( '/payments/payments', paymentData ); return response.response.result.payment; }, }, ];