You've already forked double-entry-accounting
Initial project
This commit is contained in:
161
accounting/api.py
Normal file
161
accounting/api.py
Normal file
@@ -0,0 +1,161 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user