280 lines
9.4 KiB
Python
280 lines
9.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
QBO Account ID Lookup Script
|
|
============================
|
|
This script connects to QuickBooks Online and retrieves all accounts
|
|
with their IDs, names, types, and other details.
|
|
|
|
Usage:
|
|
python get_account_ids.py [--type TYPE] [--search NAME] [--export FILE]
|
|
|
|
Examples:
|
|
python get_account_ids.py # List all accounts
|
|
python get_account_ids.py --type Bank # List only Bank accounts
|
|
python get_account_ids.py --search Checking # Search by name
|
|
python get_account_ids.py --export accounts.csv # Export to CSV
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import argparse
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
# Add parent directory to path to import from src
|
|
script_dir = Path(__file__).parent
|
|
sys.path.insert(0, str(script_dir))
|
|
|
|
from src.config.settings import Settings
|
|
from src.api.qbo_client import QBOClient
|
|
|
|
|
|
def get_accounts(client, account_type=None, search_term=None):
|
|
"""
|
|
Fetch accounts from QBO with optional filtering.
|
|
|
|
Args:
|
|
client: QBOClient instance
|
|
account_type: Filter by account type (Bank, Expense, Income, etc.)
|
|
search_term: Search accounts by name
|
|
|
|
Returns:
|
|
List of account dictionaries
|
|
"""
|
|
# Build query
|
|
query = "SELECT * FROM Account"
|
|
conditions = []
|
|
|
|
if account_type:
|
|
conditions.append(f"AccountType = '{account_type}'")
|
|
|
|
if search_term:
|
|
conditions.append(f"Name LIKE '%{search_term}%'")
|
|
|
|
if conditions:
|
|
query += " WHERE " + " AND ".join(conditions)
|
|
|
|
query += " ORDERBY Name"
|
|
|
|
# Execute query
|
|
response = client._make_request('GET', 'query', params={'query': query})
|
|
|
|
if response and 'QueryResponse' in response:
|
|
return response['QueryResponse'].get('Account', [])
|
|
|
|
return []
|
|
|
|
|
|
def format_account_table(accounts):
|
|
"""Format accounts as a readable table."""
|
|
if not accounts:
|
|
return "No accounts found."
|
|
|
|
# Define columns
|
|
headers = ['ID', 'Name', 'Type', 'SubType', 'AcctNum', 'Balance', 'Active']
|
|
|
|
# Calculate column widths
|
|
widths = {
|
|
'ID': max(len('ID'), max(len(str(a.get('Id', ''))) for a in accounts)),
|
|
'Name': max(len('Name'), min(40, max(len(str(a.get('Name', ''))) for a in accounts))),
|
|
'Type': max(len('Type'), max(len(str(a.get('AccountType', ''))) for a in accounts)),
|
|
'SubType': max(len('SubType'), max(len(str(a.get('AccountSubType', ''))) for a in accounts)),
|
|
'AcctNum': max(len('AcctNum'), max(len(str(a.get('AcctNum', ''))) for a in accounts)),
|
|
'Balance': max(len('Balance'), 12),
|
|
'Active': max(len('Active'), 6),
|
|
}
|
|
|
|
# Build header
|
|
header_row = " | ".join(h.ljust(widths[h]) for h in headers)
|
|
separator = "-+-".join("-" * widths[h] for h in headers)
|
|
|
|
lines = [header_row, separator]
|
|
|
|
# Build data rows
|
|
for account in accounts:
|
|
name = str(account.get('Name', ''))[:40]
|
|
balance = account.get('CurrentBalance', 0)
|
|
balance_str = f"${balance:,.2f}" if balance else "-"
|
|
|
|
row = [
|
|
str(account.get('Id', '')).ljust(widths['ID']),
|
|
name.ljust(widths['Name']),
|
|
str(account.get('AccountType', '')).ljust(widths['Type']),
|
|
str(account.get('AccountSubType', '')).ljust(widths['SubType']),
|
|
str(account.get('AcctNum', '')).ljust(widths['AcctNum']),
|
|
balance_str.rjust(widths['Balance']),
|
|
('Yes' if account.get('Active', True) else 'No').ljust(widths['Active']),
|
|
]
|
|
lines.append(" | ".join(row))
|
|
|
|
return "\n".join(lines)
|
|
|
|
|
|
def export_to_csv(accounts, filepath):
|
|
"""Export accounts to CSV file."""
|
|
import csv
|
|
|
|
headers = ['Id', 'Name', 'FullyQualifiedName', 'AccountType', 'AccountSubType',
|
|
'AcctNum', 'CurrentBalance', 'Active', 'Classification', 'Description']
|
|
|
|
with open(filepath, 'w', newline='', encoding='utf-8') as f:
|
|
writer = csv.DictWriter(f, fieldnames=headers, extrasaction='ignore')
|
|
writer.writeheader()
|
|
for account in accounts:
|
|
writer.writerow(account)
|
|
|
|
print(f"Exported {len(accounts)} accounts to {filepath}")
|
|
|
|
|
|
def export_to_json(accounts, filepath):
|
|
"""Export accounts to JSON file."""
|
|
with open(filepath, 'w', encoding='utf-8') as f:
|
|
json.dump(accounts, f, indent=2)
|
|
|
|
print(f"Exported {len(accounts)} accounts to {filepath}")
|
|
|
|
|
|
def print_account_types(accounts):
|
|
"""Print summary of account types."""
|
|
type_counts = {}
|
|
for account in accounts:
|
|
acct_type = account.get('AccountType', 'Unknown')
|
|
type_counts[acct_type] = type_counts.get(acct_type, 0) + 1
|
|
|
|
print("\nAccount Types Summary:")
|
|
print("-" * 30)
|
|
for acct_type, count in sorted(type_counts.items()):
|
|
print(f" {acct_type}: {count}")
|
|
print("-" * 30)
|
|
print(f" Total: {len(accounts)}")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description='Fetch Account IDs from QuickBooks Online',
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog="""
|
|
Account Types:
|
|
Bank - Bank accounts (checking, savings)
|
|
Accounts Receivable - Customer receivables
|
|
Other Current Asset - Other current assets
|
|
Fixed Asset - Property, equipment, etc.
|
|
Other Asset - Other long-term assets
|
|
Accounts Payable - Vendor payables
|
|
Credit Card - Credit card accounts
|
|
Other Current Liability - Other current liabilities
|
|
Long Term Liability - Long-term debt
|
|
Equity - Owner's equity accounts
|
|
Income - Revenue accounts
|
|
Cost of Goods Sold - COGS accounts
|
|
Expense - Expense accounts
|
|
Other Income - Other income
|
|
Other Expense - Other expenses
|
|
|
|
Examples:
|
|
%(prog)s --type Bank
|
|
%(prog)s --type Expense --search Office
|
|
%(prog)s --export accounts.csv
|
|
%(prog)s --type "Accounts Payable"
|
|
"""
|
|
)
|
|
|
|
parser.add_argument('--type', '-t', dest='account_type',
|
|
help='Filter by account type (e.g., Bank, Expense, Income)')
|
|
parser.add_argument('--search', '-s', dest='search_term',
|
|
help='Search accounts by name')
|
|
parser.add_argument('--export', '-e', dest='export_file',
|
|
help='Export to file (CSV or JSON based on extension)')
|
|
parser.add_argument('--summary', action='store_true',
|
|
help='Show account type summary')
|
|
parser.add_argument('--json', action='store_true',
|
|
help='Output as JSON')
|
|
parser.add_argument('--verbose', '-v', action='store_true',
|
|
help='Show detailed account information')
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Initialize settings and client
|
|
print("=" * 60)
|
|
print("QBO Account ID Lookup")
|
|
print("=" * 60)
|
|
|
|
try:
|
|
settings = Settings()
|
|
credentials = settings.get_credentials()
|
|
|
|
if not credentials.access_token:
|
|
print("\nError: Not connected to QuickBooks Online.")
|
|
print("Please connect via the web application first.")
|
|
sys.exit(1)
|
|
|
|
client = QBOClient(credentials)
|
|
|
|
if not client.is_authenticated:
|
|
print("\nError: Authentication failed or token expired.")
|
|
print("Please reconnect via the web application.")
|
|
sys.exit(1)
|
|
|
|
# Get company info
|
|
company_info = client.get_company_info()
|
|
print(f"\nConnected to: {company_info.get('CompanyName', 'Unknown')}")
|
|
print(f"Environment: {credentials.environment}")
|
|
print("-" * 60)
|
|
|
|
# Fetch accounts
|
|
print(f"\nFetching accounts...")
|
|
if args.account_type:
|
|
print(f" Filter by type: {args.account_type}")
|
|
if args.search_term:
|
|
print(f" Search term: {args.search_term}")
|
|
|
|
accounts = get_accounts(client, args.account_type, args.search_term)
|
|
|
|
print(f"\nFound {len(accounts)} account(s)")
|
|
print("-" * 60)
|
|
|
|
if not accounts:
|
|
print("No accounts match your criteria.")
|
|
sys.exit(0)
|
|
|
|
# Output results
|
|
if args.json:
|
|
print(json.dumps(accounts, indent=2))
|
|
elif args.verbose:
|
|
for i, account in enumerate(accounts, 1):
|
|
print(f"\n[{i}] {account.get('Name')}")
|
|
print(f" ID: {account.get('Id')}")
|
|
print(f" Type: {account.get('AccountType')} / {account.get('AccountSubType', '-')}")
|
|
print(f" Number: {account.get('AcctNum', '-')}")
|
|
print(f" Balance: ${account.get('CurrentBalance', 0):,.2f}")
|
|
print(f" Active: {account.get('Active', True)}")
|
|
if account.get('Description'):
|
|
print(f" Description: {account.get('Description')}")
|
|
else:
|
|
print(format_account_table(accounts))
|
|
|
|
# Show summary if requested
|
|
if args.summary:
|
|
print_account_types(accounts)
|
|
|
|
# Export if requested
|
|
if args.export_file:
|
|
filepath = args.export_file
|
|
if filepath.endswith('.json'):
|
|
export_to_json(accounts, filepath)
|
|
else:
|
|
export_to_csv(accounts, filepath)
|
|
|
|
print("\n" + "=" * 60)
|
|
print("Done!")
|
|
|
|
except Exception as e:
|
|
print(f"\nError: {str(e)}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main() |