from __future__ import annotations import mimetypes from pathlib import Path from typing import Any from urllib.parse import quote from app.services.document_preview import DocumentPreviewAssets from app.services.expense_claim_attachment_storage import ExpenseClaimAttachmentStorage class ExpenseClaimAttachmentPresentation: def __init__(self, storage: ExpenseClaimAttachmentStorage) -> None: self.storage = storage def build_preview_meta( self, *, file_path: Path, media_type: str, ocr_document: Any | None, ) -> dict[str, Any]: filename = file_path.name storage_key = self.storage.to_storage_key(file_path) preview_kind = self.resolve_preview_kind(media_type, filename) preview_data_url = str(getattr(ocr_document, "preview_data_url", "") or "").strip() preview_source_kind = str(getattr(ocr_document, "preview_kind", "") or "").strip() if preview_source_kind == "image" and preview_data_url: preview_asset = self._write_preview_asset_from_data_url( attachment_dir=file_path.parent, original_filename=filename, preview_data_url=preview_data_url, ) if preview_asset is not None: preview_path, preview_media_type, preview_file_name = preview_asset return { "previewable": True, "preview_kind": "image", "preview_storage_key": self.storage.to_storage_key(preview_path), "preview_media_type": preview_media_type, "preview_file_name": preview_file_name, "preview_rendered_with": DocumentPreviewAssets.renderer_id_for_source(media_type), } if preview_kind: return { "previewable": True, "preview_kind": preview_kind, "preview_storage_key": storage_key, "preview_media_type": media_type, "preview_file_name": filename, "preview_rendered_with": "", } return { "previewable": False, "preview_kind": "", "preview_storage_key": "", "preview_media_type": "", "preview_file_name": "", "preview_rendered_with": "", } @staticmethod def resolve_preview_kind(media_type: str | None, filename: str) -> str: resolved = str(media_type or "").strip() or (mimetypes.guess_type(filename)[0] or "") if resolved.startswith("image/"): return "image" if resolved == "application/pdf": return "pdf" return "" @staticmethod def decode_data_url(payload: str) -> tuple[str, bytes] | None: return DocumentPreviewAssets.decode_data_url(payload) def _write_preview_asset_from_data_url( self, *, attachment_dir: Path, original_filename: str, preview_data_url: str, ) -> tuple[Path, str, str] | None: return DocumentPreviewAssets.write_data_url_preview( preview_dir=attachment_dir, preview_name_stem=f"{Path(original_filename).stem}.preview", preview_data_url=preview_data_url, ) @staticmethod def build_preview_client_path(claim_id: str, item_id: str) -> str: return ( "/reimbursements/claims/" f"{quote(str(claim_id or '').strip(), safe='')}" f"/items/{quote(str(item_id or '').strip(), safe='')}/attachment/preview" ) @staticmethod def resolve_media_type(filename: str, *, fallback: str | None = None) -> str: guessed = mimetypes.guess_type(filename)[0] return str(guessed or fallback or "application/octet-stream") @staticmethod def is_previewable_media_type(media_type: str | None, filename: str) -> bool: resolved = str(media_type or "").strip() or (mimetypes.guess_type(filename)[0] or "") return resolved.startswith("image/") or resolved == "application/pdf" @staticmethod def resolve_display_name(storage_key: str | None) -> str: return Path(str(storage_key or "").strip()).name @classmethod def merge_reference(cls, current_invoice_id: str | None, next_invoice_id: str | None) -> str | None: normalized_next = str(next_invoice_id or "").strip() if not normalized_next: return None normalized_current = str(current_invoice_id or "").strip() if ( normalized_current and cls.resolve_display_name(normalized_current) == cls.resolve_display_name(normalized_next) ): return normalized_current return normalized_next