Exemple #1
0
    def post(self, request: Request, pk: str):
        """
        Select your preferred rates to buy a shipment label.
        """
        shipment = (models.Shipment.access_by(request).exclude(
            status=ShipmentStatus.cancelled.value).get(pk=pk))

        if shipment.status == ShipmentStatus.purchased.value:
            raise PurplShipApiException(
                f"The shipment is '{shipment.status}' and therefore already {ShipmentStatus.purchased.value}",
                code="state_error",
                status_code=status.HTTP_409_CONFLICT,
            )

        # Submit shipment to carriers
        response: Shipment = (SerializerDecorator[ShipmentPurchaseSerializer](
            context=request,
            data={
                **Shipment(shipment).data,
                **SerializerDecorator[ShipmentPurchaseData](data=request.data).data,
            },
        ).save().instance)

        # Update shipment state
        SerializerDecorator[ShipmentSerializer](shipment,
                                                data=DP.to_dict(response),
                                                context=request).save()
        create_shipment_tracker(shipment, context=request)

        return Response(Shipment(shipment).data)
class ShippingDetails(APIView):
    @swagger_auto_schema(
        tags=['Proxy'],
        operation_id=f"{ENDPOINT_ID}buy_label",
        operation_summary="Buy a shipment label",
        request_body=ShippingRequest(),
        responses={
            200: Shipment(),
            400: ErrorResponse()
        },
    )
    def post(self, request: Request):
        """
        Once the shipping rates are retrieved, provide the required info to
        submit the shipment by specifying your preferred rate.
        """
        payload = SerializerDecorator[ShippingRequestValidation](
            data=request.data).data

        response = Shipments.create(
            payload,
            resolve_tracking_url=(
                lambda tracking_number, carrier_name: reverse(
                    "purplship.server.proxy:shipment-tracking",
                    kwargs=dict(tracking_number=tracking_number,
                                carrier_name=carrier_name))))

        return Response(Shipment(response).data,
                        status=status.HTTP_201_CREATED)
Exemple #3
0
class ShipmentParcels(APIView):
    @swagger_auto_schema(
        tags=["Shipments"],
        operation_id=f"{ENDPOINT_ID}add_parcel",
        operation_summary="Add a shipment parcel",
        responses={
            200: Shipment(),
            400: ErrorResponse()
        },
        request_body=ParcelData(),
    )
    def post(self, request: Request, pk: str):
        """
        Add a parcel to an existing shipment for a multi-parcel shipment.
        """
        shipment = (models.Shipment.access_by(request).exclude(
            status=ShipmentStatus.cancelled.value).get(pk=pk))

        if shipment.status == ShipmentStatus.purchased.value:
            raise PurplShipApiException(
                "Shipment already 'purchased'",
                code="state_error",
                status_code=status.HTTP_409_CONFLICT,
            )

        parcel = (SerializerDecorator[ParcelSerializer](
            data=request.data, context=request).save().instance)
        shipment.shipment_parcels.add(parcel)
        reset_related_shipment_rates(shipment)
        return Response(Shipment(shipment).data)
Exemple #4
0
    def get(self, request: Request, pk: str):
        """
        Retrieve a shipment.
        """
        shipment = models.Shipment.access_by(request).get(pk=pk)

        return Response(Shipment(shipment).data)
Exemple #5
0
    def post(self, request: Request, pk: str):
        """
        Add the customs declaration for the shipment if non existent.
        """
        shipment = (models.Shipment.access_by(request).exclude(
            status=ShipmentStatus.cancelled.value).get(pk=pk))

        if shipment.status == ShipmentStatus.purchased.value:
            raise PurplShipApiException(
                "Shipment already 'purchased'",
                code="state_error",
                status_code=status.HTTP_409_CONFLICT,
            )

        if shipment.customs is not None:
            raise PurplShipApiException(
                "Shipment customs declaration already defined",
                code="state_error",
                status_code=status.HTTP_409_CONFLICT,
            )

        payload: dict = dict(customs=DP.to_dict(request.data),
                             shipment_rates=[],
                             messages=[])

        SerializerDecorator[ShipmentSerializer](shipment,
                                                data=payload,
                                                context=request).save()
        return Response(Shipment(shipment).data)
