From 3f5b62c93df337905c4cc7eb41d73a45dcfe23d8 Mon Sep 17 00:00:00 2001 From: Richard Davies Date: Sun, 7 Jun 2026 20:18:27 +0100 Subject: [PATCH 1/6] feat: add shared allocation-line Zod schema Co-Authored-By: Claude Opus 4.8 (1M context) --- src/helpers/allocation-schema.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/helpers/allocation-schema.ts diff --git a/src/helpers/allocation-schema.ts b/src/helpers/allocation-schema.ts new file mode 100644 index 0000000..7c9fa01 --- /dev/null +++ b/src/helpers/allocation-schema.ts @@ -0,0 +1,22 @@ +import { z } from "zod"; + +// One invoice that a credit note / overpayment / prepayment is applied to. +// Shared by all three create-allocation tools. Mirrors the SDK Allocation model +// (only the target invoice id, amount, and date are needed on the request body — +// the source object is identified by the endpoint path param). +export const allocationLineSchema = z.object({ + invoiceId: z + .string() + .describe( + "The ID of the invoice to apply this allocation to. Obtain from the list-invoices tool. The invoice must be AUTHORISED and not fully paid.", + ), + amount: z + .number() + .positive() + .describe( + "The amount to apply to this invoice (must be positive and not exceed the invoice's amount due or the source's remaining balance).", + ), + date: z + .string() + .describe("The date the allocation is applied, in YYYY-MM-DD format."), +}); From f02779b4a046db4f90fe388fd6c5491e94ce4de5 Mon Sep 17 00:00:00 2001 From: Richard Davies Date: Sun, 7 Jun 2026 20:19:26 +0100 Subject: [PATCH 2/6] feat: add list-overpayments tool Co-Authored-By: Claude Opus 4.8 (1M context) --- .../list-xero-overpayments.handler.ts | 51 ++++++++++++++++ src/tools/list/index.ts | 2 + src/tools/list/list-overpayments.tool.ts | 60 +++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 src/handlers/list-xero-overpayments.handler.ts create mode 100644 src/tools/list/list-overpayments.tool.ts diff --git a/src/handlers/list-xero-overpayments.handler.ts b/src/handlers/list-xero-overpayments.handler.ts new file mode 100644 index 0000000..6189804 --- /dev/null +++ b/src/handlers/list-xero-overpayments.handler.ts @@ -0,0 +1,51 @@ +import { xeroClient } from "../clients/xero-client.js"; +import { XeroClientResponse } from "../types/tool-response.js"; +import { formatError } from "../helpers/format-error.js"; +import { Overpayment } from "xero-node"; +import { getClientHeaders } from "../helpers/get-client-headers.js"; + +async function getOverpayments( + contactId: string | undefined, + page: number, + pageSize: number = 10, +): Promise { + await xeroClient.authenticate(); + + const response = await xeroClient.accountingApi.getOverpayments( + xeroClient.tenantId, + undefined, // ifModifiedSince + contactId ? `Contact.ContactID=guid("${contactId}")` : undefined, // where + "UpdatedDateUTC DESC", // order + page, // page + undefined, // unitdp + pageSize, // pageSize + getClientHeaders(), + ); + + return response.body.overpayments ?? []; +} + +/** + * List overpayments from Xero + */ +export async function listXeroOverpayments( + page: number = 1, + contactId?: string, + pageSize: number = 10, +): Promise> { + try { + const overpayments = await getOverpayments(contactId, page, pageSize); + + return { + result: overpayments, + isError: false, + error: null, + }; + } catch (error) { + return { + result: null, + isError: true, + error: formatError(error), + }; + } +} diff --git a/src/tools/list/index.ts b/src/tools/list/index.ts index b4d1b62..6b65e8d 100644 --- a/src/tools/list/index.ts +++ b/src/tools/list/index.ts @@ -9,6 +9,7 @@ import ListInvoicesTool from "./list-invoices.tool.js"; import ListItemsTool from "./list-items.tool.js"; import ListManualJournalsTool from "./list-manual-journals.tool.js"; import ListOrganisationDetailsTool from "./list-organisation-details.tool.js"; +import ListOverpaymentsTool from "./list-overpayments.tool.js"; import ListPaymentsTool from "./list-payments.tool.js"; import ListPayrollEmployeeLeaveBalancesTool from "./list-payroll-employee-leave-balances.tool.js"; @@ -40,6 +41,7 @@ export const ListTools = [ ListTaxRatesTool, ListTrialBalanceTool, ListPaymentsTool, + ListOverpaymentsTool, ListProfitAndLossTool, ListBankTransactionsTool, ListPayrollEmployeesTool, diff --git a/src/tools/list/list-overpayments.tool.ts b/src/tools/list/list-overpayments.tool.ts new file mode 100644 index 0000000..16cf5a9 --- /dev/null +++ b/src/tools/list/list-overpayments.tool.ts @@ -0,0 +1,60 @@ +import { z } from "zod"; +import { listXeroOverpayments } from "../../handlers/list-xero-overpayments.handler.js"; +import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; + +const ListOverpaymentsTool = CreateXeroTool( + "list-overpayments", + `List overpayments in Xero. An overpayment is money a contact paid in excess of what + they owed; its remaining balance can be allocated to an invoice with the + create-overpayment-allocation tool. Optionally filter by contact. Ask the user if they + want the next page after 10 are returned; if so, call again with the next page number.`, + { + page: z.number(), + contactId: z.string().optional(), + pageSize: z.number().min(1).max(100).default(10).optional().describe("Number of results per page (1-100, default 10)"), + }, + async ({ page, contactId, pageSize }) => { + const response = await listXeroOverpayments(page, contactId, pageSize); + if (response.error !== null) { + return { + content: [ + { + type: "text" as const, + text: `Error listing overpayments: ${response.error}`, + }, + ], + }; + } + + const overpayments = response.result; + + return { + content: [ + { + type: "text" as const, + text: `Found ${overpayments?.length || 0} overpayments:`, + }, + ...(overpayments?.map((overpayment) => ({ + type: "text" as const, + text: [ + `Overpayment ID: ${overpayment.overpaymentID}`, + `Type: ${overpayment.type || "Unknown"}`, + `Status: ${overpayment.status || "Unknown"}`, + overpayment.contact + ? `Contact: ${overpayment.contact.name} (${overpayment.contact.contactID})` + : null, + overpayment.date ? `Date: ${overpayment.date}` : null, + `Total: ${overpayment.total ?? 0}`, + `Remaining Credit: ${overpayment.remainingCredit ?? 0}`, + overpayment.currencyCode ? `Currency: ${overpayment.currencyCode}` : null, + overpayment.updatedDateUTC ? `Last Updated: ${overpayment.updatedDateUTC}` : null, + ] + .filter(Boolean) + .join("\n"), + })) || []), + ], + }; + }, +); + +export default ListOverpaymentsTool; From 95c8be07cbf24b2f978d1332d1dbc534b272456d Mon Sep 17 00:00:00 2001 From: Richard Davies Date: Sun, 7 Jun 2026 20:20:07 +0100 Subject: [PATCH 3/6] feat: add list-prepayments tool Co-Authored-By: Claude Opus 4.8 (1M context) --- src/handlers/list-xero-prepayments.handler.ts | 51 ++++++++++++++++ src/tools/list/index.ts | 2 + src/tools/list/list-prepayments.tool.ts | 60 +++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 src/handlers/list-xero-prepayments.handler.ts create mode 100644 src/tools/list/list-prepayments.tool.ts diff --git a/src/handlers/list-xero-prepayments.handler.ts b/src/handlers/list-xero-prepayments.handler.ts new file mode 100644 index 0000000..f5ab3d9 --- /dev/null +++ b/src/handlers/list-xero-prepayments.handler.ts @@ -0,0 +1,51 @@ +import { xeroClient } from "../clients/xero-client.js"; +import { XeroClientResponse } from "../types/tool-response.js"; +import { formatError } from "../helpers/format-error.js"; +import { Prepayment } from "xero-node"; +import { getClientHeaders } from "../helpers/get-client-headers.js"; + +async function getPrepayments( + contactId: string | undefined, + page: number, + pageSize: number = 10, +): Promise { + await xeroClient.authenticate(); + + const response = await xeroClient.accountingApi.getPrepayments( + xeroClient.tenantId, + undefined, // ifModifiedSince + contactId ? `Contact.ContactID=guid("${contactId}")` : undefined, // where + "UpdatedDateUTC DESC", // order + page, // page + undefined, // unitdp + pageSize, // pageSize + getClientHeaders(), + ); + + return response.body.prepayments ?? []; +} + +/** + * List prepayments from Xero + */ +export async function listXeroPrepayments( + page: number = 1, + contactId?: string, + pageSize: number = 10, +): Promise> { + try { + const prepayments = await getPrepayments(contactId, page, pageSize); + + return { + result: prepayments, + isError: false, + error: null, + }; + } catch (error) { + return { + result: null, + isError: true, + error: formatError(error), + }; + } +} diff --git a/src/tools/list/index.ts b/src/tools/list/index.ts index 6b65e8d..cb62231 100644 --- a/src/tools/list/index.ts +++ b/src/tools/list/index.ts @@ -11,6 +11,7 @@ import ListManualJournalsTool from "./list-manual-journals.tool.js"; import ListOrganisationDetailsTool from "./list-organisation-details.tool.js"; import ListOverpaymentsTool from "./list-overpayments.tool.js"; import ListPaymentsTool from "./list-payments.tool.js"; +import ListPrepaymentsTool from "./list-prepayments.tool.js"; import ListPayrollEmployeeLeaveBalancesTool from "./list-payroll-employee-leave-balances.tool.js"; import ListPayrollEmployeeLeaveTypesTool @@ -42,6 +43,7 @@ export const ListTools = [ ListTrialBalanceTool, ListPaymentsTool, ListOverpaymentsTool, + ListPrepaymentsTool, ListProfitAndLossTool, ListBankTransactionsTool, ListPayrollEmployeesTool, diff --git a/src/tools/list/list-prepayments.tool.ts b/src/tools/list/list-prepayments.tool.ts new file mode 100644 index 0000000..bd39439 --- /dev/null +++ b/src/tools/list/list-prepayments.tool.ts @@ -0,0 +1,60 @@ +import { z } from "zod"; +import { listXeroPrepayments } from "../../handlers/list-xero-prepayments.handler.js"; +import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; + +const ListPrepaymentsTool = CreateXeroTool( + "list-prepayments", + `List prepayments in Xero. A prepayment is money received in advance of an invoice; its + remaining balance can be allocated to an invoice with the create-prepayment-allocation + tool. Optionally filter by contact. Ask the user if they want the next page after 10 are + returned; if so, call again with the next page number.`, + { + page: z.number(), + contactId: z.string().optional(), + pageSize: z.number().min(1).max(100).default(10).optional().describe("Number of results per page (1-100, default 10)"), + }, + async ({ page, contactId, pageSize }) => { + const response = await listXeroPrepayments(page, contactId, pageSize); + if (response.error !== null) { + return { + content: [ + { + type: "text" as const, + text: `Error listing prepayments: ${response.error}`, + }, + ], + }; + } + + const prepayments = response.result; + + return { + content: [ + { + type: "text" as const, + text: `Found ${prepayments?.length || 0} prepayments:`, + }, + ...(prepayments?.map((prepayment) => ({ + type: "text" as const, + text: [ + `Prepayment ID: ${prepayment.prepaymentID}`, + `Type: ${prepayment.type || "Unknown"}`, + `Status: ${prepayment.status || "Unknown"}`, + prepayment.contact + ? `Contact: ${prepayment.contact.name} (${prepayment.contact.contactID})` + : null, + prepayment.date ? `Date: ${prepayment.date}` : null, + `Total: ${prepayment.total ?? 0}`, + `Remaining Credit: ${prepayment.remainingCredit ?? 0}`, + prepayment.currencyCode ? `Currency: ${prepayment.currencyCode}` : null, + prepayment.updatedDateUTC ? `Last Updated: ${prepayment.updatedDateUTC}` : null, + ] + .filter(Boolean) + .join("\n"), + })) || []), + ], + }; + }, +); + +export default ListPrepaymentsTool; From b9f0ea9c04a32a2d54d208522337b0bb669ac005 Mon Sep 17 00:00:00 2001 From: Richard Davies Date: Sun, 7 Jun 2026 20:21:00 +0100 Subject: [PATCH 4/6] feat: add create-credit-note-allocation tool Co-Authored-By: Claude Opus 4.8 (1M context) --- ...ate-xero-credit-note-allocation.handler.ts | 54 ++++++++++++++++++ .../create-credit-note-allocation.tool.ts | 57 +++++++++++++++++++ src/tools/create/index.ts | 2 + 3 files changed, 113 insertions(+) create mode 100644 src/handlers/create-xero-credit-note-allocation.handler.ts create mode 100644 src/tools/create/create-credit-note-allocation.tool.ts diff --git a/src/handlers/create-xero-credit-note-allocation.handler.ts b/src/handlers/create-xero-credit-note-allocation.handler.ts new file mode 100644 index 0000000..6704b61 --- /dev/null +++ b/src/handlers/create-xero-credit-note-allocation.handler.ts @@ -0,0 +1,54 @@ +import { xeroClient } from "../clients/xero-client.js"; +import { XeroClientResponse } from "../types/tool-response.js"; +import { formatError } from "../helpers/format-error.js"; +import { Allocation, Allocations } from "xero-node"; +import { getClientHeaders } from "../helpers/get-client-headers.js"; + +export type AllocationLine = { + invoiceId: string; + amount: number; + date: string; +}; + +function toAllocations(lines: AllocationLine[]): Allocations { + return { + allocations: lines.map((line) => ({ + invoice: { invoiceID: line.invoiceId }, + amount: line.amount, + date: line.date, + })), + }; +} + +/** + * Apply a credit note to one or more invoices. + */ +export async function createXeroCreditNoteAllocation( + creditNoteId: string, + allocations: AllocationLine[], +): Promise> { + try { + await xeroClient.authenticate(); + + const response = await xeroClient.accountingApi.createCreditNoteAllocation( + xeroClient.tenantId, + creditNoteId, + toAllocations(allocations), + undefined, // summarizeErrors + undefined, // idempotencyKey + getClientHeaders(), + ); + + return { + result: response.body.allocations ?? [], + isError: false, + error: null, + }; + } catch (error) { + return { + result: null, + isError: true, + error: formatError(error), + }; + } +} diff --git a/src/tools/create/create-credit-note-allocation.tool.ts b/src/tools/create/create-credit-note-allocation.tool.ts new file mode 100644 index 0000000..da6dcce --- /dev/null +++ b/src/tools/create/create-credit-note-allocation.tool.ts @@ -0,0 +1,57 @@ +import { z } from "zod"; +import { createXeroCreditNoteAllocation } from "../../handlers/create-xero-credit-note-allocation.handler.js"; +import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { allocationLineSchema } from "../../helpers/allocation-schema.js"; + +const CreateCreditNoteAllocationTool = CreateXeroTool( + "create-credit-note-allocation", + `Apply a credit note to one or more invoices in Xero. Provide the credit note ID (from + list-credit-notes) and one allocation per target invoice (invoice ID from list-invoices, + the amount to apply, and the date). The credit note must be AUTHORISED with remaining + credit; each invoice must be AUTHORISED and not fully paid. The total allocated cannot + exceed the credit note's remaining credit.`, + { + creditNoteId: z + .string() + .describe("The ID of the credit note to allocate. Obtain from the list-credit-notes tool."), + allocations: z + .array(allocationLineSchema) + .min(1) + .describe("One entry per invoice the credit note is applied to."), + }, + async ({ creditNoteId, allocations }) => { + const response = await createXeroCreditNoteAllocation(creditNoteId, allocations); + if (response.isError) { + return { + content: [ + { + type: "text" as const, + text: `Error creating credit note allocation: ${response.error}`, + }, + ], + }; + } + + const applied = response.result ?? []; + + return { + content: [ + { + type: "text" as const, + text: `Applied credit note ${creditNoteId} to ${applied.length} invoice(s):`, + }, + ...applied.map((allocation) => ({ + type: "text" as const, + text: [ + `Allocation ID: ${allocation.allocationID ?? "(not returned)"}`, + `Invoice ID: ${allocation.invoice?.invoiceID ?? "Unknown"}`, + `Amount: ${allocation.amount}`, + `Date: ${allocation.date}`, + ].join("\n"), + })), + ], + }; + }, +); + +export default CreateCreditNoteAllocationTool; diff --git a/src/tools/create/index.ts b/src/tools/create/index.ts index 1062a91..2d56f92 100644 --- a/src/tools/create/index.ts +++ b/src/tools/create/index.ts @@ -1,6 +1,7 @@ import CreateBankTransactionTool from "./create-bank-transaction.tool.js"; import CreateContactTool from "./create-contact.tool.js"; import CreateCreditNoteTool from "./create-credit-note.tool.js"; +import CreateCreditNoteAllocationTool from "./create-credit-note-allocation.tool.js"; import CreateInvoiceTool from "./create-invoice.tool.js"; import CreateItemTool from "./create-item.tool.js"; import CreateManualJournalTool from "./create-manual-journal.tool.js"; @@ -13,6 +14,7 @@ import CreateTrackingOptionsTool from "./create-tracking-options.tool.js"; export const CreateTools = [ CreateContactTool, CreateCreditNoteTool, + CreateCreditNoteAllocationTool, CreateManualJournalTool, CreateInvoiceTool, CreateQuoteTool, From 443d2502b927c9df1c587075a6a5872626cfa58a Mon Sep 17 00:00:00 2001 From: Richard Davies Date: Sun, 7 Jun 2026 20:21:49 +0100 Subject: [PATCH 5/6] feat: add create-overpayment-allocation tool Co-Authored-By: Claude Opus 4.8 (1M context) --- ...ate-xero-overpayment-allocation.handler.ts | 49 ++++++++++++++++ .../create-overpayment-allocation.tool.ts | 57 +++++++++++++++++++ src/tools/create/index.ts | 2 + 3 files changed, 108 insertions(+) create mode 100644 src/handlers/create-xero-overpayment-allocation.handler.ts create mode 100644 src/tools/create/create-overpayment-allocation.tool.ts diff --git a/src/handlers/create-xero-overpayment-allocation.handler.ts b/src/handlers/create-xero-overpayment-allocation.handler.ts new file mode 100644 index 0000000..3d1c310 --- /dev/null +++ b/src/handlers/create-xero-overpayment-allocation.handler.ts @@ -0,0 +1,49 @@ +import { xeroClient } from "../clients/xero-client.js"; +import { XeroClientResponse } from "../types/tool-response.js"; +import { formatError } from "../helpers/format-error.js"; +import { Allocation, Allocations } from "xero-node"; +import { getClientHeaders } from "../helpers/get-client-headers.js"; +import { AllocationLine } from "./create-xero-credit-note-allocation.handler.js"; + +function toAllocations(lines: AllocationLine[]): Allocations { + return { + allocations: lines.map((line) => ({ + invoice: { invoiceID: line.invoiceId }, + amount: line.amount, + date: line.date, + })), + }; +} + +/** + * Apply an overpayment to one or more invoices. + */ +export async function createXeroOverpaymentAllocation( + overpaymentId: string, + allocations: AllocationLine[], +): Promise> { + try { + await xeroClient.authenticate(); + + const response = await xeroClient.accountingApi.createOverpaymentAllocations( + xeroClient.tenantId, + overpaymentId, + toAllocations(allocations), + undefined, // summarizeErrors + undefined, // idempotencyKey + getClientHeaders(), + ); + + return { + result: response.body.allocations ?? [], + isError: false, + error: null, + }; + } catch (error) { + return { + result: null, + isError: true, + error: formatError(error), + }; + } +} diff --git a/src/tools/create/create-overpayment-allocation.tool.ts b/src/tools/create/create-overpayment-allocation.tool.ts new file mode 100644 index 0000000..877567f --- /dev/null +++ b/src/tools/create/create-overpayment-allocation.tool.ts @@ -0,0 +1,57 @@ +import { z } from "zod"; +import { createXeroOverpaymentAllocation } from "../../handlers/create-xero-overpayment-allocation.handler.js"; +import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { allocationLineSchema } from "../../helpers/allocation-schema.js"; + +const CreateOverpaymentAllocationTool = CreateXeroTool( + "create-overpayment-allocation", + `Apply an overpayment to one or more invoices in Xero. Provide the overpayment ID (from + list-overpayments) and one allocation per target invoice (invoice ID from list-invoices, + the amount to apply, and the date). The overpayment must have remaining credit; each + invoice must be AUTHORISED and not fully paid. The total allocated cannot exceed the + overpayment's remaining credit.`, + { + overpaymentId: z + .string() + .describe("The ID of the overpayment to allocate. Obtain from the list-overpayments tool."), + allocations: z + .array(allocationLineSchema) + .min(1) + .describe("One entry per invoice the overpayment is applied to."), + }, + async ({ overpaymentId, allocations }) => { + const response = await createXeroOverpaymentAllocation(overpaymentId, allocations); + if (response.isError) { + return { + content: [ + { + type: "text" as const, + text: `Error creating overpayment allocation: ${response.error}`, + }, + ], + }; + } + + const applied = response.result ?? []; + + return { + content: [ + { + type: "text" as const, + text: `Applied overpayment ${overpaymentId} to ${applied.length} invoice(s):`, + }, + ...applied.map((allocation) => ({ + type: "text" as const, + text: [ + `Allocation ID: ${allocation.allocationID ?? "(not returned)"}`, + `Invoice ID: ${allocation.invoice?.invoiceID ?? "Unknown"}`, + `Amount: ${allocation.amount}`, + `Date: ${allocation.date}`, + ].join("\n"), + })), + ], + }; + }, +); + +export default CreateOverpaymentAllocationTool; diff --git a/src/tools/create/index.ts b/src/tools/create/index.ts index 2d56f92..6a19d3d 100644 --- a/src/tools/create/index.ts +++ b/src/tools/create/index.ts @@ -5,6 +5,7 @@ import CreateCreditNoteAllocationTool from "./create-credit-note-allocation.tool import CreateInvoiceTool from "./create-invoice.tool.js"; import CreateItemTool from "./create-item.tool.js"; import CreateManualJournalTool from "./create-manual-journal.tool.js"; +import CreateOverpaymentAllocationTool from "./create-overpayment-allocation.tool.js"; import CreatePaymentTool from "./create-payment.tool.js"; import CreatePayrollTimesheetTool from "./create-payroll-timesheet.tool.js"; import CreateQuoteTool from "./create-quote.tool.js"; @@ -19,6 +20,7 @@ export const CreateTools = [ CreateInvoiceTool, CreateQuoteTool, CreatePaymentTool, + CreateOverpaymentAllocationTool, CreateItemTool, CreateBankTransactionTool, CreatePayrollTimesheetTool, From e7e755b603a93f14653df57fd86a6a3c4b1af473 Mon Sep 17 00:00:00 2001 From: Richard Davies Date: Sun, 7 Jun 2026 20:22:29 +0100 Subject: [PATCH 6/6] feat: add create-prepayment-allocation tool Co-Authored-By: Claude Opus 4.8 (1M context) --- ...eate-xero-prepayment-allocation.handler.ts | 49 ++++++++++++++++ .../create-prepayment-allocation.tool.ts | 57 +++++++++++++++++++ src/tools/create/index.ts | 2 + 3 files changed, 108 insertions(+) create mode 100644 src/handlers/create-xero-prepayment-allocation.handler.ts create mode 100644 src/tools/create/create-prepayment-allocation.tool.ts diff --git a/src/handlers/create-xero-prepayment-allocation.handler.ts b/src/handlers/create-xero-prepayment-allocation.handler.ts new file mode 100644 index 0000000..2f95fc1 --- /dev/null +++ b/src/handlers/create-xero-prepayment-allocation.handler.ts @@ -0,0 +1,49 @@ +import { xeroClient } from "../clients/xero-client.js"; +import { XeroClientResponse } from "../types/tool-response.js"; +import { formatError } from "../helpers/format-error.js"; +import { Allocation, Allocations } from "xero-node"; +import { getClientHeaders } from "../helpers/get-client-headers.js"; +import { AllocationLine } from "./create-xero-credit-note-allocation.handler.js"; + +function toAllocations(lines: AllocationLine[]): Allocations { + return { + allocations: lines.map((line) => ({ + invoice: { invoiceID: line.invoiceId }, + amount: line.amount, + date: line.date, + })), + }; +} + +/** + * Apply a prepayment to one or more invoices. + */ +export async function createXeroPrepaymentAllocation( + prepaymentId: string, + allocations: AllocationLine[], +): Promise> { + try { + await xeroClient.authenticate(); + + const response = await xeroClient.accountingApi.createPrepaymentAllocations( + xeroClient.tenantId, + prepaymentId, + toAllocations(allocations), + undefined, // summarizeErrors + undefined, // idempotencyKey + getClientHeaders(), + ); + + return { + result: response.body.allocations ?? [], + isError: false, + error: null, + }; + } catch (error) { + return { + result: null, + isError: true, + error: formatError(error), + }; + } +} diff --git a/src/tools/create/create-prepayment-allocation.tool.ts b/src/tools/create/create-prepayment-allocation.tool.ts new file mode 100644 index 0000000..4112366 --- /dev/null +++ b/src/tools/create/create-prepayment-allocation.tool.ts @@ -0,0 +1,57 @@ +import { z } from "zod"; +import { createXeroPrepaymentAllocation } from "../../handlers/create-xero-prepayment-allocation.handler.js"; +import { CreateXeroTool } from "../../helpers/create-xero-tool.js"; +import { allocationLineSchema } from "../../helpers/allocation-schema.js"; + +const CreatePrepaymentAllocationTool = CreateXeroTool( + "create-prepayment-allocation", + `Apply a prepayment to one or more invoices in Xero. Provide the prepayment ID (from + list-prepayments) and one allocation per target invoice (invoice ID from list-invoices, + the amount to apply, and the date). The prepayment must have remaining credit; each + invoice must be AUTHORISED and not fully paid. The total allocated cannot exceed the + prepayment's remaining credit.`, + { + prepaymentId: z + .string() + .describe("The ID of the prepayment to allocate. Obtain from the list-prepayments tool."), + allocations: z + .array(allocationLineSchema) + .min(1) + .describe("One entry per invoice the prepayment is applied to."), + }, + async ({ prepaymentId, allocations }) => { + const response = await createXeroPrepaymentAllocation(prepaymentId, allocations); + if (response.isError) { + return { + content: [ + { + type: "text" as const, + text: `Error creating prepayment allocation: ${response.error}`, + }, + ], + }; + } + + const applied = response.result ?? []; + + return { + content: [ + { + type: "text" as const, + text: `Applied prepayment ${prepaymentId} to ${applied.length} invoice(s):`, + }, + ...applied.map((allocation) => ({ + type: "text" as const, + text: [ + `Allocation ID: ${allocation.allocationID ?? "(not returned)"}`, + `Invoice ID: ${allocation.invoice?.invoiceID ?? "Unknown"}`, + `Amount: ${allocation.amount}`, + `Date: ${allocation.date}`, + ].join("\n"), + })), + ], + }; + }, +); + +export default CreatePrepaymentAllocationTool; diff --git a/src/tools/create/index.ts b/src/tools/create/index.ts index 6a19d3d..8b1678b 100644 --- a/src/tools/create/index.ts +++ b/src/tools/create/index.ts @@ -7,6 +7,7 @@ import CreateItemTool from "./create-item.tool.js"; import CreateManualJournalTool from "./create-manual-journal.tool.js"; import CreateOverpaymentAllocationTool from "./create-overpayment-allocation.tool.js"; import CreatePaymentTool from "./create-payment.tool.js"; +import CreatePrepaymentAllocationTool from "./create-prepayment-allocation.tool.js"; import CreatePayrollTimesheetTool from "./create-payroll-timesheet.tool.js"; import CreateQuoteTool from "./create-quote.tool.js"; import CreateTrackingCategoryTool from "./create-tracking-category.tool.js"; @@ -21,6 +22,7 @@ export const CreateTools = [ CreateQuoteTool, CreatePaymentTool, CreateOverpaymentAllocationTool, + CreatePrepaymentAllocationTool, CreateItemTool, CreateBankTransactionTool, CreatePayrollTimesheetTool,