275 lines
9.3 KiB
TypeScript
275 lines
9.3 KiB
TypeScript
/**
|
|
* 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<string, unknown>,
|
|
client: XeroClient
|
|
): Promise<unknown> {
|
|
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}`);
|
|
}
|
|
}
|