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.responses import success_response, format_datetime_fields from app.core.permissions import require_business_access from adapters.api.v1.schemas import QueryInfo from app.services.kardex_service import list_kardex_lines router = APIRouter(prefix="/kardex", tags=["kardex"]) @router.post( "/businesses/{business_id}/lines", summary="لیست کاردکس (خطوط اسناد)", description="دریافت خطوط اسناد مرتبط با انتخاب‌های چندگانه موجودیت‌ها با فیلتر تاریخ", ) @require_business_access("business_id") async def list_kardex_lines_endpoint( request: Request, business_id: int, query_info: QueryInfo, db: Session = Depends(get_db), ctx: AuthContext = Depends(get_current_user), ): # Compose query dict from QueryInfo and additional parameters from body query_dict: Dict[str, Any] = { "take": query_info.take, "skip": query_info.skip, "sort_by": query_info.sort_by or "document_date", "sort_desc": query_info.sort_desc, "search": query_info.search, "search_fields": query_info.search_fields, "filters": query_info.filters, } # Additional params from body (DataTable additionalParams) try: body_json = await request.json() if isinstance(body_json, dict): for key in ( "from_date", "to_date", "fiscal_year_id", "person_ids", "product_ids", "bank_account_ids", "cash_register_ids", "petty_cash_ids", "account_ids", "check_ids", "warehouse_ids", "match_mode", "result_scope", ): if key in body_json and body_json.get(key) is not None: query_dict[key] = body_json.get(key) except Exception: pass result = list_kardex_lines(db, business_id, query_dict) # Format date fields in response items (document_date) try: items = result.get("items", []) for item in items: # Use format_datetime_fields for consistency item.update(format_datetime_fields({"document_date": item.get("document_date")}, request)) except Exception: pass return success_response(data=result, request=request, message="KARDEX_LINES") @router.post( "/businesses/{business_id}/lines/export/excel", summary="خروجی Excel کاردکس", description="خروجی اکسل از لیست خطوط کاردکس با فیلترهای اعمال‌شده", ) @require_business_access("business_id") async def export_kardex_excel_endpoint( request: Request, business_id: int, body: Dict[str, Any] = Body(...), db: Session = Depends(get_db), ctx: AuthContext = Depends(get_current_user), ): from fastapi.responses import Response import datetime try: max_export_records = 10000 take_value = min(int(body.get("take", 1000)), max_export_records) except Exception: take_value = 1000 query_dict: Dict[str, Any] = { "take": take_value, "skip": int(body.get("skip", 0)), "sort_by": body.get("sort_by") or "document_date", "sort_desc": bool(body.get("sort_desc", True)), "search": body.get("search"), "search_fields": body.get("search_fields"), "filters": body.get("filters"), "from_date": body.get("from_date"), "to_date": body.get("to_date"), "person_ids": body.get("person_ids"), "product_ids": body.get("product_ids"), "bank_account_ids": body.get("bank_account_ids"), "cash_register_ids": body.get("cash_register_ids"), "petty_cash_ids": body.get("petty_cash_ids"), "account_ids": body.get("account_ids"), "check_ids": body.get("check_ids"), "warehouse_ids": body.get("warehouse_ids"), "match_mode": body.get("match_mode") or "any", "result_scope": body.get("result_scope") or "lines_matching", "include_running_balance": bool(body.get("include_running_balance", False)), } result = list_kardex_lines(db, business_id, query_dict) items = result.get("items", []) items = [format_datetime_fields(it, request) for it in items] # Build simple Excel using openpyxl from openpyxl import Workbook from io import BytesIO wb = Workbook() ws = wb.active ws.title = "Kardex" headers = [ "document_date", "document_code", "document_type", "warehouse", "movement", "description", "debit", "credit", "quantity", "running_amount", "running_quantity", ] ws.append(headers) for it in items: ws.append([ it.get("document_date"), it.get("document_code"), it.get("document_type"), it.get("warehouse_name") or it.get("warehouse_id"), it.get("movement"), it.get("description"), it.get("debit"), it.get("credit"), it.get("quantity"), it.get("running_amount"), it.get("running_quantity"), ]) buf = BytesIO() wb.save(buf) content = buf.getvalue() filename = f"kardex_{business_id}_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx" return Response( content=content, media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", headers={ "Content-Disposition": f"attachment; filename={filename}", "Content-Length": str(len(content)), "Access-Control-Expose-Headers": "Content-Disposition", }, ) @router.post( "/businesses/{business_id}/lines/export/pdf", summary="خروجی PDF کاردکس", description="خروجی PDF از لیست خطوط کاردکس با فیلترهای اعمال‌شده", ) @require_business_access("business_id") async def export_kardex_pdf_endpoint( request: Request, business_id: int, body: Dict[str, Any] = Body(...), db: Session = Depends(get_db), ctx: AuthContext = Depends(get_current_user), ): from fastapi.responses import Response import datetime from weasyprint import HTML, CSS from weasyprint.text.fonts import FontConfiguration from html import escape try: max_export_records = 10000 take_value = min(int(body.get("take", 1000)), max_export_records) except Exception: take_value = 1000 query_dict: Dict[str, Any] = { "take": take_value, "skip": int(body.get("skip", 0)), "sort_by": body.get("sort_by") or "document_date", "sort_desc": bool(body.get("sort_desc", True)), "search": body.get("search"), "search_fields": body.get("search_fields"), "filters": body.get("filters"), "from_date": body.get("from_date"), "to_date": body.get("to_date"), "person_ids": body.get("person_ids"), "product_ids": body.get("product_ids"), "bank_account_ids": body.get("bank_account_ids"), "cash_register_ids": body.get("cash_register_ids"), "petty_cash_ids": body.get("petty_cash_ids"), "account_ids": body.get("account_ids"), "check_ids": body.get("check_ids"), "warehouse_ids": body.get("warehouse_ids"), "match_mode": body.get("match_mode") or "any", "result_scope": body.get("result_scope") or "lines_matching", "include_running_balance": bool(body.get("include_running_balance", False)), } result = list_kardex_lines(db, business_id, query_dict) items = result.get("items", []) items = [format_datetime_fields(it, request) for it in items] # Build simple HTML table def cell(val: Any) -> str: return escape(str(val)) if val is not None else "" rows_html = "".join([ f"" f"{cell(it.get('document_date'))}" f"{cell(it.get('document_code'))}" f"{cell(it.get('document_type'))}" f"{cell(it.get('warehouse_name') or it.get('warehouse_id'))}" f"{cell(it.get('movement'))}" f"{cell(it.get('description'))}" f"{cell(it.get('debit'))}" f"{cell(it.get('credit'))}" f"{cell(it.get('quantity'))}" f"{cell(it.get('running_amount'))}" f"{cell(it.get('running_quantity'))}" f"" for it in items ]) html = f"""

گزارش کاردکس

{rows_html}
تاریخ سند کد سند نوع سند انبار جهت حرکت شرح بدهکار بستانکار تعداد مانده مبلغ مانده تعداد
""" font_config = FontConfiguration() pdf_bytes = HTML(string=html).write_pdf(stylesheets=[CSS(string="@page { size: A4 landscape; margin: 12mm; }")], font_config=font_config) filename = f"kardex_{business_id}_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf" return Response( content=pdf_bytes, media_type="application/pdf", headers={ "Content-Disposition": f"attachment; filename={filename}", "Content-Length": str(len(pdf_bytes)), "Access-Control-Expose-Headers": "Content-Disposition", }, )