# models/schema/letzshop.py """ Pydantic schemas for Letzshop marketplace integration. Covers: - Vendor credentials management - Letzshop order import/sync - Fulfillment queue operations - Sync logs """ from datetime import datetime from typing import Any from pydantic import BaseModel, ConfigDict, Field # ============================================================================ # Credentials Schemas # ============================================================================ class LetzshopCredentialsCreate(BaseModel): """Schema for creating/updating Letzshop credentials.""" api_key: str = Field(..., min_length=1, description="Letzshop API key") api_endpoint: str | None = Field( None, description="Custom API endpoint (defaults to https://letzshop.lu/graphql)", ) auto_sync_enabled: bool = Field(False, description="Enable automatic order sync") sync_interval_minutes: int = Field( 15, ge=5, le=1440, description="Sync interval in minutes (5-1440)" ) class LetzshopCredentialsUpdate(BaseModel): """Schema for updating Letzshop credentials (partial update).""" api_key: str | None = Field(None, min_length=1) api_endpoint: str | None = None auto_sync_enabled: bool | None = None sync_interval_minutes: int | None = Field(None, ge=5, le=1440) class LetzshopCredentialsResponse(BaseModel): """Schema for Letzshop credentials response (API key is masked).""" model_config = ConfigDict(from_attributes=True) id: int vendor_id: int api_key_masked: str = Field(..., description="Masked API key for display") api_endpoint: str auto_sync_enabled: bool sync_interval_minutes: int last_sync_at: datetime | None last_sync_status: str | None last_sync_error: str | None created_at: datetime updated_at: datetime class LetzshopCredentialsStatus(BaseModel): """Schema for Letzshop connection status.""" is_configured: bool is_connected: bool last_sync_at: datetime | None last_sync_status: str | None auto_sync_enabled: bool # ============================================================================ # Letzshop Order Schemas # ============================================================================ class LetzshopInventoryUnit(BaseModel): """Schema for Letzshop inventory unit.""" id: str state: str class LetzshopOrderBase(BaseModel): """Base schema for Letzshop order.""" letzshop_order_id: str letzshop_shipment_id: str | None = None letzshop_order_number: str | None = None letzshop_state: str | None = None customer_email: str | None = None customer_name: str | None = None total_amount: str | None = None currency: str = "EUR" class LetzshopOrderCreate(LetzshopOrderBase): """Schema for creating a Letzshop order record.""" vendor_id: int raw_order_data: dict[str, Any] | None = None inventory_units: list[dict[str, Any]] | None = None class LetzshopOrderResponse(LetzshopOrderBase): """Schema for Letzshop order response.""" model_config = ConfigDict(from_attributes=True) id: int vendor_id: int local_order_id: int | None sync_status: str last_synced_at: datetime | None sync_error: str | None confirmed_at: datetime | None rejected_at: datetime | None tracking_set_at: datetime | None tracking_number: str | None tracking_carrier: str | None inventory_units: list[dict[str, Any]] | None created_at: datetime updated_at: datetime class LetzshopOrderDetailResponse(LetzshopOrderResponse): """Schema for detailed Letzshop order response with raw data.""" raw_order_data: dict[str, Any] | None = None class LetzshopOrderListResponse(BaseModel): """Schema for paginated Letzshop order list.""" orders: list[LetzshopOrderResponse] total: int skip: int limit: int # ============================================================================ # Fulfillment Schemas # ============================================================================ class FulfillmentConfirmRequest(BaseModel): """Schema for confirming order fulfillment.""" inventory_unit_ids: list[str] = Field( ..., min_length=1, description="List of inventory unit IDs to confirm" ) class FulfillmentRejectRequest(BaseModel): """Schema for rejecting order fulfillment.""" inventory_unit_ids: list[str] = Field( ..., min_length=1, description="List of inventory unit IDs to reject" ) reason: str | None = Field(None, max_length=500, description="Rejection reason") class FulfillmentTrackingRequest(BaseModel): """Schema for setting tracking information.""" tracking_number: str = Field(..., min_length=1, max_length=100) tracking_carrier: str = Field( ..., min_length=1, max_length=100, description="Carrier code (e.g., dhl, ups)" ) class FulfillmentQueueItemResponse(BaseModel): """Schema for fulfillment queue item response.""" model_config = ConfigDict(from_attributes=True) id: int vendor_id: int letzshop_order_id: int operation: str payload: dict[str, Any] status: str attempts: int max_attempts: int last_attempt_at: datetime | None next_retry_at: datetime | None error_message: str | None completed_at: datetime | None response_data: dict[str, Any] | None created_at: datetime updated_at: datetime class FulfillmentQueueListResponse(BaseModel): """Schema for paginated fulfillment queue list.""" items: list[FulfillmentQueueItemResponse] total: int skip: int limit: int # ============================================================================ # Sync Log Schemas # ============================================================================ class LetzshopSyncLogResponse(BaseModel): """Schema for Letzshop sync log response.""" model_config = ConfigDict(from_attributes=True) id: int vendor_id: int operation_type: str direction: str status: str records_processed: int records_succeeded: int records_failed: int error_details: dict[str, Any] | None started_at: datetime completed_at: datetime | None duration_seconds: int | None triggered_by: str | None created_at: datetime class LetzshopSyncLogListResponse(BaseModel): """Schema for paginated sync log list.""" logs: list[LetzshopSyncLogResponse] total: int skip: int limit: int # ============================================================================ # Sync Trigger Schemas # ============================================================================ class LetzshopSyncTriggerRequest(BaseModel): """Schema for triggering a sync operation.""" operation: str = Field( "order_import", pattern="^(order_import|full_sync)$", description="Type of sync operation", ) class LetzshopSyncTriggerResponse(BaseModel): """Schema for sync trigger response.""" success: bool message: str sync_log_id: int | None = None orders_imported: int = 0 orders_updated: int = 0 errors: list[str] = Field(default_factory=list) # ============================================================================ # Connection Test Schemas # ============================================================================ class LetzshopConnectionTestRequest(BaseModel): """Schema for testing Letzshop connection.""" api_key: str = Field(..., min_length=1, description="API key to test") api_endpoint: str | None = Field(None, description="Custom endpoint to test") class LetzshopConnectionTestResponse(BaseModel): """Schema for connection test response.""" success: bool message: str response_time_ms: float | None = None error_details: str | None = None # ============================================================================ # Generic Response Schemas # ============================================================================ class LetzshopSuccessResponse(BaseModel): """Generic success response for Letzshop operations.""" success: bool message: str class FulfillmentOperationResponse(BaseModel): """Response for fulfillment operations (confirm, reject, tracking).""" success: bool message: str confirmed_units: list[str] | None = None tracking_number: str | None = None tracking_carrier: str | None = None errors: list[str] | None = None # ============================================================================ # Admin Overview Schemas # ============================================================================ class LetzshopVendorOverview(BaseModel): """Schema for vendor Letzshop integration overview (admin view).""" vendor_id: int vendor_name: str vendor_code: str is_configured: bool auto_sync_enabled: bool last_sync_at: datetime | None last_sync_status: str | None pending_orders: int total_orders: int class LetzshopVendorListResponse(BaseModel): """Schema for paginated vendor Letzshop overview list.""" vendors: list[LetzshopVendorOverview] total: int skip: int limit: int