Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/consts/deeplinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,7 @@ export const manualJournalDeepLink = (journalId: string) => {
export const billDeepLink = (orgShortCode: string, billId: string) => {
return `https://go.xero.com/organisationlogin/default.aspx?shortcode=${orgShortCode}&redirecturl=/AccountsPayable/Edit.aspx?InvoiceID=${billId}`;
};

export const accountDeepLink = (orgShortCode: string, accountId: string) => {
return `https://go.xero.com/app/${orgShortCode}/accounts/settings/${accountId}`;
};
53 changes: 53 additions & 0 deletions src/handlers/archive-xero-account.handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { xeroClient } from "../clients/xero-client.js";
import { XeroClientResponse } from "../types/tool-response.js";
import { formatError } from "../helpers/format-error.js";
import { Account, Accounts } from "xero-node";
import { getClientHeaders } from "../helpers/get-client-headers.js";

async function archiveAccount(
accountId: string,
): Promise<Account | undefined> {
await xeroClient.authenticate();

const account: Account = {
status: Account.StatusEnum.ARCHIVED,
};

const accounts: Accounts = {
accounts: [account],
};

const response = await xeroClient.accountingApi.updateAccount(
xeroClient.tenantId,
accountId,
accounts,
undefined, // idempotencyKey
getClientHeaders(),
);

return response.body.accounts?.[0];
}

export async function archiveXeroAccount(
accountId: string,
): Promise<XeroClientResponse<Account>> {
try {
const archivedAccount = await archiveAccount(accountId);

if (!archivedAccount) {
throw new Error("Account archival failed.");
}

return {
result: archivedAccount,
isError: false,
error: null,
};
} catch (error) {
return {
result: null,
isError: true,
error: formatError(error),
};
}
}
70 changes: 70 additions & 0 deletions src/handlers/create-xero-account.handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { xeroClient } from "../clients/xero-client.js";
import { XeroClientResponse } from "../types/tool-response.js";
import { formatError } from "../helpers/format-error.js";
import { Account, AccountType } from "xero-node";
import { getClientHeaders } from "../helpers/get-client-headers.js";

async function createAccount(
name: string,
code: string,
type: AccountType,
description?: string,
taxType?: string,
bankAccountNumber?: string,
): Promise<Account | undefined> {
await xeroClient.authenticate();

const account: Account = {
name,
code,
type,
description,
taxType,
bankAccountNumber,
};

const response = await xeroClient.accountingApi.createAccount(
xeroClient.tenantId,
account,
undefined, // idempotencyKey
getClientHeaders(),
);

return response.body.accounts?.[0];
}

export async function createXeroAccount(
name: string,
code: string,
type: AccountType,
description?: string,
taxType?: string,
bankAccountNumber?: string,
): Promise<XeroClientResponse<Account>> {
try {
const createdAccount = await createAccount(
name,
code,
type,
description,
taxType,
bankAccountNumber,
);

if (!createdAccount) {
throw new Error("Account creation failed.");
}

return {
result: createdAccount,
isError: false,
error: null,
};
} catch (error) {
return {
result: null,
isError: true,
error: formatError(error),
};
}
}
98 changes: 98 additions & 0 deletions src/handlers/update-xero-account.handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { xeroClient } from "../clients/xero-client.js";
import { XeroClientResponse } from "../types/tool-response.js";
import { formatError } from "../helpers/format-error.js";
import { Account, Accounts, AccountType, CurrencyCode } from "xero-node";
import { getClientHeaders } from "../helpers/get-client-headers.js";

async function updateAccount(
accountId: string,
name?: string,
code?: string,
type?: AccountType,
description?: string,
taxType?: string,
currencyCode?: string,
enablePaymentsToAccount?: boolean,
showInExpenseClaims?: boolean,
reportingCode?: string,
reportingCodeName?: string,
addToWatchlist?: boolean,
): Promise<Account | undefined> {
await xeroClient.authenticate();

const account: Account = {
name,
code,
type,
description,
taxType,
currencyCode: currencyCode as unknown as CurrencyCode,
enablePaymentsToAccount,
showInExpenseClaims,
reportingCode,
reportingCodeName,
addToWatchlist,
};

const accounts: Accounts = {
accounts: [account],
};

const response = await xeroClient.accountingApi.updateAccount(
xeroClient.tenantId,
accountId,
accounts,
undefined, // idempotencyKey
getClientHeaders(),
);

return response.body.accounts?.[0];
}

export async function updateXeroAccount(
accountId: string,
name?: string,
code?: string,
type?: AccountType,
description?: string,
taxType?: string,
currencyCode?: string,
enablePaymentsToAccount?: boolean,
showInExpenseClaims?: boolean,
reportingCode?: string,
reportingCodeName?: string,
addToWatchlist?: boolean,
): Promise<XeroClientResponse<Account>> {
try {
const updatedAccount = await updateAccount(
accountId,
name,
code,
type,
description,
taxType,
currencyCode,
enablePaymentsToAccount,
showInExpenseClaims,
reportingCode,
reportingCodeName,
addToWatchlist,
);

if (!updatedAccount) {
throw new Error("Account update failed.");
}

return {
result: updatedAccount,
isError: false,
error: null,
};
} catch (error) {
return {
result: null,
isError: true,
error: formatError(error),
};
}
}
4 changes: 4 additions & 0 deletions src/helpers/get-deeplink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
manualJournalDeepLink,
quoteDeepLink,
billDeepLink,
accountDeepLink,
} from "../consts/deeplinks.js";