Exemple #6
0
    def post(self, request: Request, pk: str):
        """
        Add one or many options to your shipment.<br/>
        **eg:**<br/>
        - add shipment **insurance**
        - specify the preferred transaction **currency**
        - setup a **cash collected on delivery** option

        ```json
        {
            "insurance": 120,
            "currency": "USD"
        }
        ```

        And many more, check additional options available in the [reference](#operation/all_references).
        """
        shipment = (models.Shipment.access_by(request).exclude(
            status=ShipmentStatus.cancelled.value).get(pk=pk))

        if shipment.status == ShipmentStatus.purchased.value:
            raise PurplShipApiException(
                "Shipment already 'purchased'",
                code="state_error",
                status_code=status.HTTP_409_CONFLICT,
            )

        payload: dict = dict(options=DP.to_dict(request.data),
                             shipment_rates=[],
                             messages=[])

        SerializerDecorator[ShipmentSerializer](shipment, data=payload).save()

        return Response(Shipment(shipment).data)
Exemple #7
0
 def get(self, request: Request):
     """
     Retrieve all shipments.
     """
     shipments = self.filter_queryset(self.get_queryset())
     response = self.paginate_queryset(Shipment(shipments, many=True).data)
     return self.get_paginated_response(response)
Exemple #8
0
class ShipmentDetail(APIView):
    @swagger_auto_schema(
        tags=["Shipments"],
        operation_id=f"{ENDPOINT_ID}retrieve",
        operation_summary="Retrieve a shipment",
        responses={
            200: Shipment(),
            400: ErrorResponse()
        },
    )
    def get(self, request: Request, pk: str):
        """
        Retrieve a shipment.
        """
        shipment = models.Shipment.access_by(request).get(pk=pk)

        return Response(Shipment(shipment).data)

    @swagger_auto_schema(
        tags=["Shipments"],
        operation_id=f"{ENDPOINT_ID}cancel",
        operation_summary="Cancel a shipment",
        responses={
            200: OperationResponse(),
            400: ErrorResponse()
        },
    )
    def delete(self, request: Request, pk: str):
        """
        Void a shipment with the associated label.
        """
        shipment = models.Shipment.access_by(request).get(pk=pk)

        if shipment.status not in [
                ShipmentStatus.purchased.value,
                ShipmentStatus.created.value,
        ]:
            raise PurplShipApiException(
                f"The shipment is '{shipment.status}' and can therefore not be cancelled anymore...",
                code="state_error",
                status_code=status.HTTP_409_CONFLICT,
            )

        if shipment.pickup_shipments.exists():
            raise PurplShipApiException(
                (f"This shipment is scheduled for pickup '{shipment.pickup_shipments.first().pk}' "
                 "Please cancel this shipment from the pickup before."),
                code="state_error",
                status_code=status.HTTP_409_CONFLICT,
            )

        confirmation = SerializerDecorator[ShipmentCancelSerializer](
            shipment, data={}, context=request).save()
        return Response(OperationResponse(confirmation.instance).data)
Exemple #9
0
    def post(self, request: Request):
        """
        Create a new shipment instance.
        """
        carrier_filter = {
            **SerializerDecorator[ShipmentMode](data=request.query_params).data
        }
        shipment = (SerializerDecorator[ShipmentSerializer](
            data=request.data,
            context=request).save(carrier_filter=carrier_filter).instance)

        return Response(Shipment(shipment).data,
                        status=status.HTTP_201_CREATED)
Exemple #10
0
class ShipmentRates(APIView):
    logging_methods = ["GET"]

    @swagger_auto_schema(
        tags=["Shipments"],
        operation_id=f"{ENDPOINT_ID}rates",
        operation_summary="Fetch new shipment rates",
        responses={
            200: Shipment(),
            400: ErrorResponse()
        },
        request_body=ShipmentRateData(),
    )
    def post(self, request: Request, pk: str):
        """
        Refresh the list of the shipment rates
        """
        shipment = (models.Shipment.access_by(request).exclude(
            status=ShipmentStatus.cancelled.value).get(pk=pk))

        rate_payload = SerializerDecorator[ShipmentRateData](
            data=request.data).data
        carrier_ids = (rate_payload["carrier_ids"] if "carrier_ids"
                       in rate_payload else shipment.carrier_ids)

        carriers = Carriers.list(
            active=True,
            capability="shipping",
            context=request,
            test=shipment.test_mode,
            carrier_ids=carrier_ids,
        )

        rate_response: RateResponse = (SerializerDecorator[RateSerializer](
            context=request,
            data={
                **ShipmentData(shipment).data,
                **rate_payload
            }).save(carriers=carriers).instance)

        SerializerDecorator[ShipmentSerializer](
            shipment,
            context=request,
            data={
                "rates": Rate(rate_response.rates, many=True).data,
                "messages": DP.to_dict(rate_response.messages),
                **rate_payload,
            },
        ).save(carriers=carriers)

        return Response(Shipment(shipment).data)
