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
73 changes: 73 additions & 0 deletions src/commands/accounts/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {Flags} from '@oclif/core'
import {BaseCommand} from '../../base-command.js'
import {accountCreateSchema, formatZodError} from '../../lib/validators.js'
import type {Account} from 'xero-node'

export default class AccountsCreate extends BaseCommand {
static override description = 'Create an account in Xero'

static override examples = [
'<%= config.bin %> accounts create --name "Travel Expenses" --code 420 --type EXPENSE',
'<%= config.bin %> accounts create --file account.json',
]

static override flags = {
...BaseCommand.baseFlags,
file: Flags.string({description: 'JSON file with account data'}),
name: Flags.string({description: 'Account name'}),
code: Flags.string({description: 'Account code'}),
type: Flags.string({description: 'Account type (BANK, CURRENT, CURRLIAB, DEPRECIATN, DIRECTCOSTS, EQUITY, EXPENSE, FIXED, INVENTORY, LIABILITY, NONCURRENT, OTHERINCOME, OVERHEADS, PREPAYMENT, REVENUE, SALES, TERMLIAB, PAYG)'}),
description: Flags.string({description: 'Account description'}),
'tax-type': Flags.string({description: 'Tax type'}),
'enable-payments-to-account': Flags.boolean({description: 'Enable payments to account', allowNo: true}),
'show-in-expense-claims': Flags.boolean({description: 'Show in expense claims', allowNo: true}),
'add-to-watchlist': Flags.boolean({description: 'Show on the Xero dashboard watchlist', allowNo: true}),
}

async run(): Promise<void> {
const {flags} = await this.parse(AccountsCreate)

let data: Record<string, unknown>
if (flags.file) {
data = this.readJsonFile(flags.file) as Record<string, unknown>
} else {
data = {
name: flags.name,
code: flags.code,
type: flags.type,
description: flags.description,
taxType: flags['tax-type'],
enablePaymentsToAccount: flags['enable-payments-to-account'],
showInExpenseClaims: flags['show-in-expense-claims'],
addToWatchlist: flags['add-to-watchlist'],
}
}

const parsed = accountCreateSchema.safeParse(data)
if (!parsed.success) {
this.error(`Validation errors:\n${formatZodError(parsed.error)}`)
}

const result = await this.xeroCall(flags, async (xero, tenantId) => {
const account: Account = {
name: parsed.data.name,
code: parsed.data.code,
type: parsed.data.type as unknown as Account['type'],
description: parsed.data.description,
taxType: parsed.data.taxType,
enablePaymentsToAccount: parsed.data.enablePaymentsToAccount,
showInExpenseClaims: parsed.data.showInExpenseClaims,
addToWatchlist: parsed.data.addToWatchlist,
}
const response = await xero.accountingApi.createAccount(tenantId, account)
return response.body.accounts?.[0]
})

if (flags.json) {
this.log(JSON.stringify(result, null, 2))
} else {
const r = result as Record<string, unknown> | undefined
this.log(`Account created: ${r?.name} (${r?.accountID})`)
}
}
}
2 changes: 2 additions & 0 deletions src/commands/accounts/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export default class AccountsList extends BaseCommand {
{key: 'code', header: 'Code'},
{key: 'name', header: 'Name'},
{key: 'type', header: 'Type'},
{key: 'taxType', header: 'Tax Type'},
{key: 'description', header: 'Description'},
{key: 'status', header: 'Status', format: (v) => formatStatus(String(v ?? ''))},
{key: '_class', header: 'Class'},
],
Expand Down
6 changes: 6 additions & 0 deletions src/commands/accounts/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export default class AccountsUpdate extends BaseCommand {
status: Flags.string({description: 'Account status (ACTIVE or ARCHIVED)'}),
'tax-type': Flags.string({description: 'Tax type'}),
'enable-payments-to-account': Flags.boolean({description: 'Enable payments to account', allowNo: true}),
'show-in-expense-claims': Flags.boolean({description: 'Show in expense claims', allowNo: true}),
'add-to-watchlist': Flags.boolean({description: 'Show on the Xero dashboard watchlist', allowNo: true}),
}

async run(): Promise<void> {
Expand Down Expand Up @@ -55,6 +57,8 @@ export default class AccountsUpdate extends BaseCommand {
status: flags.status,
taxType: flags['tax-type'],
enablePaymentsToAccount: flags['enable-payments-to-account'],
showInExpenseClaims: flags['show-in-expense-claims'],
addToWatchlist: flags['add-to-watchlist'],
}

const parsed = accountUpdateSchema.safeParse(data)
Expand All @@ -71,6 +75,8 @@ export default class AccountsUpdate extends BaseCommand {
status: parsed.data.status as Account['status'],
taxType: parsed.data.taxType,
enablePaymentsToAccount: parsed.data.enablePaymentsToAccount,
showInExpenseClaims: parsed.data.showInExpenseClaims,
addToWatchlist: parsed.data.addToWatchlist,
}
const response = await xero.accountingApi.updateAccount(tenantId, parsed.data.accountId, {accounts: [account]})
return response.body.accounts?.[0]
Expand Down
15 changes: 15 additions & 0 deletions src/lib/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,19 @@ export const itemUpdateSchema = itemCreateSchema.extend({
itemId: z.string().min(1, 'Item ID is required'),
})

const accountTypes = ['BANK', 'CURRENT', 'CURRLIAB', 'DEPRECIATN', 'DIRECTCOSTS', 'EQUITY', 'EXPENSE', 'FIXED', 'INVENTORY', 'LIABILITY', 'NONCURRENT', 'OTHERINCOME', 'OVERHEADS', 'PREPAYMENT', 'REVENUE', 'SALES', 'TERMLIAB', 'PAYG'] as const

export const accountCreateSchema = z.object({
name: z.string().min(1, 'Name is required'),
code: z.string().min(1, 'Code is required'),
type: z.enum([...accountTypes], {message: `Type must be one of: ${accountTypes.join(', ')}`}),
description: z.string().optional(),
taxType: z.string().optional(),
enablePaymentsToAccount: z.boolean().optional(),
showInExpenseClaims: z.boolean().optional(),
addToWatchlist: z.boolean().optional(),
})

export const accountUpdateSchema = z.object({
accountId: z.string().min(1, 'Account ID is required'),
name: z.string().min(1).optional(),
Expand All @@ -179,6 +192,8 @@ export const accountUpdateSchema = z.object({
status: z.enum(['ACTIVE', 'ARCHIVED']).optional(),
taxType: z.string().optional(),
enablePaymentsToAccount: z.boolean().optional(),
showInExpenseClaims: z.boolean().optional(),
addToWatchlist: z.boolean().optional(),
})

export const trackingCategoryCreateSchema = z.object({
Expand Down