Watch
1
0
Fork
You've already forked Seyyed_arc
0
forked from hesabix/arc
Seyyed_arc/hesabixAPI/adapters/api/v1/warehouse_reports.py

928 lines
34 KiB
Python
Executable file

"""
API endpoints for warehouse reports
"""
from typing import Dict, Any, List, Optional
from fastapi import APIRouter, Depends, Request, Body, Response
from sqlalchemy.orm import Session
import io
import datetime
import re
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, require_business_permission_dep
from app.core.responses import success_response, format_datetime_fields
from app.core.response_cache import cache_response
from app.core.i18n import negotiate_locale
from app.services.warehouse_reports_service import (
get_warehouse_documents_summary_report,
get_slow_moving_items_report,
get_critical_stock_report,
get_inter_warehouse_transfers_report,
get_adjustment_documents_report,
get_warehouse_performance_report,
get_product_movement_history_report,
get_inventory_valuation_report,
get_pending_documents_report,
get_inventory_turnover_report,
)
router = APIRouter(prefix="/warehouse-reports", tags=["warehouse_reports"])
def _create_excel_export(items: List[Dict[str, Any]], headers: List[tuple], filename_base: str, request: Request, db: Session, business_id: int) -> Response:
"""تابع کمکی برای ساخت فایل Excel"""
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
from adapters.db.models.business import Business
locale = negotiate_locale(request.headers.get("Accept-Language"))
is_fa = (locale == 'fa')
wb = Workbook()
ws = wb.active
ws.title = filename_base[:31] # Excel sheet name limit
# RTL for Persian
if locale == 'fa':
try:
ws.sheet_view.rightToLeft = True
except Exception:
pass
# Header style
header_font = Font(bold=True)
header_fill = PatternFill(start_color="DDDDDD", end_color="DDDDDD", fill_type="solid")
thin_border = Border(left=Side(style='thin'), right=Side(style='thin'), top=Side(style='thin'), bottom=Side(style='thin'))
# Add headers
header_labels = [h[1] for h in headers]
ws.append(header_labels)
for cell in ws[1]:
cell.font = header_font
cell.fill = header_fill
cell.alignment = Alignment(horizontal="center")
cell.border = thin_border
# Add data rows
keys = [h[0] for h in headers]
for it in items:
row = []
for k in keys:
value = it.get(k)
if value is None:
row.append('')
elif isinstance(value, (int, float)):
row.append(float(value))
elif isinstance(value, (dict, list)):
row.append(str(value))
else:
row.append(str(value))
ws.append(row)
for cell in ws[ws.max_row]:
cell.border = thin_border
if locale == 'fa':
cell.alignment = Alignment(horizontal="right")
# Auto width columns
try:
for column in ws.columns:
max_length = 0
column_letter = column[0].column_letter
for cell in column:
try:
if cell.value is not None and len(str(cell.value)) > max_length:
max_length = len(str(cell.value))
except Exception:
pass
ws.column_dimensions[column_letter].width = min(max_length + 2, 50)
except Exception:
pass
output = io.BytesIO()
wb.save(output)
data = output.getvalue()
# Build filename
biz_name = ""
try:
b = db.query(Business).filter(Business.id == business_id).first()
if b is not None:
biz_name = b.name or ""
except Exception:
biz_name = ""
def slugify(text: str) -> str:
return re.sub(r"[^A-Za-z0-9_-]+", "_", text).strip("_")
base = filename_base
if biz_name:
base += f"_{slugify(biz_name)}"
filename = f"{base}_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
return Response(
content=data,
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
headers={
"Content-Disposition": f"attachment; filename={filename}",
"Content-Length": str(len(data)),
"Access-Control-Expose-Headers": "Content-Disposition",
},
)
@router.post("/businesses/{business_id}/documents-summary",
summary="گزارش خلاصه حواله‌های انبار",
description="خلاصه حواله‌های انبار به تفکیک نوع با آمار ورود و خروج",
)
@require_business_access("business_id")
@cache_response(ttl=60, vary_by=["business_id"])
async def warehouse_documents_summary_endpoint(
request: Request,
business_id: int,
body: Dict[str, Any] = Body(default={}),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
_: None = Depends(require_business_permission_dep("reports", "view")),
) -> Dict[str, Any]:
"""گزارش خلاصه حواله‌های انبار"""
if not ctx.can_read_section("reports"):
from app.core.responses import ApiError
raise ApiError("FORBIDDEN", "Missing business permission: reports.read", http_status=403)
date_from = body.get("date_from")
date_to = body.get("date_to")
doc_types = body.get("doc_types")
warehouse_ids = body.get("warehouse_ids")
status = body.get("status")
skip = int(body.get("skip", 0))
take = int(body.get("take", 50))
result = get_warehouse_documents_summary_report(
db=db,
business_id=business_id,
date_from=date_from,
date_to=date_to,
doc_types=doc_types,
warehouse_ids=warehouse_ids,
status=status,
skip=skip,
take=take,
)
result["items"] = [format_datetime_fields(item, request) for item in result["items"]]
return success_response(data=result, request=request)
@router.post("/businesses/{business_id}/documents-summary/export/excel",
summary="خروجی Excel گزارش خلاصه حواله‌های انبار",
)
@require_business_access("business_id")
async def export_warehouse_documents_summary_excel(
request: Request,
business_id: int,
body: Dict[str, Any] = Body(default={}),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
_: None = Depends(require_business_permission_dep("reports", "export")),
) -> Response:
"""خروجی Excel گزارش خلاصه حواله‌های انبار"""
locale = negotiate_locale(request.headers.get("Accept-Language"))
is_fa = (locale == 'fa')
result = get_warehouse_documents_summary_report(
db=db,
business_id=business_id,
date_from=body.get("date_from"),
date_to=body.get("date_to"),
doc_types=body.get("doc_types"),
warehouse_ids=body.get("warehouse_ids"),
status=body.get("status"),
skip=0,
take=10000,
)
items = result.get("items", [])
headers = [
('code', 'کد حواله' if is_fa else 'Document Code'),
('document_date', 'تاریخ' if is_fa else 'Date'),
('doc_type', 'نوع حواله' if is_fa else 'Document Type'),
('items_count', 'تعداد اقلام' if is_fa else 'Items Count'),
('total_quantity', 'مقدار کل' if is_fa else 'Total Quantity'),
]
return _create_excel_export(items, headers, "warehouse_documents_summary", request, db, business_id)
@router.post("/businesses/{business_id}/slow-moving-items",
summary="گزارش کالاهای کم‌گردش",
description="کالاهایی که در بازه زمانی مشخص شده هیچ حرکتی نداشته‌اند",
)
@require_business_access("business_id")
@cache_response(ttl=120, vary_by=["business_id"])
async def slow_moving_items_endpoint(
request: Request,
business_id: int,
body: Dict[str, Any] = Body(default={}),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
_: None = Depends(require_business_permission_dep("reports", "view")),
) -> Dict[str, Any]:
"""گزارش کالاهای کم‌گردش"""
if not ctx.can_read_section("reports"):
from app.core.responses import ApiError
raise ApiError("FORBIDDEN", "Missing business permission: reports.read", http_status=403)
days_without_movement = int(body.get("days_without_movement", 90))
warehouse_ids = body.get("warehouse_ids")
category_ids = body.get("category_ids")
skip = int(body.get("skip", 0))
take = int(body.get("take", 50))
result = get_slow_moving_items_report(
db=db,
business_id=business_id,
days_without_movement=days_without_movement,
warehouse_ids=warehouse_ids,
category_ids=category_ids,
skip=skip,
take=take,
)
return success_response(data=result, request=request)
@router.post("/businesses/{business_id}/slow-moving-items/export/excel",
summary="خروجی Excel گزارش کالاهای کم‌گردش",
)
@require_business_access("business_id")
async def export_slow_moving_items_excel(
request: Request,
business_id: int,
body: Dict[str, Any] = Body(default={}),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
_: None = Depends(require_business_permission_dep("reports", "export")),
) -> Response:
"""خروجی Excel گزارش کالاهای کم‌گردش"""
locale = negotiate_locale(request.headers.get("Accept-Language"))
is_fa = (locale == 'fa')
result = get_slow_moving_items_report(
db=db,
business_id=business_id,
days_without_movement=int(body.get("days_without_movement", 90)),
warehouse_ids=body.get("warehouse_ids"),
category_ids=body.get("category_ids"),
skip=0,
take=10000,
)
items = result.get("items", [])
headers = [
('product_code', 'کد محصول' if is_fa else 'Product Code'),
('product_name', 'نام محصول' if is_fa else 'Product Name'),
('current_stock', 'موجودی فعلی' if is_fa else 'Current Stock'),
('unit', 'واحد' if is_fa else 'Unit'),
('days_without_movement', 'روز بدون حرکت' if is_fa else 'Days Without Movement'),
]
return _create_excel_export(items, headers, "slow_moving_items", request, db, business_id)
@router.post("/businesses/{business_id}/critical-stock",
summary="گزارش کالاهای با موجودی بحرانی",
description="کالاهایی که موجودی آن‌ها کمتر از حد تعیین شده است",
)
@require_business_access("business_id")
async def critical_stock_endpoint(
request: Request,
business_id: int,
body: Dict[str, Any] = Body(default={}),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
_: None = Depends(require_business_permission_dep("reports", "view")),
) -> Dict[str, Any]:
"""گزارش کالاهای با موجودی بحرانی"""
if not ctx.can_read_section("reports"):
from app.core.responses import ApiError
raise ApiError("FORBIDDEN", "Missing business permission: reports.read", http_status=403)
warehouse_ids = body.get("warehouse_ids")
category_ids = body.get("category_ids")
as_of_date = body.get("as_of_date")
skip = int(body.get("skip", 0))
take = int(body.get("take", 50))
result = get_critical_stock_report(
db=db,
business_id=business_id,
warehouse_ids=warehouse_ids,
category_ids=category_ids,
as_of_date=as_of_date,
skip=skip,
take=take,
)
return success_response(data=result, request=request)
@router.post("/businesses/{business_id}/critical-stock/export/excel",
summary="خروجی Excel گزارش کالاهای با موجودی بحرانی",
)
@require_business_access("business_id")
async def export_critical_stock_excel(
request: Request,
business_id: int,
body: Dict[str, Any] = Body(default={}),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
_: None = Depends(require_business_permission_dep("reports", "export")),
) -> Response:
"""خروجی Excel گزارش کالاهای با موجودی بحرانی"""
locale = negotiate_locale(request.headers.get("Accept-Language"))
is_fa = (locale == 'fa')
result = get_critical_stock_report(
db=db,
business_id=business_id,
warehouse_ids=body.get("warehouse_ids"),
category_ids=body.get("category_ids"),
as_of_date=body.get("as_of_date"),
skip=0,
take=10000,
)
items = result.get("items", [])
headers = [
('product_code', 'کد محصول' if is_fa else 'Product Code'),
('product_name', 'نام محصول' if is_fa else 'Product Name'),
('warehouse_name', 'انبار' if is_fa else 'Warehouse'),
('current_stock', 'موجودی فعلی' if is_fa else 'Current Stock'),
('min_stock', 'حداقل موجودی' if is_fa else 'Min Stock'),
('difference', 'تفاوت' if is_fa else 'Difference'),
('unit', 'واحد' if is_fa else 'Unit'),
]
return _create_excel_export(items, headers, "critical_stock", request, db, business_id)
@router.post("/businesses/{business_id}/inter-warehouse-transfers",
summary="گزارش انتقالات بین انبارها",
description="جزئیات انتقالات بین انبارها",
)
@require_business_access("business_id")
async def inter_warehouse_transfers_endpoint(
request: Request,
business_id: int,
body: Dict[str, Any] = Body(default={}),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
_: None = Depends(require_business_permission_dep("reports", "view")),
) -> Dict[str, Any]:
"""گزارش انتقالات بین انبارها"""
if not ctx.can_read_section("reports"):
from app.core.responses import ApiError
raise ApiError("FORBIDDEN", "Missing business permission: reports.read", http_status=403)
date_from = body.get("date_from")
date_to = body.get("date_to")
warehouse_from_ids = body.get("warehouse_from_ids")
warehouse_to_ids = body.get("warehouse_to_ids")
skip = int(body.get("skip", 0))
take = int(body.get("take", 50))
result = get_inter_warehouse_transfers_report(
db=db,
business_id=business_id,
date_from=date_from,
date_to=date_to,
warehouse_from_ids=warehouse_from_ids,
warehouse_to_ids=warehouse_to_ids,
skip=skip,
take=take,
)
result["items"] = [format_datetime_fields(item, request) for item in result["items"]]
return success_response(data=result, request=request)
@router.post("/businesses/{business_id}/inter-warehouse-transfers/export/excel",
summary="خروجی Excel گزارش انتقالات بین انبارها",
)
@require_business_access("business_id")
async def export_inter_warehouse_transfers_excel(
request: Request,
business_id: int,
body: Dict[str, Any] = Body(default={}),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
_: None = Depends(require_business_permission_dep("reports", "export")),
) -> Response:
"""خروجی Excel گزارش انتقالات بین انبارها"""
locale = negotiate_locale(request.headers.get("Accept-Language"))
is_fa = (locale == 'fa')
result = get_inter_warehouse_transfers_report(
db=db,
business_id=business_id,
date_from=body.get("date_from"),
date_to=body.get("date_to"),
warehouse_from_ids=body.get("warehouse_from_ids"),
warehouse_to_ids=body.get("warehouse_to_ids"),
skip=0,
take=10000,
)
items = result.get("items", [])
headers = [
('code', 'کد حواله' if is_fa else 'Document Code'),
('document_date', 'تاریخ' if is_fa else 'Date'),
('warehouse_from_name', 'انبار مبدا' if is_fa else 'From Warehouse'),
('warehouse_to_name', 'انبار مقصد' if is_fa else 'To Warehouse'),
('items_count', 'تعداد اقلام' if is_fa else 'Items Count'),
('total_quantity', 'مقدار کل' if is_fa else 'Total Quantity'),
]
return _create_excel_export(items, headers, "inter_warehouse_transfers", request, db, business_id)
@router.post("/businesses/{business_id}/adjustment-documents",
summary="گزارش حواله‌های تعدیل",
description="تحلیل حواله‌های تعدیل و تفاوت‌های موجودی",
)
@require_business_access("business_id")
async def adjustment_documents_endpoint(
request: Request,
business_id: int,
body: Dict[str, Any] = Body(default={}),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
_: None = Depends(require_business_permission_dep("reports", "view")),
) -> Dict[str, Any]:
"""گزارش حواله‌های تعدیل"""
if not ctx.can_read_section("reports"):
from app.core.responses import ApiError
raise ApiError("FORBIDDEN", "Missing business permission: reports.read", http_status=403)
date_from = body.get("date_from")
date_to = body.get("date_to")
warehouse_ids = body.get("warehouse_ids")
skip = int(body.get("skip", 0))
take = int(body.get("take", 50))
result = get_adjustment_documents_report(
db=db,
business_id=business_id,
date_from=date_from,
date_to=date_to,
warehouse_ids=warehouse_ids,
skip=skip,
take=take,
)
result["items"] = [format_datetime_fields(item, request) for item in result["items"]]
return success_response(data=result, request=request)
@router.post("/businesses/{business_id}/adjustment-documents/export/excel",
summary="خروجی Excel گزارش حواله‌های تعدیل",
)
@require_business_access("business_id")
async def export_adjustment_documents_excel(
request: Request,
business_id: int,
body: Dict[str, Any] = Body(default={}),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
_: None = Depends(require_business_permission_dep("reports", "export")),
) -> Response:
"""خروجی Excel گزارش حواله‌های تعدیل"""
locale = negotiate_locale(request.headers.get("Accept-Language"))
is_fa = (locale == 'fa')
result = get_adjustment_documents_report(
db=db,
business_id=business_id,
date_from=body.get("date_from"),
date_to=body.get("date_to"),
warehouse_ids=body.get("warehouse_ids"),
skip=0,
take=10000,
)
items = result.get("items", [])
headers = [
('code', 'کد حواله' if is_fa else 'Document Code'),
('document_date', 'تاریخ' if is_fa else 'Date'),
('items_count', 'تعداد اقلام' if is_fa else 'Items Count'),
('quantity_increase', 'افزایش' if is_fa else 'Increase'),
('quantity_decrease', 'کاهش' if is_fa else 'Decrease'),
('net_adjustment', 'خالص تعدیل' if is_fa else 'Net Adjustment'),
]
return _create_excel_export(items, headers, "adjustment_documents", request, db, business_id)
@router.post("/businesses/{business_id}/warehouse-performance",
summary="گزارش عملکرد انبارها",
description="مقایسه عملکرد انبارها",
)
@require_business_access("business_id")
async def warehouse_performance_endpoint(
request: Request,
business_id: int,
body: Dict[str, Any] = Body(default={}),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
_: None = Depends(require_business_permission_dep("reports", "view")),
) -> Dict[str, Any]:
"""گزارش عملکرد انبارها"""
if not ctx.can_read_section("reports"):
from app.core.responses import ApiError
raise ApiError("FORBIDDEN", "Missing business permission: reports.read", http_status=403)
date_from = body.get("date_from")
date_to = body.get("date_to")
warehouse_ids = body.get("warehouse_ids")
result = get_warehouse_performance_report(
db=db,
business_id=business_id,
date_from=date_from,
date_to=date_to,
warehouse_ids=warehouse_ids,
)
return success_response(data=result, request=request)
@router.post("/businesses/{business_id}/warehouse-performance/export/excel",
summary="خروجی Excel گزارش عملکرد انبارها",
)
@require_business_access("business_id")
async def export_warehouse_performance_excel(
request: Request,
business_id: int,
body: Dict[str, Any] = Body(default={}),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
_: None = Depends(require_business_permission_dep("reports", "export")),
) -> Response:
"""خروجی Excel گزارش عملکرد انبارها"""
locale = negotiate_locale(request.headers.get("Accept-Language"))
is_fa = (locale == 'fa')
result = get_warehouse_performance_report(
db=db,
business_id=business_id,
date_from=body.get("date_from"),
date_to=body.get("date_to"),
warehouse_ids=body.get("warehouse_ids"),
)
items = result.get("items", [])
headers = [
('warehouse_code', 'کد انبار' if is_fa else 'Warehouse Code'),
('warehouse_name', 'نام انبار' if is_fa else 'Warehouse Name'),
('total_documents', 'تعداد حواله‌ها' if is_fa else 'Total Documents'),
('total_items', 'تعداد اقلام' if is_fa else 'Total Items'),
('total_quantity_in', 'کل ورود' if is_fa else 'Total In'),
('total_quantity_out', 'کل خروج' if is_fa else 'Total Out'),
('net_quantity', 'خالص' if is_fa else 'Net'),
]
return _create_excel_export(items, headers, "warehouse_performance", request, db, business_id)
@router.post("/businesses/{business_id}/product-movement-history",
summary="گزارش تاریخچه حرکات یک کالا",
description="تاریخچه کامل حرکات یک کالا در تمام انبارها",
)
@require_business_access("business_id")
async def product_movement_history_endpoint(
request: Request,
business_id: int,
body: Dict[str, Any] = Body(default={}),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
_: None = Depends(require_business_permission_dep("reports", "view")),
) -> Dict[str, Any]:
"""گزارش تاریخچه حرکات یک کالا"""
if not ctx.can_read_section("reports"):
from app.core.responses import ApiError
raise ApiError("FORBIDDEN", "Missing business permission: reports.read", http_status=403)
product_id = body.get("product_id")
if not product_id:
from app.core.responses import ApiError
raise ApiError("PRODUCT_ID_REQUIRED", "product_id is required", http_status=400)
date_from = body.get("date_from")
date_to = body.get("date_to")
warehouse_ids = body.get("warehouse_ids")
skip = int(body.get("skip", 0))
take = int(body.get("take", 50))
result = get_product_movement_history_report(
db=db,
business_id=business_id,
product_id=int(product_id),
date_from=date_from,
date_to=date_to,
warehouse_ids=warehouse_ids,
skip=skip,
take=take,
)
result["items"] = [format_datetime_fields(item, request) for item in result["items"]]
return success_response(data=result, request=request)
@router.post("/businesses/{business_id}/product-movement-history/export/excel",
summary="خروجی Excel گزارش تاریخچه حرکات یک کالا",
)
@require_business_access("business_id")
async def export_product_movement_history_excel(
request: Request,
business_id: int,
body: Dict[str, Any] = Body(default={}),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
_: None = Depends(require_business_permission_dep("reports", "export")),
) -> Response:
"""خروجی Excel گزارش تاریخچه حرکات یک کالا"""
locale = negotiate_locale(request.headers.get("Accept-Language"))
is_fa = (locale == 'fa')
product_id = body.get("product_id")
if not product_id:
from app.core.responses import ApiError
raise ApiError("PRODUCT_ID_REQUIRED", "product_id is required", http_status=400)
result = get_product_movement_history_report(
db=db,
business_id=business_id,
product_id=int(product_id),
date_from=body.get("date_from"),
date_to=body.get("date_to"),
warehouse_ids=body.get("warehouse_ids"),
skip=0,
take=10000,
)
items = result.get("items", [])
headers = [
('document_code', 'کد حواله' if is_fa else 'Document Code'),
('document_date', 'تاریخ' if is_fa else 'Date'),
('doc_type', 'نوع حواله' if is_fa else 'Document Type'),
('warehouse_name', 'انبار' if is_fa else 'Warehouse'),
('movement', 'نوع حرکت' if is_fa else 'Movement'),
('quantity', 'مقدار' if is_fa else 'Quantity'),
]
return _create_excel_export(items, headers, "product_movement_history", request, db, business_id)
@router.post("/businesses/{business_id}/inventory-valuation",
summary="گزارش ارزش موجودی انبار",
description="ارزش موجودی انبارها به معادل ارز پایهٔ کسب‌وکار",
)
@require_business_access("business_id")
async def inventory_valuation_endpoint(
request: Request,
business_id: int,
body: Dict[str, Any] = Body(default={}),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
_: None = Depends(require_business_permission_dep("reports", "view")),
) -> Dict[str, Any]:
"""گزارش ارزش موجودی انبار"""
if not ctx.can_read_section("reports"):
from app.core.responses import ApiError
raise ApiError("FORBIDDEN", "Missing business permission: reports.read", http_status=403)
as_of_date = body.get("as_of_date")
warehouse_ids = body.get("warehouse_ids")
category_ids = body.get("category_ids")
currency_id = body.get("currency_id")
skip = int(body.get("skip", 0))
take = int(body.get("take", 50))
result = get_inventory_valuation_report(
db=db,
business_id=business_id,
as_of_date=as_of_date,
warehouse_ids=warehouse_ids,
category_ids=category_ids,
currency_id=currency_id,
skip=skip,
take=take,
)
return success_response(data=result, request=request)
@router.post("/businesses/{business_id}/inventory-valuation/export/excel",
summary="خروجی Excel گزارش ارزش موجودی انبار",
)
@require_business_access("business_id")
async def export_inventory_valuation_excel(
request: Request,
business_id: int,
body: Dict[str, Any] = Body(default={}),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
_: None = Depends(require_business_permission_dep("reports", "export")),
) -> Response:
"""خروجی Excel گزارش ارزش موجودی انبار"""
locale = negotiate_locale(request.headers.get("Accept-Language"))
is_fa = (locale == 'fa')
result = get_inventory_valuation_report(
db=db,
business_id=business_id,
as_of_date=body.get("as_of_date"),
warehouse_ids=body.get("warehouse_ids"),
category_ids=body.get("category_ids"),
currency_id=body.get("currency_id"),
skip=0,
take=10000,
)
items = result.get("items", [])
headers = [
('product_code', 'کد محصول' if is_fa else 'Product Code'),
('product_name', 'نام محصول' if is_fa else 'Product Name'),
('warehouse_name', 'انبار' if is_fa else 'Warehouse'),
('quantity', 'موجودی' if is_fa else 'Quantity'),
('unit', 'واحد' if is_fa else 'Unit'),
('cost_price', 'قیمت تمام شده' if is_fa else 'Cost Price'),
('value', 'ارزش' if is_fa else 'Value'),
]
return _create_excel_export(items, headers, "inventory_valuation", request, db, business_id)
@router.post("/businesses/{business_id}/pending-documents",
summary="گزارش حواله‌های در انتظار تایید",
description="حواله‌های draft یا در انتظار تایید",
)
@require_business_access("business_id")
async def pending_documents_endpoint(
request: Request,
business_id: int,
body: Dict[str, Any] = Body(default={}),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
_: None = Depends(require_business_permission_dep("reports", "view")),
) -> Dict[str, Any]:
"""گزارش حواله‌های در انتظار تایید"""
if not ctx.can_read_section("reports"):
from app.core.responses import ApiError
raise ApiError("FORBIDDEN", "Missing business permission: reports.read", http_status=403)
warehouse_ids = body.get("warehouse_ids")
skip = int(body.get("skip", 0))
take = int(body.get("take", 50))
result = get_pending_documents_report(
db=db,
business_id=business_id,
warehouse_ids=warehouse_ids,
skip=skip,
take=take,
)
result["items"] = [format_datetime_fields(item, request) for item in result["items"]]
return success_response(data=result, request=request)
@router.post("/businesses/{business_id}/pending-documents/export/excel",
summary="خروجی Excel گزارش حواله‌های در انتظار تایید",
)
@require_business_access("business_id")
async def export_pending_documents_excel(
request: Request,
business_id: int,
body: Dict[str, Any] = Body(default={}),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
_: None = Depends(require_business_permission_dep("reports", "export")),
) -> Response:
"""خروجی Excel گزارش حواله‌های در انتظار تایید"""
locale = negotiate_locale(request.headers.get("Accept-Language"))
is_fa = (locale == 'fa')
result = get_pending_documents_report(
db=db,
business_id=business_id,
warehouse_ids=body.get("warehouse_ids"),
skip=0,
take=10000,
)
items = result.get("items", [])
headers = [
('code', 'کد حواله' if is_fa else 'Document Code'),
('document_date', 'تاریخ' if is_fa else 'Date'),
('doc_type', 'نوع حواله' if is_fa else 'Document Type'),
('items_count', 'تعداد اقلام' if is_fa else 'Items Count'),
('days_pending', 'روز انتظار' if is_fa else 'Days Pending'),
]
return _create_excel_export(items, headers, "pending_documents", request, db, business_id)
@router.post("/businesses/{business_id}/inventory-turnover",
summary="گزارش گردش موجودی",
description="نرخ گردش موجودی کالاها",
)
@require_business_access("business_id")
async def inventory_turnover_endpoint(
request: Request,
business_id: int,
body: Dict[str, Any] = Body(default={}),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
_: None = Depends(require_business_permission_dep("reports", "view")),
) -> Dict[str, Any]:
"""گزارش گردش موجودی"""
if not ctx.can_read_section("reports"):
from app.core.responses import ApiError
raise ApiError("FORBIDDEN", "Missing business permission: reports.read", http_status=403)
date_from = body.get("date_from")
date_to = body.get("date_to")
warehouse_ids = body.get("warehouse_ids")
category_ids = body.get("category_ids")
skip = int(body.get("skip", 0))
take = int(body.get("take", 50))
result = get_inventory_turnover_report(
db=db,
business_id=business_id,
date_from=date_from,
date_to=date_to,
warehouse_ids=warehouse_ids,
category_ids=category_ids,
skip=skip,
take=take,
)
return success_response(data=result, request=request)
@router.post("/businesses/{business_id}/inventory-turnover/export/excel",
summary="خروجی Excel گزارش گردش موجودی",
)
@require_business_access("business_id")
async def export_inventory_turnover_excel(
request: Request,
business_id: int,
body: Dict[str, Any] = Body(default={}),
ctx: AuthContext = Depends(get_current_user),
db: Session = Depends(get_db),
_: None = Depends(require_business_permission_dep("reports", "export")),
) -> Response:
"""خروجی Excel گزارش گردش موجودی"""
locale = negotiate_locale(request.headers.get("Accept-Language"))
is_fa = (locale == 'fa')
result = get_inventory_turnover_report(
db=db,
business_id=business_id,
date_from=body.get("date_from"),
date_to=body.get("date_to"),
warehouse_ids=body.get("warehouse_ids"),
category_ids=body.get("category_ids"),
skip=0,
take=10000,
)
items = result.get("items", [])
headers = [
('product_code', 'کد محصول' if is_fa else 'Product Code'),
('product_name', 'نام محصول' if is_fa else 'Product Name'),
('average_stock', 'موجودی متوسط' if is_fa else 'Average Stock'),
('total_out', 'کل خروج' if is_fa else 'Total Out'),
('turnover_rate', 'نرخ گردش' if is_fa else 'Turnover Rate'),
('unit', 'واحد' if is_fa else 'Unit'),
]
return _create_excel_export(items, headers, "inventory_turnover", request, db, business_id)