from rest_framework import serializers from rest_framework.exceptions import ValidationError from .models import DeutschePostOrder, DeutschePostBulkOrder class DeutschePostOrderSerializer(serializers.ModelSerializer): state_display = serializers.CharField(source='get_state_display', read_only=True) label_size_display = serializers.CharField(source='get_label_size_display', read_only=True) tracking_url = serializers.SerializerMethodField(read_only=True) estimated_delivery_days = serializers.SerializerMethodField(read_only=True) shipping_cost_estimate = serializers.SerializerMethodField(read_only=True) can_be_finalized = serializers.SerializerMethodField(read_only=True) can_be_cancelled = serializers.SerializerMethodField(read_only=True) is_trackable = serializers.SerializerMethodField(read_only=True) class Meta: model = DeutschePostOrder fields = [ 'id', 'created_at', 'state', 'state_display', 'order_id', 'customer_ekp', 'recipient_name', 'recipient_phone', 'recipient_email', 'address_line1', 'address_line2', 'address_line3', 'city', 'address_state', 'postal_code', 'destination_country', 'product_type', 'service_level', 'shipment_gross_weight', 'shipment_amount', 'shipment_currency', 'sender_tax_id', 'importer_tax_id', 'return_item_wanted', 'cust_ref', 'awb_number', 'barcode', 'tracking_url', 'label_pdf', 'label_size', 'label_size_display', 'metadata', 'last_error', 'estimated_delivery_days', 'shipping_cost_estimate', 'can_be_finalized', 'can_be_cancelled', 'is_trackable' ] read_only_fields = [ 'id', 'created_at', 'order_id', 'awb_number', 'barcode', 'tracking_url', 'metadata', 'last_error', 'label_pdf' ] def get_tracking_url(self, obj): return obj.get_tracking_url() def get_estimated_delivery_days(self, obj): return obj.get_estimated_delivery_days() def get_shipping_cost_estimate(self, obj): return float(obj.get_shipping_cost_estimate()) def get_can_be_finalized(self, obj): return obj.can_be_finalized() def get_can_be_cancelled(self, obj): return obj.can_be_cancelled() def get_is_trackable(self, obj): return obj.is_trackable() def validate_destination_country(self, value): """Validate country code format.""" if len(value) != 2: raise ValidationError("Country code must be 2 characters (ISO format)") return value.upper() def validate_shipment_gross_weight(self, value): """Validate weight is reasonable.""" if value <= 0: raise ValidationError("Shipment weight must be greater than 0") if value > 30000: # 30kg limit for most Deutsche Post products raise ValidationError("Shipment weight cannot exceed 30kg (30000g)") return value def validate(self, attrs): """Validate the complete order data.""" # Check required fields for shipping required_fields = ['recipient_name', 'address_line1', 'city', 'postal_code', 'destination_country'] missing_fields = [field for field in required_fields if not attrs.get(field)] if missing_fields: raise ValidationError(f"Required fields missing: {', '.join(missing_fields)}") # Check contact information if not attrs.get('recipient_email') and not attrs.get('recipient_phone'): raise ValidationError("Either recipient email or phone is required for delivery notifications") return attrs class DeutschePostBulkOrderSerializer(serializers.ModelSerializer): deutschepost_order_ids = serializers.ListField( child=serializers.IntegerField(), write_only=True, required=True, help_text="List of DeutschePostOrder IDs to include in bulk order" ) status_display = serializers.CharField(source='get_status_display', read_only=True) orders_count = serializers.SerializerMethodField(read_only=True) total_weight_kg = serializers.SerializerMethodField(read_only=True) tracking_url = serializers.SerializerMethodField(read_only=True) status_url = serializers.SerializerMethodField(read_only=True) can_be_cancelled = serializers.SerializerMethodField(read_only=True) class Meta: model = DeutschePostBulkOrder fields = [ 'id', 'created_at', 'status', 'status_display', 'bulk_order_id', 'bulk_order_type', 'description', 'deutschepost_orders', 'deutschepost_order_ids', 'orders_count', 'total_weight_kg', 'tracking_url', 'status_url', 'can_be_cancelled', 'bulk_label_pdf', 'paperwork_pdf', 'metadata', 'last_error' ] read_only_fields = [ 'id', 'created_at', 'bulk_order_id', 'deutschepost_orders', 'bulk_label_pdf', 'paperwork_pdf', 'metadata', 'last_error' ] def get_orders_count(self, obj): return obj.deutschepost_orders.count() def get_total_weight_kg(self, obj): return obj.get_total_weight_kg() def get_tracking_url(self, obj): return obj.get_tracking_url() def get_status_url(self, obj): return obj.get_bulk_status_url() def get_can_be_cancelled(self, obj): return obj.can_be_cancelled() def validate_deutschepost_order_ids(self, value): """Validate that all orders exist and are eligible for bulk processing.""" if not value: raise ValidationError("At least one Deutsche Post order is required") errors = [] orders = DeutschePostOrder.objects.filter(id__in=value) if len(orders) != len(value): missing_ids = set(value) - set(orders.values_list('id', flat=True)) errors.append(f"Orders not found: {missing_ids}") for order in orders: # Check if order is created remotely if not order.order_id: errors.append(f"Order {order.id} not created remotely") # Check if commerce order uses Deutsche Post if order.commerce_order: if not order.commerce_order.carrier: errors.append(f"Commerce order {order.commerce_order.id} has no carrier") elif order.commerce_order.carrier.shipping_method != "deutschepost": errors.append(f"Commerce order {order.commerce_order.id} doesn't use Deutsche Post") elif order.commerce_order.status != order.commerce_order.OrderStatus.COMPLETED: errors.append(f"Commerce order {order.commerce_order.id} is not completed") # Check if order is already in another bulk order if order.bulk_orders.exists(): errors.append(f"Order {order.id} already in bulk order") if errors: raise ValidationError(errors) return value def create(self, validated_data): order_ids = validated_data.pop('deutschepost_order_ids') bulk_order = super().create(validated_data) # Add orders to bulk order orders = DeutschePostOrder.objects.filter(id__in=order_ids) bulk_order.deutschepost_orders.set(orders) return bulk_order class DeutschePostTrackingSerializer(serializers.Serializer): """Serializer for tracking information response.""" order_id = serializers.CharField(read_only=True) awb_number = serializers.CharField(read_only=True) barcode = serializers.CharField(read_only=True) tracking_url = serializers.URLField(read_only=True) state = serializers.CharField(read_only=True) last_updated = serializers.DateTimeField(read_only=True) tracking_events = serializers.JSONField(read_only=True)