Major refactor of commerce and Stripe integration

Refactored commerce models to support refunds, invoices, and improved carrier/payment logic. Added new serializers and viewsets for products, categories, images, discount codes, and refunds. Introduced Stripe client integration and removed legacy Stripe admin/model code. Updated Dockerfile for PDF generation dependencies. Removed obsolete migration files and updated configuration app initialization. Added invoice template and tasks for order cleanup.
This commit is contained in:
2025-11-18 01:00:03 +01:00
parent 7a715efeda
commit b8a1a594b2
35 changed files with 1215 additions and 332 deletions

View File

@@ -21,10 +21,11 @@ from django.db import models
from django.utils import timezone
from django.core.validators import RegexValidator
from django.core.files.base import ContentFile
from django.apps import apps
from .client import PacketaAPI
from commerce.models import Order, Carrier
from configuration.models import Configuration
from configuration.models import ShopConfiguration
packeta_client = PacketaAPI() # single reusable instance
@@ -55,6 +56,13 @@ class ZasilkovnaPacket(models.Model):
help_text="Hmotnost zásilky v gramech"
)
# 🚚 návratové směrovací kódy (pro vrácení zásilky)
return_routing = models.JSONField(
default=list,
blank=True,
help_text="Seznam 2 routing stringů pro vrácení zásilky"
)
class PDF_SIZE(models.TextChoices):
A6_ON_A6 = ("A6 on A6", "105x148 mm (A6) label on a page of the same size")
A7_ON_A7 = ("A7 on A7", "105x74 mm (A7) label on a page of the same size")
@@ -63,19 +71,16 @@ class ZasilkovnaPacket(models.Model):
A8_ON_A8 = ("A8 on A8", "50x74 mm (A8) label on a page of the same size")
size_of_pdf = models.CharField(max_length=20, choices=PDF_SIZE.choices, default=PDF_SIZE.A6_ON_A6)
# 🚚 návratové směrovací kódy (pro vrácení zásilky)
return_routing = models.JSONField(
default=list,
blank=True,
help_text="Seznam 2 routing stringů pro vrácení zásilky"
)
def save(self, *args, **kwargs):
# On first save, create the packet remotely if packet_id is not set
# workaroud to avoid circular import
Carrier = apps.get_model('commerce', 'Carrier')
Order = apps.get_model('commerce', 'Order')
carrier = Carrier.objects.get(zasilkovna=self)
order = Order.objects.get(carrier=carrier)
cash_on_delivery = order.payment.payment_method == order.payment.PAYMENT.CASH_ON_DELIVERY
if not self.packet_id:
response = packeta_client.create_packet(
address_id=self.addressId,
@@ -85,14 +90,13 @@ class ZasilkovnaPacket(models.Model):
surname=order.last_name,
company=order.company,
email=order.email,
addressId=Configuration.get_solo().zasilkovna_address_id,
addressId=ShopConfiguration.get_solo().zasilkovna_address_id,
#FIXME: udělat logiku pro počítaní dobírky a hodnoty zboží
cod=100.00,
value=100.00,
cod=order.total_price if cash_on_delivery else 0, # dobírka
value=order.total_price,
currency=Configuration.get_solo().currency,
eshop= Configuration.get_solo().name,
currency=ShopConfiguration.get_solo().currency,
eshop= ShopConfiguration.get_solo().name,
)
self.packet_id = response['packet_id']
self.barcode = response['barcode']

View File

@@ -0,0 +1,38 @@
from rest_framework import serializers
from .models import ZasilkovnaPacket, ZasilkovnaShipment
class ZasilkovnaPacketSerializer(serializers.ModelSerializer):
class Meta:
model = ZasilkovnaPacket
fields = [
"id",
"created_at",
"packet_id",
"barcode",
"state",
"weight",
"return_routing",
]
read_only_fields = fields
class TrackingURLSerializer(serializers.Serializer):
barcode = serializers.CharField(read_only=True)
tracking_url = serializers.URLField(read_only=True)
class ZasilkovnaShipmentSerializer(serializers.ModelSerializer):
packets = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = ZasilkovnaShipment
fields = [
"id",
"created_at",
"shipment_id",
"barcode",
"packets",
]
read_only_fields = fields

View File

@@ -0,0 +1,15 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ZasilkovnaShipmentViewSet, ZasilkovnaPacketViewSet
router = DefaultRouter()
router.register(r"shipments", ZasilkovnaShipmentViewSet, basename="zasilkovna-shipment")
router.register(r"packets", ZasilkovnaPacketViewSet, basename="zasilkovna-packet")
app_name = "zasilkovna"
urlpatterns = [
path("", include(router.urls)),
]

View File

@@ -1,8 +1,80 @@
#views.py
from rest_framework import viewsets, mixins, status
from rest_framework.decorators import action
from rest_framework.response import Response
"""
TODO: OBJEDNAVANÍ SE VYVOLÁVA V CARRIER V COMMERCE.MODELS.PY
získaní labelu,
info o kurýrovi, vracení balíku,
vytvoření hromadné expedice
"""
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiResponse
from .models import ZasilkovnaShipment, ZasilkovnaPacket
from .serializers import (
ZasilkovnaShipmentSerializer,
ZasilkovnaPacketSerializer,
TrackingURLSerializer,
)
@extend_schema_view(
list=extend_schema(
tags=["Zásilkovna"],
summary="List shipments",
description="Returns a paginated list of Packeta (Zásilkovna) shipments.",
responses={200: ZasilkovnaShipmentSerializer},
),
retrieve=extend_schema(
tags=["Zásilkovna"],
summary="Retrieve a shipment",
description="Returns detail for a single shipment.",
responses={200: ZasilkovnaShipmentSerializer},
),
)
class ZasilkovnaShipmentViewSet(viewsets.ReadOnlyModelViewSet):
queryset = ZasilkovnaShipment.objects.all().order_by("-created_at")
serializer_class = ZasilkovnaShipmentSerializer
@extend_schema_view(
retrieve=extend_schema(
tags=["Zásilkovna"],
summary="Retrieve a packet",
description="Returns detail for a single packet.",
responses={200: ZasilkovnaPacketSerializer},
)
)
class ZasilkovnaPacketViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
queryset = ZasilkovnaPacket.objects.all()
serializer_class = ZasilkovnaPacketSerializer
@extend_schema(
tags=["Zásilkovna"],
summary="Get public tracking URL",
description=(
"Returns the public Zásilkovna tracking URL derived from the packet's barcode."
),
responses={200: OpenApiResponse(response=TrackingURLSerializer)},
)
@action(detail=True, methods=["get"], url_path="tracking-url")
def tracking_url(self, request, pk=None):
packet: ZasilkovnaPacket = self.get_object()
data = {
"barcode": packet.barcode,
"tracking_url": packet.get_tracking_url(),
}
return Response(data)
@extend_schema(
tags=["Zásilkovna"],
summary="Cancel packet",
description=(
"Cancels the packet through the Packeta API and updates its state to CANCELED. "
"No request body is required."
),
request=None,
responses={200: OpenApiResponse(response=ZasilkovnaPacketSerializer)},
)
@action(detail=True, methods=["patch"], url_path="cancel")
def cancel(self, request, pk=None):
packet: ZasilkovnaPacket = self.get_object()
packet.cancel_packet()
serializer = self.get_serializer(packet)
return Response(serializer.data, status=status.HTTP_200_OK)