export enum DeepLinkType {
Expand All @@ -17,6 +18,7 @@ export enum DeepLinkType {
QUOTE,
PAYMENT,
BILL,
ACCOUNT,
}

/**
Expand Down Expand Up @@ -48,5 +50,7 @@ export const getDeepLink = async (type: DeepLinkType, itemId: string) => {
return paymentDeepLink(orgShortCode, itemId);
case DeepLinkType.BILL:
return billDeepLink(orgShortCode, itemId);
case DeepLinkType.ACCOUNT:
return accountDeepLink(orgShortCode, itemId);
}
};
115 changes: 115 additions & 0 deletions src/tools/create/create-account.tool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { createXeroAccount } from "../../handlers/create-xero-account.handler.js";
import { z } from "zod";
import { DeepLinkType, getDeepLink } from "../../helpers/get-deeplink.js";
import { ensureError } from "../../helpers/ensure-error.js";
import { CreateXeroTool } from "../../helpers/create-xero-tool.js";
import { AccountType } from "xero-node";

const CreateAccountTool = CreateXeroTool(
"create-account",
"Create an account in Xero's chart of accounts.\
When an account is created, a deep link to the account in Xero is returned. \
This deep link can be used to view the account in Xero directly. \
This link should be displayed to the user.",
{
name: z.string().describe("Name of the account (max 150 chars)"),
code: z.string().describe("A unique alphanumeric account code (max 10 chars)"),
type: z
.enum([
"BANK",
"CURRENT",
"CURRLIAB",
"DEPRECIATN",
"DIRECTCOSTS",
"EQUITY",
"EXPENSE",
"FIXED",
"INVENTORY",
"LIABILITY",
"NONCURRENT",
"OTHERINCOME",
"OVERHEADS",
"PREPAYMENT",
"REVENUE",
"SALES",
"TERMLIAB",
"PAYG",
])
.describe("The account type"),
description: z
.string()
.optional()
.describe(
"Description of the account (max 4000 chars, not valid for bank accounts)",
),
taxType: z
.string()
.optional()
.describe("The tax type for the account"),
bankAccountNumber: z
.string()
.optional()
.describe("Bank account number (only for BANK type accounts)"),
},
async ({ name, code, type, description, taxType, bankAccountNumber }) => {
try {
const response = await createXeroAccount(
name,
code,
type as unknown as AccountType,
description,
taxType,
bankAccountNumber,
);

if (response.isError) {
return {
content: [
{
type: "text" as const,
text: `Error creating account: ${response.error}`,
},
],
};
}

const account = response.result;

const deepLink = account.accountID
? await getDeepLink(DeepLinkType.ACCOUNT, account.accountID)
: null;

return {
content: [
{
type: "text" as const,
text: [
"Account created successfully:",
`Name: ${account.name}`,
`Code: ${account.code}`,
`ID: ${account.accountID}`,
`Type: ${account.type}`,
`Status: ${account.status}`,
deepLink ? `Link to view: ${deepLink}` : null,
]
.filter(Boolean)
.join("\n"),
},
],
};
} catch (error) {
const err = ensureError(error);

return {
content: [
{
type: "text" as const,
text: `Error creating account: ${err.message}`,
},
],
};
}
},
);

export default CreateAccountTool;
2 changes: 2 additions & 0 deletions src/tools/create/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import CreateAccountTool from "./create-account.tool.js";
import CreateBankTransactionTool from "./create-bank-transaction.tool.js";
import CreateContactTool from "./create-contact.tool.js";
import CreateCreditNoteTool from "./create-credit-note.tool.js";
Expand All @@ -11,6 +12,7 @@ import CreateTrackingCategoryTool from "./create-tracking-category.tool.js";
import CreateTrackingOptionsTool from "./create-tracking-options.tool.js";

export const CreateTools = [
CreateAccountTool,
CreateContactTool,
CreateCreditNoteTool,
CreateManualJournalTool,
Expand Down
Loading