Skip to content

Commit f2766ea

Browse files
committed
Add item and tracking support to invoices
1 parent 7495083 commit f2766ea

File tree

7 files changed

+85
-24
lines changed

7 files changed

+85
-24
lines changed

src/consts/deeplinks.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,7 @@ export const bankTransactionDeepLink = (accountId: string, bankTransactionId: st
2828
export const manualJournalDeepLink = (journalId: string) => {
2929
return `https://go.xero.com/Journal/View.aspx?invoiceID=${journalId}`;
3030
};
31+
32+
export const billDeepLink = (orgShortCode: string, billId: string) => {
33+
return `https://go.xero.com/organisationlogin/default.aspx?shortcode=${orgShortCode}&redirecturl=/AccountsPayable/Edit.aspx?InvoiceID=${billId}`;
34+
};

src/handlers/create-xero-invoice.handler.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { xeroClient } from "../clients/xero-client.js";
22
import { XeroClientResponse } from "../types/tool-response.js";
33
import { formatError } from "../helpers/format-error.js";
4-
import { Invoice } from "xero-node";
4+
import { Invoice, LineItemTracking } from "xero-node";
55
import { getClientHeaders } from "../helpers/get-client-headers.js";
66

77
interface InvoiceLineItem {
@@ -10,17 +10,20 @@ interface InvoiceLineItem {
1010
unitAmount: number;
1111
accountCode: string;
1212
taxType: string;
13+
itemCode?: string;
14+
tracking?: LineItemTracking[];
1315
}
1416

1517
async function createInvoice(
1618
contactId: string,
1719
lineItems: InvoiceLineItem[],
20+
type: Invoice.TypeEnum,
1821
reference: string | undefined,
1922
): Promise<Invoice | undefined> {
2023
await xeroClient.authenticate();
2124

2225
const invoice: Invoice = {
23-
type: Invoice.TypeEnum.ACCREC,
26+
type: type,
2427
contact: {
2528
contactID: contactId,
2629
},
@@ -53,10 +56,16 @@ async function createInvoice(
5356
export async function createXeroInvoice(
5457
contactId: string,
5558
lineItems: InvoiceLineItem[],
59+
type: Invoice.TypeEnum = Invoice.TypeEnum.ACCREC,
5660
reference?: string,
5761
): Promise<XeroClientResponse<Invoice>> {
5862
try {
59-
const createdInvoice = await createInvoice(contactId, lineItems, reference);
63+
const createdInvoice = await createInvoice(
64+
contactId,
65+
lineItems,
66+
type,
67+
reference,
68+
);
6069

6170
if (!createdInvoice) {
6271
throw new Error("Invoice creation failed.");

src/handlers/update-xero-invoice.handler.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { xeroClient } from "../clients/xero-client.js";
22
import { XeroClientResponse } from "../types/tool-response.js";
33
import { formatError } from "../helpers/format-error.js";
4-
import { Invoice } from "xero-node";
4+
import { Invoice, LineItemTracking } from "xero-node";
55
import { getClientHeaders } from "../helpers/get-client-headers.js";
66

77
interface InvoiceLineItem {
@@ -10,6 +10,8 @@ interface InvoiceLineItem {
1010
unitAmount: number;
1111
accountCode: string;
1212
taxType: string;
13+
itemCode?: string;
14+
tracking?: LineItemTracking[];
1315
}
1416

1517
async function getInvoice(invoiceId: string): Promise<Invoice | undefined> {

src/helpers/format-line-item.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ import { LineItem } from "xero-node";
22

33
export const formatLineItem = (lineItem: LineItem): string => {
44
return [
5+
`Item ID: ${lineItem.item}`,
6+
`Item Code: ${lineItem.itemCode}`,
57
`Description: ${lineItem.description}`,
68
`Quantity: ${lineItem.quantity}`,
79
`Unit Amount: ${lineItem.unitAmount}`,
810
`Account Code: ${lineItem.accountCode}`,
911
`Tax Type: ${lineItem.taxType}`,
12+
`Tracking: ${lineItem.tracking}`,
13+
`Line Amount: ${lineItem.lineAmount}`,
1014
].join("\n");
1115
};

src/helpers/get-deeplink.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
paymentDeepLink,
77
manualJournalDeepLink,
88
quoteDeepLink,
9+
billDeepLink,
910
} from "../consts/deeplinks.js";
1011

1112
export enum DeepLinkType {
@@ -15,6 +16,7 @@ export enum DeepLinkType {
1516
MANUAL_JOURNAL,
1617
QUOTE,
1718
PAYMENT,
19+
BILL,
1820
}
1921

2022
/**
@@ -44,5 +46,7 @@ export const getDeepLink = async (type: DeepLinkType, itemId: string) => {
4446
return quoteDeepLink(orgShortCode, itemId);
4547
case DeepLinkType.PAYMENT:
4648
return paymentDeepLink(orgShortCode, itemId);
49+
case DeepLinkType.BILL:
50+
return billDeepLink(orgShortCode, itemId);
4751
}
4852
};

src/tools/create/create-invoice.tool.ts

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,26 @@ import { z } from "zod";
22
import { createXeroInvoice } from "../../handlers/create-xero-invoice.handler.js";
33
import { DeepLinkType, getDeepLink } from "../../helpers/get-deeplink.js";
44
import { CreateXeroTool } from "../../helpers/create-xero-tool.js";
5+
import { Invoice } from "xero-node";
6+
7+
const trackingSchema = z.object({
8+
name: z.string().describe("The name of the tracking category. Can be obtained from the list-tracking-categories tool"),
9+
option: z.string().describe("The name of the tracking option. Can be obtained from the list-tracking-categories tool"),
10+
trackingCategoryID: z.string().describe("The ID of the tracking category. \
11+
Can be obtained from the list-tracking-categories tool"),
12+
});
513

614
const lineItemSchema = z.object({
7-
description: z.string(),
8-
quantity: z.number(),
9-
unitAmount: z.number(),
10-
accountCode: z.string(),
11-
taxType: z.string(),
15+
description: z.string().describe("The description of the line item"),
16+
quantity: z.number().describe("The quantity of the line item"),
17+
unitAmount: z.number().describe("The price per unit of the line item"),
18+
accountCode: z.string().describe("The account code of the line item - can be obtained from the list-accounts tool"),
19+
taxType: z.string().describe("The tax type of the line item - can be obtained from the list-tax-rates tool"),
20+
itemCode: z.string().describe("The item code of the line item - can be obtained from the list-items tool \
21+
If the item is not listed, add without an item code and ask the user if they would like to add an item code.").optional(),
22+
tracking: z.array(trackingSchema).describe("Up to 2 tracking categories and options can be added to the line item. \
23+
Can be obtained from the list-tracking-categories tool. \
24+
Only use if prompted by the user.").optional(),
1225
});
1326

1427
const CreateInvoiceTool = CreateXeroTool(
@@ -18,12 +31,18 @@ const CreateInvoiceTool = CreateXeroTool(
1831
This deep link can be used to view the invoice in Xero directly. \
1932
This link should be displayed to the user.",
2033
{
21-
contactId: z.string(),
34+
contactId: z.string().describe("The ID of the contact to create the invoice for. \
35+
Can be obtained from the list-contacts tool."),
2236
lineItems: z.array(lineItemSchema),
23-
reference: z.string().optional(),
37+
type: z.enum(["ACCREC", "ACCPAY"]).describe("The type of invoice to create. \
38+
ACCREC is for sales invoices, Accounts Receivable, or customer invoices. \
39+
ACCPAY is for purchase invoices, Accounts Payable invoices, supplier invoices, or bills. \
40+
If the type is not specified, the default is ACCREC."),
41+
reference: z.string().describe("A reference number for the invoice.").optional(),
2442
},
25-
async ({ contactId, lineItems, reference }) => {
26-
const result = await createXeroInvoice(contactId, lineItems, reference);
43+
async ({ contactId, lineItems, type, reference }) => {
44+
const xeroInvoiceType = type === "ACCREC" ? Invoice.TypeEnum.ACCREC : Invoice.TypeEnum.ACCPAY;
45+
const result = await createXeroInvoice(contactId, lineItems, xeroInvoiceType, reference);
2746
if (result.isError) {
2847
return {
2948
content: [
@@ -38,7 +57,10 @@ const CreateInvoiceTool = CreateXeroTool(
3857
const invoice = result.result;
3958

4059
const deepLink = invoice.invoiceID
41-
? await getDeepLink(DeepLinkType.INVOICE, invoice.invoiceID)
60+
? await getDeepLink(
61+
invoice.type === Invoice.TypeEnum.ACCREC ? DeepLinkType.INVOICE : DeepLinkType.BILL,
62+
invoice.invoiceID,
63+
)
4264
: null;
4365

4466
return {
@@ -49,6 +71,7 @@ const CreateInvoiceTool = CreateXeroTool(
4971
"Invoice created successfully:",
5072
`ID: ${invoice?.invoiceID}`,
5173
`Contact: ${invoice?.contact?.name}`,
74+
`Type: ${invoice?.type}`,
5275
`Total: ${invoice?.total}`,
5376
`Status: ${invoice?.status}`,
5477
deepLink ? `Link to view: ${deepLink}` : null,

src/tools/update/update-invoice.tool.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,25 @@ import { updateXeroInvoice } from "../../handlers/update-xero-invoice.handler.js
33
import { DeepLinkType, getDeepLink } from "../../helpers/get-deeplink.js";
44
import { CreateXeroTool } from "../../helpers/create-xero-tool.js";
55

6+
const trackingSchema = z.object({
7+
name: z.string().describe("The name of the tracking category. Can be obtained from the list-tracking-categories tool"),
8+
option: z.string().describe("The name of the tracking option. Can be obtained from the list-tracking-categories tool"),
9+
trackingCategoryID: z.string().describe("The ID of the tracking category. \
10+
Can be obtained from the list-tracking-categories tool"),
11+
});
12+
613
const lineItemSchema = z.object({
7-
description: z.string(),
8-
quantity: z.number(),
9-
unitAmount: z.number(),
10-
accountCode: z.string(),
11-
taxType: z.string(),
14+
description: z.string().describe("The description of the line item"),
15+
quantity: z.number().describe("The quantity of the line item"),
16+
unitAmount: z.number().describe("The price per unit of the line item"),
17+
accountCode: z.string().describe("The account code of the line item - can be obtained from the list-accounts tool"),
18+
taxType: z.string().describe("The tax type of the line item - can be obtained from the list-tax-rates tool"),
19+
itemCode: z.string().describe("The item code of the line item - can be obtained from the list-items tool \
20+
If the item was not populated in the original invoice, \
21+
add without an item code unless the user has told you to add an item code.").optional(),
22+
tracking: z.array(trackingSchema).describe("Up to 2 tracking categories and options can be added to the line item. \
23+
Can be obtained from the list-tracking-categories tool. \
24+
Only use if prompted by the user.").optional(),
1225
});
1326

1427
const UpdateInvoiceTool = CreateXeroTool(
@@ -20,15 +33,16 @@ const UpdateInvoiceTool = CreateXeroTool(
2033
This deep link can be used to view the contact in Xero directly. \
2134
This link should be displayed to the user.",
2235
{
23-
invoiceId: z.string(),
36+
invoiceId: z.string().describe("The ID of the invoice to update."),
2437
lineItems: z.array(lineItemSchema).optional().describe(
2538
"All line items must be provided. Any line items not provided will be removed. Including existing line items. \
2639
Do not modify line items that have not been specified by the user",
2740
),
28-
reference: z.string().optional(),
29-
dueDate: z.string().optional(),
30-
date: z.string().optional(),
31-
contactId: z.string().optional(),
41+
reference: z.string().optional().describe("A reference number for the invoice."),
42+
dueDate: z.string().optional().describe("The due date of the invoice."),
43+
date: z.string().optional().describe("The date of the invoice."),
44+
contactId: z.string().optional().describe("The ID of the contact to update the invoice for. \
45+
Can be obtained from the list-contacts tool."),
3246
},
3347
async (
3448
{
@@ -87,6 +101,7 @@ const UpdateInvoiceTool = CreateXeroTool(
87101
"Invoice updated successfully:",
88102
`ID: ${invoice?.invoiceID}`,
89103
`Contact: ${invoice?.contact?.name}`,
104+
`Type: ${invoice?.type}`,
90105
`Total: ${invoice?.total}`,
91106
`Status: ${invoice?.status}`,
92107
deepLink ? `Link to view: ${deepLink}` : null,

0 commit comments

Comments
 (0)