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

331 lines
11 KiB
Python

"""
API endpoints برای هزینه و درآمد (Expense & Income)
"""
from typing import Any, Dict
from fastapi import APIRouter, Depends, Request, Body
from sqlalchemy.orm import Session
from adapters.db.session import get_db
from app.core.auth_dependency import get_current_user, AuthContext
from app.core.permissions import require_business_management_dep, require_business_access
from app.core.responses import success_response, format_datetime_fields
from adapters.api.v1.schemas import QueryInfo
from app.services.expense_income_service import (
create_expense_income,
list_expense_income,
get_expense_income,
update_expense_income,
delete_expense_income,
delete_multiple_expense_income,
)
router = APIRouter(tags=["expense-income"])
@router.post(
"/businesses/{business_id}/expense-income/create",
summary="ایجاد سند هزینه یا درآمد",
description="ایجاد سند هزینه/درآمد با چند سطر حساب و چند طرف‌حساب",
)
@require_business_access("business_id")
async def create_expense_income_endpoint(
request: Request,
business_id: int,
body: Dict[str, Any] = Body(...),
db: Session = Depends(get_db),
ctx: AuthContext = Depends(get_current_user),
_: None = Depends(require_business_management_dep),
):
created = create_expense_income(db, business_id, ctx.get_user_id(), body)
return success_response(
data=format_datetime_fields(created, request),
request=request,
message="EXPENSE_INCOME_CREATED",
)
@router.post(
"/businesses/{business_id}/expense-income",
summary="لیست اسناد هزینه/درآمد",
description="دریافت لیست اسناد هزینه/درآمد با جستجو و صفحه‌بندی",
)
@require_business_access("business_id")
async def list_expense_income_endpoint(
request: Request,
business_id: int,
query_info: QueryInfo = Body(...),
db: Session = Depends(get_db),
ctx: AuthContext = Depends(get_current_user),
):
query_dict: Dict[str, Any] = {
"take": query_info.take,
"skip": query_info.skip,
"sort_by": query_info.sort_by,
"sort_desc": query_info.sort_desc,
"search": query_info.search,
}
# Read extra body filters
try:
body_json = await request.json()
if isinstance(body_json, dict):
for key in ["document_type", "from_date", "to_date"]:
if key in body_json:
query_dict[key] = body_json[key]
except Exception:
pass
# Fiscal year from header
try:
fy_header = request.headers.get("X-Fiscal-Year-ID")
if fy_header:
query_dict["fiscal_year_id"] = int(fy_header)
except Exception:
pass
result = list_expense_income(db, business_id, query_dict)
result["items"] = [format_datetime_fields(item, request) for item in result.get("items", [])]
return success_response(data=result, request=request, message="EXPENSE_INCOME_LIST_FETCHED")
@router.get(
"/expense-income/{document_id}",
summary="جزئیات سند هزینه/درآمد",
description="دریافت جزئیات یک سند هزینه یا درآمد",
)
async def get_expense_income_endpoint(
request: Request,
document_id: int,
db: Session = Depends(get_db),
ctx: AuthContext = Depends(get_current_user),
):
"""دریافت جزئیات سند"""
result = get_expense_income(db, document_id)
if not result:
from app.core.responses import ApiError
raise ApiError(
"DOCUMENT_NOT_FOUND",
"Expense/Income document not found",
http_status=404
)
# بررسی دسترسی
business_id = result.get("business_id")
if business_id and not ctx.can_access_business(business_id):
from app.core.responses import ApiError
raise ApiError("FORBIDDEN", "Access denied", http_status=403)
return success_response(
data=format_datetime_fields(result, request),
request=request,
message="EXPENSE_INCOME_DETAILS"
)
@router.put(
"/expense-income/{document_id}",
summary="ویرایش سند هزینه/درآمد",
description="ویرایش یک سند هزینه یا درآمد",
)
async def update_expense_income_endpoint(
request: Request,
document_id: int,
body: Dict[str, Any] = Body(...),
db: Session = Depends(get_db),
ctx: AuthContext = Depends(get_current_user),
_: None = Depends(require_business_management_dep),
):
"""ویرایش سند هزینه/درآمد"""
updated = update_expense_income(db, document_id, ctx.get_user_id(), body)
return success_response(
data=format_datetime_fields(updated, request),
request=request,
message="EXPENSE_INCOME_UPDATED"
)
@router.delete(
"/expense-income/{document_id}",
summary="حذف سند هزینه/درآمد",
description="حذف یک سند هزینه یا درآمد",
)
async def delete_expense_income_endpoint(
request: Request,
document_id: int,
db: Session = Depends(get_db),
ctx: AuthContext = Depends(get_current_user),
_: None = Depends(require_business_management_dep),
):
"""حذف سند هزینه/درآمد"""
success = delete_expense_income(db, document_id)
if not success:
from app.core.responses import ApiError
raise ApiError("DELETE_FAILED", "Failed to delete document", http_status=500)
return success_response(
data={"deleted": True},
request=request,
message="EXPENSE_INCOME_DELETED"
)
@router.post(
"/expense-income/bulk-delete",
summary="حذف گروهی اسناد هزینه/درآمد",
description="حذف چندین سند هزینه یا درآمد",
)
async def delete_multiple_expense_income_endpoint(
request: Request,
body: Dict[str, Any] = Body(...),
db: Session = Depends(get_db),
ctx: AuthContext = Depends(get_current_user),
_: None = Depends(require_business_management_dep),
):
"""حذف گروهی اسناد"""
document_ids = body.get("document_ids", [])
if not document_ids:
from app.core.responses import ApiError
raise ApiError("INVALID_REQUEST", "document_ids is required", http_status=400)
success = delete_multiple_expense_income(db, document_ids)
if not success:
from app.core.responses import ApiError
raise ApiError("DELETE_FAILED", "Failed to delete documents", http_status=500)
return success_response(
data={"deleted_count": len(document_ids)},
request=request,
message="EXPENSE_INCOME_BULK_DELETED"
)
@router.post(
"/businesses/{business_id}/expense-income/export/excel",
summary="خروجی Excel اسناد هزینه/درآمد",
description="دریافت فایل Excel لیست اسناد هزینه/درآمد",
)
@require_business_access("business_id")
async def export_expense_income_excel_endpoint(
request: Request,
business_id: int,
db: Session = Depends(get_db),
ctx: AuthContext = Depends(get_current_user),
):
"""خروجی Excel"""
from app.services.expense_income_service import export_expense_income_excel
from fastapi.responses import Response
# دریافت پارامترهای فیلتر
query_dict = {}
try:
body_json = await request.json()
if isinstance(body_json, dict):
for key in ["document_type", "from_date", "to_date"]:
if key in body_json:
query_dict[key] = body_json[key]
except Exception:
pass
# سال مالی از هدر
try:
fy_header = request.headers.get("X-Fiscal-Year-ID")
if fy_header:
query_dict["fiscal_year_id"] = int(fy_header)
except Exception:
pass
excel_data = export_expense_income_excel(db, business_id, query_dict)
return Response(
content=excel_data,
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
headers={"Content-Disposition": f"attachment; filename=expense_income_{business_id}.xlsx"}
)
@router.post(
"/businesses/{business_id}/expense-income/export/pdf",
summary="خروجی PDF اسناد هزینه/درآمد",
description="دریافت فایل PDF لیست اسناد هزینه/درآمد",
)
@require_business_access("business_id")
async def export_expense_income_pdf_endpoint(
request: Request,
business_id: int,
db: Session = Depends(get_db),
ctx: AuthContext = Depends(get_current_user),
):
"""خروجی PDF"""
from app.services.expense_income_service import export_expense_income_pdf
from fastapi.responses import Response
# دریافت پارامترهای فیلتر
query_dict = {}
try:
body_json = await request.json()
if isinstance(body_json, dict):
for key in ["document_type", "from_date", "to_date"]:
if key in body_json:
query_dict[key] = body_json[key]
except Exception:
pass
# سال مالی از هدر
try:
fy_header = request.headers.get("X-Fiscal-Year-ID")
if fy_header:
query_dict["fiscal_year_id"] = int(fy_header)
except Exception:
pass
pdf_data = export_expense_income_pdf(db, business_id, query_dict)
return Response(
content=pdf_data,
media_type="application/pdf",
headers={"Content-Disposition": f"attachment; filename=expense_income_{business_id}.pdf"}
)
@router.get(
"/expense-income/{document_id}/pdf",
summary="PDF یک سند هزینه/درآمد",
description="دریافت فایل PDF یک سند هزینه یا درآمد",
)
async def get_expense_income_pdf_endpoint(
request: Request,
document_id: int,
db: Session = Depends(get_db),
ctx: AuthContext = Depends(get_current_user),
):
"""PDF یک سند"""
from app.services.expense_income_service import generate_expense_income_pdf
from fastapi.responses import Response
# بررسی دسترسی
doc = get_expense_income(db, document_id)
if not doc:
from app.core.responses import ApiError
raise ApiError("DOCUMENT_NOT_FOUND", "Document not found", http_status=404)
business_id = doc.get("business_id")
if business_id and not ctx.can_access_business(business_id):
from app.core.responses import ApiError
raise ApiError("FORBIDDEN", "Access denied", http_status=403)
pdf_data = generate_expense_income_pdf(db, document_id)
return Response(
content=pdf_data,
media_type="application/pdf",
headers={"Content-Disposition": f"attachment; filename=expense_income_{document_id}.pdf"}
)