diff --git a/src/commands/accounts/create.ts b/src/commands/accounts/create.ts new file mode 100644 index 0000000..2044da5 --- /dev/null +++ b/src/commands/accounts/create.ts @@ -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 { + const {flags} = await this.parse(AccountsCreate) + + let data: Record + if (flags.file) { + data = this.readJsonFile(flags.file) as Record + } 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 | undefined + this.log(`Account created: ${r?.name} (${r?.accountID})`) + } + } +} diff --git a/src/commands/accounts/list.ts b/src/commands/accounts/list.ts index 0e26c5a..95d90e0 100644 --- a/src/commands/accounts/list.ts +++ b/src/commands/accounts/list.ts @@ -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'}, ], diff --git a/src/commands/accounts/update.ts b/src/commands/accounts/update.ts index 0fbdd72..74d07ab 100644 --- a/src/commands/accounts/update.ts +++ b/src/commands/accounts/update.ts @@ -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 { @@ -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) @@ -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] diff --git a/src/lib/validators.ts b/src/lib/validators.ts index b2db45c..3c44a35 100644 --- a/src/lib/validators.ts +++ b/src/lib/validators.ts @@ -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(), @@ -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({