Exemple #11
0
class ShipmentOptions(APIView):
    @swagger_auto_schema(
        tags=["Shipments"],
        operation_id=f"{ENDPOINT_ID}set_options",
        operation_summary="Add shipment options",
        responses={
            200: Shipment(),
            400: ErrorResponse()
        },
        request_body=openapi.Schema(
            title="options",
            type=openapi.TYPE_OBJECT,
            additional_properties=True,
        ),
    )
    def post(self, request: Request, pk: str):
        """
        Add one or many options to your shipment.<br/>
        **eg:**<br/>
        - add shipment **insurance**
        - specify the preferred transaction **currency**
        - setup a **cash collected on delivery** option

        ```json
        {
            "insurance": 120,
            "currency": "USD"
        }
        ```

        And many more, check additional options available in the [reference](#operation/all_references).
        """
        shipment = (models.Shipment.access_by(request).exclude(
            status=ShipmentStatus.cancelled.value).get(pk=pk))

        if shipment.status == ShipmentStatus.purchased.value:
            raise PurplShipApiException(
                "Shipment already 'purchased'",
                code="state_error",
                status_code=status.HTTP_409_CONFLICT,
            )

        payload: dict = dict(options=DP.to_dict(request.data),
                             shipment_rates=[],
                             messages=[])

        SerializerDecorator[ShipmentSerializer](shipment, data=payload).save()

        return Response(Shipment(shipment).data)
    def post(self, request: Request):
        """
        Once the shipping rates are retrieved, provide the required info to
        submit the shipment by specifying your preferred rate.
        """
        payload = SerializerDecorator[ShippingRequestValidation](
            data=request.data).data

        response = Shipments.create(
            payload,
            resolve_tracking_url=(
                lambda tracking_number, carrier_name: reverse(
                    "purplship.server.proxy:shipment-tracking",
                    kwargs=dict(tracking_number=tracking_number,
                                carrier_name=carrier_name))))

        return Response(Shipment(response).data,
                        status=status.HTTP_201_CREATED)
Exemple #13
0
    def post(self, request: Request, pk: str):
        """
        Add a parcel to an existing shipment for a multi-parcel shipment.
        """
        shipment = (models.Shipment.access_by(request).exclude(
            status=ShipmentStatus.cancelled.value).get(pk=pk))

        if shipment.status == ShipmentStatus.purchased.value:
            raise PurplShipApiException(
                "Shipment already 'purchased'",
                code="state_error",
                status_code=status.HTTP_409_CONFLICT,
            )

        parcel = (SerializerDecorator[ParcelSerializer](
            data=request.data, context=request).save().instance)
        shipment.shipment_parcels.add(parcel)
        reset_related_shipment_rates(shipment)
        return Response(Shipment(shipment).data)
Exemple #14
0
class ShipmentPurchase(APIView):
    @swagger_auto_schema(
        tags=["Shipments"],
        operation_id=f"{ENDPOINT_ID}purchase",
        operation_summary="Buy a shipment label",
        responses={
            200: Shipment(),
            400: ErrorResponse()
        },
        request_body=ShipmentPurchaseData(),
    )
    def post(self, request: Request, pk: str):
        """
        Select your preferred rates to buy a shipment label.
        """
        shipment = (models.Shipment.access_by(request).exclude(
            status=ShipmentStatus.cancelled.value).get(pk=pk))

        if shipment.status == ShipmentStatus.purchased.value:
            raise PurplShipApiException(
                f"The shipment is '{shipment.status}' and therefore already {ShipmentStatus.purchased.value}",
                code="state_error",
                status_code=status.HTTP_409_CONFLICT,
            )

        # Submit shipment to carriers
        response: Shipment = (SerializerDecorator[ShipmentPurchaseSerializer](
            context=request,
            data={
                **Shipment(shipment).data,
                **SerializerDecorator[ShipmentPurchaseData](data=request.data).data,
            },
        ).save().instance)

        # Update shipment state
        SerializerDecorator[ShipmentSerializer](shipment,
                                                data=DP.to_dict(response),
                                                context=request).save()
        create_shipment_tracker(shipment, context=request)

        return Response(Shipment(shipment).data)
