aee0f09db8
- Datafabrik: Dockerfile fix, agentorkestrering fungerar - Vision: Identify-modell, FAISS, OCR alla testade - API: Alla 7 integrationstester passerade - Upplösare: Entitetsupplösning verifierad
284 lines
9.9 KiB
Python
284 lines
9.9 KiB
Python
"""
|
|
Economy/Ledger Management Module
|
|
Handles accounts, transactions, bank accounts, and financial reporting
|
|
"""
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy.orm import Session
|
|
from typing import List, Optional
|
|
from datetime import datetime, date
|
|
from decimal import Decimal
|
|
|
|
from app.database import get_db
|
|
from app.models import Account, Transaction, BankAccount, AccountType, User
|
|
from app.core.security import get_current_user
|
|
|
|
router = APIRouter(prefix="/economy", tags=["economy"])
|
|
|
|
# ─── ACCOUNTS ─────────────────────────────────────────────────────────────────
|
|
|
|
@router.get("/accounts")
|
|
async def list_accounts(
|
|
account_type: Optional[AccountType] = None,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""List all accounts with optional filtering."""
|
|
query = db.query(Account)
|
|
if account_type:
|
|
query = query.filter(Account.account_type == account_type)
|
|
accounts = query.all()
|
|
return {
|
|
"accounts": [
|
|
{
|
|
"id": a.id,
|
|
"account_number": a.account_number,
|
|
"name": a.name,
|
|
"type": a.account_type.value,
|
|
"balance": float(a.balance) if a.balance else 0,
|
|
"currency": a.currency,
|
|
"is_active": a.is_active,
|
|
}
|
|
for a in accounts
|
|
],
|
|
"total": query.count(),
|
|
}
|
|
|
|
@router.post("/accounts")
|
|
async def create_account(
|
|
account_number: str,
|
|
name: str,
|
|
account_type: AccountType,
|
|
parent_id: Optional[int] = None,
|
|
description: Optional[str] = None,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""Create a new account."""
|
|
account = Account(
|
|
account_number=account_number,
|
|
name=name,
|
|
account_type=account_type,
|
|
parent_id=parent_id,
|
|
description=description,
|
|
)
|
|
db.add(account)
|
|
db.commit()
|
|
db.refresh(account)
|
|
return {"status": "created", "account_id": account.id}
|
|
|
|
@router.get("/accounts/{account_id}")
|
|
async def get_account(
|
|
account_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""Get account details."""
|
|
account = db.query(Account).filter(Account.id == account_id).first()
|
|
if not account:
|
|
raise HTTPException(status_code=404, detail="Account not found")
|
|
return {
|
|
"id": account.id,
|
|
"account_number": account.account_number,
|
|
"name": account.name,
|
|
"type": account.account_type.value,
|
|
"balance": float(account.balance) if account.balance else 0,
|
|
"currency": account.currency,
|
|
"description": account.description,
|
|
"is_active": account.is_active,
|
|
}
|
|
|
|
# ─── TRANSACTIONS ─────────────────────────────────────────────────────────────
|
|
|
|
@router.get("/transactions")
|
|
async def list_transactions(
|
|
account_id: Optional[int] = None,
|
|
start_date: Optional[date] = None,
|
|
end_date: Optional[date] = None,
|
|
skip: int = 0,
|
|
limit: int = 100,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""List transactions with filtering."""
|
|
query = db.query(Transaction)
|
|
if account_id:
|
|
query = query.filter(
|
|
(Transaction.debit_account_id == account_id) |
|
|
(Transaction.credit_account_id == account_id)
|
|
)
|
|
if start_date:
|
|
query = query.filter(Transaction.date >= start_date)
|
|
if end_date:
|
|
query = query.filter(Transaction.date <= end_date)
|
|
|
|
transactions = query.order_by(Transaction.date.desc()).offset(skip).limit(limit).all()
|
|
return {
|
|
"transactions": [
|
|
{
|
|
"id": t.id,
|
|
"transaction_id": t.transaction_id,
|
|
"date": t.date.isoformat() if t.date else None,
|
|
"description": t.description,
|
|
"amount": float(t.amount) if t.amount else 0,
|
|
"debit_account_id": t.debit_account_id,
|
|
"credit_account_id": t.credit_account_id,
|
|
"status": t.status,
|
|
"reference": t.reference,
|
|
}
|
|
for t in transactions
|
|
],
|
|
"total": query.count(),
|
|
}
|
|
|
|
@router.post("/transactions")
|
|
async def create_transaction(
|
|
transaction_id: str,
|
|
date: datetime,
|
|
description: str,
|
|
amount: float,
|
|
debit_account_id: int,
|
|
credit_account_id: int,
|
|
reference: Optional[str] = None,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""Create a new transaction (double-entry bookkeeping)."""
|
|
# Verify accounts exist
|
|
debit_account = db.query(Account).filter(Account.id == debit_account_id).first()
|
|
credit_account = db.query(Account).filter(Account.id == credit_account_id).first()
|
|
|
|
if not debit_account or not credit_account:
|
|
raise HTTPException(status_code=404, detail="One or both accounts not found")
|
|
|
|
transaction = Transaction(
|
|
transaction_id=transaction_id,
|
|
date=date,
|
|
description=description,
|
|
amount=Decimal(str(amount)),
|
|
debit_account_id=debit_account_id,
|
|
credit_account_id=credit_account_id,
|
|
reference=reference,
|
|
created_by=current_user.id,
|
|
)
|
|
|
|
# Update account balances
|
|
debit_account.balance += Decimal(str(amount))
|
|
credit_account.balance -= Decimal(str(amount))
|
|
|
|
db.add(transaction)
|
|
db.commit()
|
|
db.refresh(transaction)
|
|
return {"status": "created", "transaction_id": transaction.id}
|
|
|
|
# ─── BANK ACCOUNTS ────────────────────────────────────────────────────────────
|
|
|
|
@router.get("/bank-accounts")
|
|
async def list_bank_accounts(
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""List all bank accounts."""
|
|
accounts = db.query(BankAccount).all()
|
|
return {
|
|
"accounts": [
|
|
{
|
|
"id": a.id,
|
|
"bank_name": a.bank_name,
|
|
"account_name": a.account_name,
|
|
"account_number": a.account_number,
|
|
"iban": a.iban,
|
|
"bic": a.bic,
|
|
"currency": a.currency,
|
|
"current_balance": float(a.current_balance) if a.current_balance else 0,
|
|
"account_type": a.account_type,
|
|
"is_active": a.is_active,
|
|
}
|
|
for a in accounts
|
|
]
|
|
}
|
|
|
|
@router.post("/bank-accounts")
|
|
async def create_bank_account(
|
|
bank_name: str,
|
|
account_name: str,
|
|
account_number: Optional[str] = None,
|
|
iban: Optional[str] = None,
|
|
bic: Optional[str] = None,
|
|
currency: str = "SEK",
|
|
account_type: Optional[str] = None,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""Create a new bank account."""
|
|
account = BankAccount(
|
|
bank_name=bank_name,
|
|
account_name=account_name,
|
|
account_number=account_number,
|
|
iban=iban,
|
|
bic=bic,
|
|
currency=currency,
|
|
account_type=account_type,
|
|
)
|
|
db.add(account)
|
|
db.commit()
|
|
db.refresh(account)
|
|
return {"status": "created", "bank_account_id": account.id}
|
|
|
|
# ─── REPORTS ──────────────────────────────────────────────────────────────────
|
|
|
|
@router.get("/reports/balance-sheet")
|
|
async def get_balance_sheet(
|
|
as_of: Optional[date] = None,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""Generate balance sheet report."""
|
|
assets = db.query(Account).filter(Account.account_type == AccountType.ASSET).all()
|
|
liabilities = db.query(Account).filter(Account.account_type == AccountType.LIABILITY).all()
|
|
equity = db.query(Account).filter(Account.account_type == AccountType.EQUITY).all()
|
|
|
|
return {
|
|
"report_type": "Balance Sheet",
|
|
"as_of": as_of.isoformat() if as_of else datetime.utcnow().date().isoformat(),
|
|
"assets": {
|
|
"total": sum(float(a.balance) for a in assets),
|
|
"accounts": [{"name": a.name, "balance": float(a.balance)} for a in assets]
|
|
},
|
|
"liabilities": {
|
|
"total": sum(float(l.balance) for l in liabilities),
|
|
"accounts": [{"name": l.name, "balance": float(l.balance)} for l in liabilities]
|
|
},
|
|
"equity": {
|
|
"total": sum(float(e.balance) for e in equity),
|
|
"accounts": [{"name": e.name, "balance": float(e.balance)} for e in equity]
|
|
},
|
|
}
|
|
|
|
@router.get("/reports/income-statement")
|
|
async def get_income_statement(
|
|
start_date: date,
|
|
end_date: date,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_user),
|
|
):
|
|
"""Generate income statement (resultaträkning)."""
|
|
revenues = db.query(Account).filter(Account.account_type == AccountType.REVENUE).all()
|
|
expenses = db.query(Account).filter(Account.account_type == AccountType.EXPENSE).all()
|
|
|
|
total_revenue = sum(float(r.balance) for r in revenues)
|
|
total_expenses = sum(float(e.balance) for e in expenses)
|
|
|
|
return {
|
|
"report_type": "Income Statement",
|
|
"period": {"start": start_date.isoformat(), "end": end_date.isoformat()},
|
|
"revenue": {
|
|
"total": total_revenue,
|
|
"accounts": [{"name": r.name, "balance": float(r.balance)} for r in revenues]
|
|
},
|
|
"expenses": {
|
|
"total": total_expenses,
|
|
"accounts": [{"name": e.name, "balance": float(e.balance)} for e in expenses]
|
|
},
|
|
"net_income": total_revenue - total_expenses,
|
|
}
|