hesabixArc/hesabixAPI/adapters/api/v1/accounts.py
2025-10-27 18:47:45 +00:00

200 lines
6 KiB
Python

from typing import List, Dict, Any, Optional
from fastapi import APIRouter, Depends, Request
from sqlalchemy.orm import Session
from pydantic import BaseModel
from adapters.db.session import get_db
from adapters.api.v1.schemas import SuccessResponse
from adapters.api.v1.schema_models.account import AccountTreeNode
from app.core.responses import success_response
from app.core.auth_dependency import get_current_user, AuthContext
from app.core.permissions import require_business_access
from adapters.db.models.account import Account
router = APIRouter(prefix="/accounts", tags=["accounts"])
class SearchAccountsRequest(BaseModel):
"""درخواست جستجوی حساب‌ها"""
take: int = 50
skip: int = 0
search: Optional[str] = None
sort_by: Optional[str] = "code"
sort_desc: bool = False
def _build_tree(nodes: list[Dict[str, Any]]) -> list[AccountTreeNode]:
by_id: dict[int, AccountTreeNode] = {}
roots: list[AccountTreeNode] = []
for n in nodes:
node = AccountTreeNode(
id=n['id'], code=n['code'], name=n['name'], account_type=n.get('account_type'), parent_id=n.get('parent_id')
)
by_id[node.id] = node
for node in list(by_id.values()):
pid = node.parent_id
if pid and pid in by_id:
by_id[pid].children.append(node)
else:
roots.append(node)
return roots
@router.get("/business/{business_id}/tree",
summary="دریافت درخت حساب‌ها برای یک کسب و کار",
description="لیست حساب‌های عمومی و حساب‌های اختصاصی کسب و کار به صورت درختی",
)
@require_business_access("business_id")
def get_accounts_tree(
request: Request,
business_id: int,
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db)
) -> dict:
# دریافت حساب‌های عمومی (business_id IS NULL) و حساب‌های مختص این کسب و کار
rows = db.query(Account).filter(
(Account.business_id == None) | (Account.business_id == business_id) # noqa: E711
).order_by(Account.code.asc()).all()
flat = [
{"id": r.id, "code": r.code, "name": r.name, "account_type": r.account_type, "parent_id": r.parent_id}
for r in rows
]
tree = _build_tree(flat)
return success_response({"items": [n.model_dump() for n in tree]}, request)
@router.get("/business/{business_id}",
summary="دریافت لیست حساب‌ها برای یک کسب و کار",
description="لیست تمام حساب‌های عمومی و حساب‌های اختصاصی کسب و کار (بدون ساختار درختی)",
)
@require_business_access("business_id")
def get_accounts_list(
request: Request,
business_id: int,
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db)
) -> dict:
"""دریافت لیست ساده حساب‌ها"""
rows = db.query(Account).filter(
(Account.business_id == None) | (Account.business_id == business_id) # noqa: E711
).order_by(Account.code.asc()).all()
items = [
{
"id": r.id,
"code": r.code,
"name": r.name,
"account_type": r.account_type,
"parent_id": r.parent_id,
"business_id": r.business_id,
"created_at": r.created_at.isoformat() if r.created_at else None,
"updated_at": r.updated_at.isoformat() if r.updated_at else None,
}
for r in rows
]
return success_response({"items": items}, request)
@router.get("/business/{business_id}/account/{account_id}",
summary="دریافت جزئیات یک حساب خاص",
description="دریافت اطلاعات کامل یک حساب بر اساس ID",
)
@require_business_access("business_id")
def get_account_by_id(
request: Request,
business_id: int,
account_id: int,
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db)
) -> dict:
"""دریافت یک حساب خاص"""
account = db.query(Account).filter(
Account.id == account_id,
(Account.business_id == None) | (Account.business_id == business_id) # noqa: E711
).first()
if not account:
from fastapi import HTTPException
raise HTTPException(status_code=404, detail="حساب یافت نشد")
account_data = {
"id": account.id,
"code": account.code,
"name": account.name,
"account_type": account.account_type,
"parent_id": account.parent_id,
"business_id": account.business_id,
"created_at": account.created_at.isoformat() if account.created_at else None,
"updated_at": account.updated_at.isoformat() if account.updated_at else None,
}
return success_response(account_data, request)
@router.post("/business/{business_id}",
summary="جستجو و فیلتر حساب‌ها",
description="جستجو در حساب‌ها با قابلیت فیلتر، مرتب‌سازی و صفحه‌بندی",
)
@require_business_access("business_id")
def search_accounts(
request: Request,
business_id: int,
search_request: SearchAccountsRequest,
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db)
) -> dict:
"""جستجوی حساب‌ها با فیلتر"""
query = db.query(Account).filter(
(Account.business_id == None) | (Account.business_id == business_id) # noqa: E711
)
# اعمال جستجو
if search_request.search:
search_term = f"%{search_request.search}%"
query = query.filter(
(Account.code.ilike(search_term)) | (Account.name.ilike(search_term))
)
# شمارش کل
total = query.count()
# مرتب‌سازی
if search_request.sort_by == "name":
order_col = Account.name
else:
order_col = Account.code
if search_request.sort_desc:
query = query.order_by(order_col.desc())
else:
query = query.order_by(order_col.asc())
# صفحه‌بندی
query = query.offset(search_request.skip).limit(search_request.take)
rows = query.all()
items = [
{
"id": r.id,
"code": r.code,
"name": r.name,
"account_type": r.account_type,
"parent_id": r.parent_id,
"business_id": r.business_id,
"created_at": r.created_at.isoformat() if r.created_at else None,
"updated_at": r.updated_at.isoformat() if r.updated_at else None,
}
for r in rows
]
return success_response({
"items": items,
"total": total,
"skip": search_request.skip,
"take": search_request.take,
}, request)