fc / apps /invoice_recognition /api /invoice_recognition_api.py
jun2024's picture
Upload 2241 files
b4f2238 verified
Raw History Blame Contribute Delete
6.82 kB
"""
发票识别 API
提供发票文件上传、识别状态查询和下载接口。
"""
import logging
from pathlib import Path
from typing import Any
from django.http import HttpResponse
from django.utils.translation import gettext as _
from ninja import File, Router
from ninja.errors import HttpError
from ninja.files import UploadedFile
from apps.core.infrastructure.throttling import rate_limit_from_settings
logger = logging.getLogger("apps.invoice_recognition")
router = Router(tags=["发票识别"])
@router.post("/quick-recognize")
def quick_recognize(
request: Any,
files: list[UploadedFile] = File(...), # type: ignore[type-arg]
) -> dict[str, Any]:
"""快速识别发票文件(不创建任务)"""
if not files:
raise HttpError(400, _("未提供文件"))
service = _get_quick_recognition_service()
try:
results = service.recognize_files(files)
except Exception as exc:
logger.error("快速识别失败: %s", exc, exc_info=True)
raise HttpError(500, _("服务器内部错误"))
results_data: list[dict[str, Any]] = []
for result in results:
result_dict: dict[str, Any] = {
"filename": result.filename,
"success": result.success,
}
if result.success and result.data:
import datetime
from decimal import Decimal
def _serialize_value(value: Any) -> Any:
if isinstance(value, (datetime.datetime, datetime.date)):
return str(value)
if isinstance(value, Decimal):
return str(value)
return value
result_dict["data"] = {
"invoice_code": _serialize_value(result.data.invoice_code),
"invoice_number": _serialize_value(result.data.invoice_number),
"invoice_date": _serialize_value(result.data.invoice_date),
"amount": _serialize_value(result.data.amount),
"tax_amount": _serialize_value(result.data.tax_amount),
"total_amount": _serialize_value(result.data.total_amount),
"buyer_name": _serialize_value(result.data.buyer_name),
"seller_name": _serialize_value(result.data.seller_name),
"project_name": _serialize_value(result.data.project_name),
"category": _serialize_value(result.data.category),
}
else:
result_dict["error"] = result.error
results_data.append(result_dict)
return {"results": results_data}
def _get_recognition_service() -> Any:
from apps.invoice_recognition.services.wiring import get_invoice_recognition_service
return get_invoice_recognition_service()
def _get_download_service() -> Any:
from apps.invoice_recognition.services.wiring import get_invoice_download_service
return get_invoice_download_service()
def _get_quick_recognition_service() -> Any:
from apps.invoice_recognition.services.wiring import get_quick_recognition_service
return get_quick_recognition_service()
@router.post("/{task_id}/upload")
@rate_limit_from_settings("UPLOAD", by_user=True)
def upload_invoices(
request: Any,
task_id: int,
files: list[UploadedFile] = File(...), # type: ignore[type-arg]
) -> dict[str, Any]:
"""多文件上传 + 自动识别"""
from django.core.exceptions import ObjectDoesNotExist, ValidationError
service = _get_recognition_service()
try:
records = service.upload_and_recognize(task_id, files)
except ObjectDoesNotExist:
raise HttpError(404, _("任务不存在"))
except ValidationError as exc:
raise HttpError(400, str(exc.message if hasattr(exc, "message") else exc))
record_list: list[dict[str, Any]] = [
{
"id": r.id,
"original_filename": r.original_filename,
"status": r.status,
"invoice_code": r.invoice_code,
"invoice_number": r.invoice_number,
"is_duplicate": r.is_duplicate,
"category": r.category,
}
for r in records
]
return {"success": True, "count": len(records), "records": record_list}
@router.get("/{task_id}/status")
def get_task_status(
request: Any,
task_id: int,
) -> dict[str, Any]:
"""任务状态 + 发票记录列表"""
from django.core.exceptions import ObjectDoesNotExist
service = _get_recognition_service()
try:
data = service.get_task_status(task_id)
except ObjectDoesNotExist:
raise HttpError(404, _("任务不存在"))
def _serialize(obj: Any) -> Any:
import datetime
from decimal import Decimal
if isinstance(obj, (datetime.datetime, datetime.date)):
return str(obj)
if isinstance(obj, Decimal):
return str(obj)
if isinstance(obj, dict):
return {k: _serialize(v) for k, v in obj.items()}
if isinstance(obj, list):
return [_serialize(item) for item in obj]
return obj
serialized = _serialize(data)
assert isinstance(serialized, dict)
return serialized
@router.get("/{task_id}/download")
@rate_limit_from_settings("EXPORT", by_user=True)
def download_invoices(
request: Any,
task_id: int,
scope: str,
fmt: str = "zip",
invoice_id: int | None = None,
category: str | None = None,
) -> HttpResponse:
"""灵活下载:scope=single/category/all,fmt=pdf/zip"""
from django.core.exceptions import ObjectDoesNotExist
content_type_map: dict[str, str] = {
"pdf": "application/pdf",
"zip": "application/zip",
}
content_type = content_type_map.get(fmt, "application/octet-stream")
download_service = _get_download_service()
try:
if scope == "single":
if invoice_id is None:
raise HttpError(400, _("scope=single 时必须提供 invoice_id"))
file_path, filename = download_service.download_single(invoice_id)
with Path(file_path).open("rb") as f:
data = f.read()
elif scope == "category":
if category is None:
raise HttpError(400, _("scope=category 时必须提供 category"))
data, filename = download_service.download_by_category(task_id, category, fmt)
elif scope == "all":
data, filename = download_service.download_all(task_id, fmt)
else:
raise HttpError(400, _("无效的 scope 参数,允许值:single/category/all"))
except ObjectDoesNotExist:
raise HttpError(404, _("任务或发票不存在"))
response = HttpResponse(data, content_type=content_type)
response["Content-Disposition"] = f'attachment; filename="{filename}"'
return response