import cherrypy from sqlalchemy.orm import sessionmaker from datetime import datetime from accounting.models import Account, BankAccount, BankTransaction, JournalEntry, JournalEntryLine, ReconciliationReport, \ ReconciliationMatch import simplejson as json class AccountingAPI: def __init__(self, db_engine): self.db_engine = db_engine Session = sessionmaker(bind=db_engine) self.session = Session() @cherrypy.expose @cherrypy.tools.json_out() def index(self): return {"status": "success", "message": "Accounting API is running"} # Bank Accounts Endpoints @cherrypy.expose @cherrypy.tools.json_out() def bank_accounts(self): if cherrypy.request.method != 'GET': raise cherrypy.HTTPError(405) accounts = self.session.query(BankAccount).all() return [{ 'id': a.id, 'name': a.name, 'bank_name': a.bank_name, 'account_number': a.account_number, 'currency': a.currency } for a in accounts] @cherrypy.expose @cherrypy.tools.json_in() @cherrypy.tools.json_out() def add_bank_account(self): if cherrypy.request.method != 'POST': raise cherrypy.HTTPError(405) data = cherrypy.request.json account = BankAccount( name=data['name'], bank_name=data['bank_name'], account_number=data['account_number'], currency=data.get('currency', 'USD') ) self.session.add(account) self.session.commit() return {'status': 'success', 'id': account.id} # Add OPTIONS method handler for CORS preflight @cherrypy.expose def add_bank_account_options(self): cherrypy.response.headers['Allow'] = 'POST, OPTIONS' cherrypy.response.headers['Access-Control-Allow-Headers'] = 'Content-Type' return '' # Accounts Endpoints @cherrypy.expose @cherrypy.tools.json_out() def accounts(self): if cherrypy.request.method != 'GET': raise cherrypy.HTTPError(405) accounts = self.session.query(Account).all() return [{ 'id': a.id, 'name': a.name, 'code': a.code, 'type': a.account_type, 'balance': float(a.balance) if a.balance else 0 } for a in accounts] @cherrypy.expose @cherrypy.tools.json_in() @cherrypy.tools.json_out() def add_account(self): if cherrypy.request.method != 'POST': raise cherrypy.HTTPError(405) data = cherrypy.request.json account = Account( name=data['name'], account_type=data['type'], code=data['code'], parent_id=data.get('parent_id') ) self.session.add(account) self.session.commit() return {'status': 'success', 'id': account.id} # Journal Entries Endpoints @cherrypy.expose @cherrypy.tools.json_in() @cherrypy.tools.json_out() def add_journal_entry(self): if cherrypy.request.method != 'POST': raise cherrypy.HTTPError(405) data = cherrypy.request.json total_debit = sum(line['amount'] for line in data['lines'] if line['is_debit']) total_credit = sum(line['amount'] for line in data['lines'] if not line['is_debit']) if abs(total_debit - total_credit) > 0.01: return {'status': 'error', 'message': 'Debits and credits must balance'} entry = JournalEntry( date=datetime.strptime(data['date'], '%Y-%m-%d').date(), reference=data.get('reference', ''), description=data.get('description', '') ) self.session.add(entry) for line_data in data['lines']: line = JournalEntryLine( journal_entry=entry, account_id=line_data['account_id'], amount=line_data['amount'], is_debit=line_data['is_debit'] ) self.session.add(line) account = self.session.query(Account).get(line_data['account_id']) if line_data['is_debit']: account.balance += line_data['amount'] else: account.balance -= line_data['amount'] self.session.commit() return {'status': 'success', 'id': entry.id} @cherrypy.expose @cherrypy.tools.json_out() def journal_entries(self): if cherrypy.request.method != 'GET': raise cherrypy.HTTPError(405) entries = self.session.query(JournalEntry).all() return [{ 'id': e.id, 'date': e.date.isoformat(), 'description': e.description, 'reference': e.reference, 'lines': [{ 'account_id': l.account_id, 'amount': float(l.amount), 'is_debit': l.is_debit } for l in e.lines] } for e in entries] # Add default OPTIONS handler for all endpoints @cherrypy.expose def default(self, *args, **kwargs): if cherrypy.request.method == 'OPTIONS': cherrypy.response.headers['Allow'] = 'GET, POST, OPTIONS' cherrypy.response.headers['Access-Control-Allow-Headers'] = 'Content-Type' return '' raise cherrypy.HTTPError(404)