/** * Xero Credit Note Tools * Handles credit notes (refunds/credits) for both AR and AP */ import { z } from 'zod'; import { XeroClient } from '../clients/xero.js'; import type { Tool } from '@modelcontextprotocol/sdk/types.js'; const LineItemSchema = z.object({ Description: z.string().optional(), Quantity: z.number().optional(), UnitAmount: z.number().optional(), ItemCode: z.string().optional(), AccountCode: z.string().optional(), TaxType: z.string().optional(), LineAmount: z.number().optional() }); const ContactSchema = z.object({ ContactID: z.string().optional(), Name: z.string().optional() }); export function getTools(_client: XeroClient): Tool[] { return [ // List credit notes { name: 'xero_list_credit_notes', description: 'List all credit notes. Use where clause to filter by Type (ACCRECCREDIT for customer credits, ACCPAYCREDIT for supplier credits).', inputSchema: { type: 'object', properties: { page: { type: 'number', description: 'Page number (default 1)' }, pageSize: { type: 'number', description: 'Page size (max 100)' }, where: { type: 'string', description: 'Filter expression (e.g., Type=="ACCRECCREDIT")' }, order: { type: 'string', description: 'Order by field (e.g., Date DESC)' }, ifModifiedSince: { type: 'string', description: 'ISO date to get only records modified since' } } } }, // Get single credit note { name: 'xero_get_credit_note', description: 'Get a specific credit note by ID. Returns full details including line items and allocations.', inputSchema: { type: 'object', properties: { creditNoteId: { type: 'string', description: 'Credit note ID (GUID)' } }, required: ['creditNoteId'] } }, // Create credit note { name: 'xero_create_credit_note', description: 'Create a new credit note. Type can be ACCRECCREDIT (customer credit) or ACCPAYCREDIT (supplier credit).', inputSchema: { type: 'object', properties: { type: { type: 'string', enum: ['ACCRECCREDIT', 'ACCPAYCREDIT'], description: 'ACCRECCREDIT for customer credit, ACCPAYCREDIT for supplier credit' }, contact: { type: 'object', properties: { ContactID: { type: 'string', description: 'Contact ID (GUID)' }, Name: { type: 'string', description: 'Contact name' } }, description: 'Contact' }, lineItems: { type: 'array', items: { type: 'object', properties: { Description: { type: 'string' }, Quantity: { type: 'number' }, UnitAmount: { type: 'number' }, AccountCode: { type: 'string' }, TaxType: { type: 'string' } } }, description: 'Line items (at least one required)' }, date: { type: 'string', description: 'Credit note date (YYYY-MM-DD)' }, reference: { type: 'string', description: 'Reference text' }, creditNoteNumber: { type: 'string', description: 'Credit note number (optional)' }, status: { type: 'string', enum: ['DRAFT', 'AUTHORISED'], description: 'Status (default: DRAFT)' }, lineAmountTypes: { type: 'string', enum: ['Exclusive', 'Inclusive', 'NoTax'], description: 'How line amounts are calculated (default: Exclusive)' }, currencyCode: { type: 'string', description: 'Currency code (e.g., USD)' } }, required: ['type', 'contact', 'lineItems'] } }, // Update credit note { name: 'xero_update_credit_note', description: 'Update an existing credit note. Can update status, reference, and line items.', inputSchema: { type: 'object', properties: { creditNoteId: { type: 'string', description: 'Credit note ID (GUID)' }, status: { type: 'string', enum: ['DRAFT', 'AUTHORISED', 'SUBMITTED'], description: 'New status' }, reference: { type: 'string', description: 'Reference text' }, lineItems: { type: 'array', items: { type: 'object', properties: { Description: { type: 'string' }, Quantity: { type: 'number' }, UnitAmount: { type: 'number' }, AccountCode: { type: 'string' } } } } }, required: ['creditNoteId'] } }, // Void credit note { name: 'xero_void_credit_note', description: 'Void a credit note. This sets the status to VOIDED.', inputSchema: { type: 'object', properties: { creditNoteId: { type: 'string', description: 'Credit note ID (GUID)' } }, required: ['creditNoteId'] } }, // Allocate credit note { name: 'xero_allocate_credit_note', description: 'Allocate a credit note to an invoice. This applies the credit to an invoice balance.', inputSchema: { type: 'object', properties: { creditNoteId: { type: 'string', description: 'Credit note ID (GUID)' }, invoiceId: { type: 'string', description: 'Invoice ID (GUID) to allocate to' }, amount: { type: 'number', description: 'Amount to allocate' }, date: { type: 'string', description: 'Allocation date (YYYY-MM-DD)' } }, required: ['creditNoteId', 'invoiceId', 'amount'] } } ]; } export async function handleCreditNoteTool( toolName: string, args: Record, client: XeroClient ): Promise { switch (toolName) { case 'xero_list_credit_notes': { const options = { page: args.page as number | undefined, pageSize: args.pageSize as number | undefined, where: args.where as string | undefined, order: args.order as string | undefined, ifModifiedSince: args.ifModifiedSince ? new Date(args.ifModifiedSince as string) : undefined }; return await client.getCreditNotes(options); } case 'xero_get_credit_note': { const { creditNoteId } = z.object({ creditNoteId: z.string() }).parse(args); return await client.getCreditNote(creditNoteId as any); } case 'xero_create_credit_note': { const schema = z.object({ type: z.enum(['ACCRECCREDIT', 'ACCPAYCREDIT']), contact: ContactSchema, lineItems: z.array(LineItemSchema).min(1), date: z.string().optional(), reference: z.string().optional(), creditNoteNumber: z.string().optional(), status: z.enum(['DRAFT', 'AUTHORISED']).default('DRAFT'), lineAmountTypes: z.enum(['Exclusive', 'Inclusive', 'NoTax']).default('Exclusive'), currencyCode: z.string().optional() }); const data = schema.parse(args); const creditNote: any = { Type: data.type, Contact: data.contact, LineItems: data.lineItems, Status: data.status, LineAmountTypes: data.lineAmountTypes }; if (data.date) creditNote.Date = data.date; if (data.reference) creditNote.Reference = data.reference; if (data.creditNoteNumber) creditNote.CreditNoteNumber = data.creditNoteNumber; if (data.currencyCode) creditNote.CurrencyCode = data.currencyCode; return await client.createCreditNote(creditNote); } case 'xero_update_credit_note': { const schema = z.object({ creditNoteId: z.string(), status: z.enum(['DRAFT', 'AUTHORISED', 'SUBMITTED']).optional(), reference: z.string().optional(), lineItems: z.array(LineItemSchema).optional() }); const data = schema.parse(args); const updates: any = {}; if (data.status) updates.Status = data.status; if (data.reference) updates.Reference = data.reference; if (data.lineItems) updates.LineItems = data.lineItems; return await client.updateCreditNote(data.creditNoteId as any, updates); } case 'xero_void_credit_note': { const { creditNoteId } = z.object({ creditNoteId: z.string() }).parse(args); return await client.updateCreditNote(creditNoteId as any, { Status: 'VOIDED' as any }); } case 'xero_allocate_credit_note': { const schema = z.object({ creditNoteId: z.string(), invoiceId: z.string(), amount: z.number(), date: z.string().optional() }); const data = schema.parse(args); // Get the credit note const creditNote = await client.getCreditNote(data.creditNoteId as any); const allocations = (creditNote as any).Allocations || []; allocations.push({ Invoice: { InvoiceID: data.invoiceId }, Amount: data.amount, Date: data.date || new Date().toISOString().split('T')[0] }); return { success: true, message: 'Credit note allocation created', allocation: allocations[allocations.length - 1] }; } default: throw new Error(`Unknown credit note tool: ${toolName}`); } }