Exemple #15
0
class ShipmentCustoms(APIView):
    @swagger_auto_schema(
        tags=["Shipments"],
        operation_id=f"{ENDPOINT_ID}add_customs",
        operation_summary="Add a customs declaration",
        responses={
            200: Shipment(),
            400: ErrorResponse()
        },
        request_body=CustomsData(),
    )
    def post(self, request: Request, pk: str):
        """
        Add the customs declaration for the shipment if non existent.
        """
        shipment = (models.Shipment.access_by(request).exclude(
            status=ShipmentStatus.cancelled.value).get(pk=pk))

        if shipment.status == ShipmentStatus.purchased.value:
            raise PurplShipApiException(
                "Shipment already 'purchased'",
                code="state_error",
                status_code=status.HTTP_409_CONFLICT,
            )

        if shipment.customs is not None:
            raise PurplShipApiException(
                "Shipment customs declaration already defined",
                code="state_error",
                status_code=status.HTTP_409_CONFLICT,
            )

        payload: dict = dict(customs=DP.to_dict(request.data),
                             shipment_rates=[],
                             messages=[])

        SerializerDecorator[ShipmentSerializer](shipment,
                                                data=payload,
                                                context=request).save()
        return Response(Shipment(shipment).data)
Exemple #16
0
    def post(self, request: Request, pk: str):
        """
        Refresh the list of the shipment rates
        """
        shipment = (models.Shipment.access_by(request).exclude(
            status=ShipmentStatus.cancelled.value).get(pk=pk))

        rate_payload = SerializerDecorator[ShipmentRateData](
            data=request.data).data
        carrier_ids = (rate_payload["carrier_ids"] if "carrier_ids"
                       in rate_payload else shipment.carrier_ids)

        carriers = Carriers.list(
            active=True,
            capability="shipping",
            context=request,
            test=shipment.test_mode,
            carrier_ids=carrier_ids,
        )

        rate_response: RateResponse = (SerializerDecorator[RateSerializer](
            context=request,
            data={
                **ShipmentData(shipment).data,
                **rate_payload
            }).save(carriers=carriers).instance)

        SerializerDecorator[ShipmentSerializer](
            shipment,
            context=request,
            data={
                "rates": Rate(rate_response.rates, many=True).data,
                "messages": DP.to_dict(rate_response.messages),
                **rate_payload,
            },
        ).save(carriers=carriers)

        return Response(Shipment(shipment).data)
Exemple #17
0
class ShipmentList(GenericAPIView):
    pagination_class = type("CustomPagination", (LimitOffsetPagination, ),
                            dict(default_limit=20))
    filter_backends = (filters.DjangoFilterBackend, )
    filterset_class = ShipmentFilters
    model = models.Shipment

    def get_queryset(self):
        queryset = super().get_queryset()
        _filters = tuple()
        query_params = getattr(self.request, "query_params", {})
        carrier_name = query_params.get("carrier_name")

        if carrier_name is not None:
            _filters += (
                Q(meta__rate_provider=carrier_name)
                | Q(
                    **{
                        f"selected_rate_carrier__{carrier_name}settings__isnull":
                        False
                    }), )

        return queryset.filter(*_filters)

    @swagger_auto_schema(
        tags=["Shipments"],
        operation_id=f"{ENDPOINT_ID}list",
        operation_summary="List all shipments",
        responses={
            200: Shipments(),
            400: ErrorResponse()
        },
        manual_parameters=ShipmentFilters.parameters,
    )
    def get(self, request: Request):
        """
        Retrieve all shipments.
        """
        shipments = self.filter_queryset(self.get_queryset())
        response = self.paginate_queryset(Shipment(shipments, many=True).data)
        return self.get_paginated_response(response)

    @swagger_auto_schema(
        tags=["Shipments"],
        operation_id=f"{ENDPOINT_ID}create",
        operation_summary="Create a shipment",
        responses={
            200: Shipment(),
            400: ErrorResponse()
        },
        request_body=ShipmentData(),
        query_serializer=ShipmentMode(),
    )
    def post(self, request: Request):
        """
        Create a new shipment instance.
        """
        carrier_filter = {
            **SerializerDecorator[ShipmentMode](data=request.query_params).data
        }
        shipment = (SerializerDecorator[ShipmentSerializer](
            data=request.data,
            context=request).save(carrier_filter=carrier_filter).instance)

        return Response(Shipment(shipment).data,
                        status=status.HTTP_201_CREATED)