from __future__ import annotations 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_access from app.core.responses import success_response, ApiError, format_datetime_fields from adapters.api.v1.schemas import QueryInfo from adapters.db.models.document import Document from sqlalchemy import and_ from app.services.inventory_transfer_service import create_inventory_transfer, DOCUMENT_TYPE_INVENTORY_TRANSFER router = APIRouter(prefix="/inventory-transfers", tags=["inventory_transfers"]) @router.post("/business/{business_id}") @require_business_access("business_id") def create_inventory_transfer_endpoint( request: Request, business_id: int, payload: Dict[str, Any] = Body(...), ctx: AuthContext = Depends(get_current_user), db: Session = Depends(get_db), ) -> Dict[str, Any]: if not ctx.has_business_permission("inventory", "write"): raise ApiError("FORBIDDEN", "Missing business permission: inventory.write", http_status=403) result = create_inventory_transfer(db, business_id, ctx.user_id, payload) return success_response(data=format_datetime_fields(result["data"], request), request=request, message=result.get("message")) @router.post("/business/{business_id}/query") @require_business_access("business_id") def query_inventory_transfers_endpoint( request: Request, business_id: int, payload: QueryInfo, ctx: AuthContext = Depends(get_current_user), db: Session = Depends(get_db), ) -> Dict[str, Any]: if not ctx.can_read_section("inventory"): raise ApiError("FORBIDDEN", "Missing business permission: inventory.read", http_status=403) take = max(1, payload.take) skip = max(0, payload.skip) q = db.query(Document).filter( and_( Document.business_id == business_id, Document.document_type == DOCUMENT_TYPE_INVENTORY_TRANSFER, ) ) total = q.count() rows = q.order_by(Document.document_date.desc(), Document.id.desc()).offset(skip).limit(take).all() items = [{ "id": d.id, "code": d.code, "document_date": d.document_date, "currency_id": d.currency_id, "description": d.description, } for d in rows] return success_response(data={ "items": format_datetime_fields(items, request), "pagination": { "total": total, "page": (skip // take) + 1, "per_page": take, "total_pages": (total + take - 1) // take, "has_next": skip + take < total, "has_prev": skip > 0, }, "query_info": payload.model_dump(), }, request=request) @router.post("/business/{business_id}/export/excel", summary="خروجی Excel لیست انتقال موجودی", description="خروجی اکسل از لیست اسناد انتقال موجودی بین انبارها", ) @require_business_access("business_id") def export_inventory_transfers_excel( request: Request, business_id: int, body: Dict[str, Any] = Body(default={}), ctx: AuthContext = Depends(get_current_user), db: Session = Depends(get_db), ): if not ctx.can_read_section("inventory"): raise ApiError("FORBIDDEN", "Missing business permission: inventory.read", http_status=403) from fastapi.responses import Response from openpyxl import Workbook from io import BytesIO import datetime take = min(int(body.get("take", 1000)), 10000) skip = int(body.get("skip", 0)) q = db.query(Document).filter(and_(Document.business_id == business_id, Document.document_type == DOCUMENT_TYPE_INVENTORY_TRANSFER)) rows = q.order_by(Document.document_date.desc(), Document.id.desc()).offset(skip).limit(take).all() wb = Workbook() ws = wb.active ws.title = "InventoryTransfers" ws.append(["code", "document_date", "description"]) for d in rows: ws.append([d.code, d.document_date.isoformat(), d.description]) buf = BytesIO() wb.save(buf) content = buf.getvalue() filename = f"inventory_transfers_{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("/business/{business_id}/export/pdf", summary="خروجی PDF لیست انتقال موجودی", description="خروجی PDF از لیست اسناد انتقال موجودی بین انبارها", ) @require_business_access("business_id") def export_inventory_transfers_pdf( request: Request, business_id: int, body: Dict[str, Any] = Body(default={}), ctx: AuthContext = Depends(get_current_user), db: Session = Depends(get_db), ): if not ctx.can_read_section("inventory"): raise ApiError("FORBIDDEN", "Missing business permission: inventory.read", http_status=403) from fastapi.responses import Response from weasyprint import HTML, CSS from weasyprint.text.fonts import FontConfiguration from html import escape import datetime take = min(int(body.get("take", 1000)), 10000) skip = int(body.get("skip", 0)) q = db.query(Document).filter(and_(Document.business_id == business_id, Document.document_type == DOCUMENT_TYPE_INVENTORY_TRANSFER)) rows = q.order_by(Document.document_date.desc(), Document.id.desc()).offset(skip).limit(take).all() def cell(v: Any) -> str: return escape(v.isoformat()) if hasattr(v, 'isoformat') else escape(str(v) if v is not None else "") rows_html = "".join([ f"" f"{cell(d.code)}" f"{cell(d.document_date)}" f"{cell(d.description)}" f"" for d in rows ]) # تلاش برای رندر با قالب سفارشی (inventory_transfers/list) resolved_html = None try: from app.services.report_template_service import ReportTemplateService explicit_template_id = None try: if body.get("template_id") is not None: explicit_template_id = int(body.get("template_id")) except Exception: explicit_template_id = None # نام کسب‌وکار business_name = "" try: from adapters.db.models.business import Business biz = db.query(Business).filter(Business.id == business_id).first() if biz is not None: business_name = biz.name or "" except Exception: business_name = "" # Locale from app.core.i18n import negotiate_locale locale = negotiate_locale(request.headers.get("Accept-Language")) is_fa = (locale == 'fa') headers = ["کد سند", "تاریخ سند", "شرح"] keys = ["code", "document_date", "description"] headers_html = "کد سندتاریخ سندشرح" template_context = { "title_text": "لیست انتقال‌ها" if is_fa else "Transfers List", "business_name": business_name, "generated_at": datetime.datetime.now().strftime('%Y/%m/%d %H:%M'), "is_fa": is_fa, "headers": headers, "keys": keys, "items": [ {"code": d.code, "document_date": d.document_date, "description": d.description} for d in rows ], "table_headers_html": headers_html, "table_rows_html": rows_html, } resolved_html = ReportTemplateService.try_render_resolved( db=db, business_id=business_id, module_key="inventory_transfers", subtype="list", context=template_context, explicit_template_id=explicit_template_id, ) except Exception: resolved_html = None html = f"""

لیست انتقال موجودی بین انبارها

{rows_html}
کد سند تاریخ سند شرح
""" final_html = resolved_html or html font_config = FontConfiguration() pdf_bytes = HTML(string=final_html).write_pdf(stylesheets=[CSS(string="@page { size: A4 portrait; margin: 12mm; }")], font_config=font_config) filename = f"inventory_transfers_{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", }, )