Refactor commerce models and add configuration app
Major refactor of commerce models: restructured Carrier, Payment, and DiscountCode models, improved order total calculation, and integrated Zasilkovna and Stripe logic. Added new configuration Django app for shop settings, updated Zasilkovna and Stripe models, and fixed Zasilkovna client WSDL URL. Removed unused serializers and views in commerce, and registered new apps in settings.
This commit is contained in:
@@ -1,11 +1,14 @@
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
#from django.utils.translation import gettext_lazy as _
|
||||
from decimal import Decimal
|
||||
|
||||
from thirdparty.zasilkovna.models import PacketaShipment
|
||||
from configuration.models import ShopConfiguration
|
||||
from thirdparty.zasilkovna.models import ZasilkovnaPacket
|
||||
from thirdparty.stripe.models import StripePayment
|
||||
|
||||
#FIXME: přidat soft delete pro všchny modely !!!!
|
||||
|
||||
class Category(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
@@ -77,31 +80,175 @@ class ProductImage(models.Model):
|
||||
return f"{self.product.name} image"
|
||||
|
||||
|
||||
# ------------------ OBJENDÁVKOVÉ MODELY (dole) ------------------
|
||||
|
||||
# ------------------ OBJEDNÁVKY ------------------
|
||||
|
||||
# Dopravci a způsoby dopravy
|
||||
class Carrier(models.Model):
|
||||
class Order(models.Model):
|
||||
class Status(models.TextChoices):
|
||||
PENDING = "pending", _("Čeká na platbu")
|
||||
PAID = "paid", _("Zaplaceno")
|
||||
CANCELLED = "cancelled", _("Zrušeno")
|
||||
SHIPPED = "shipped", _("Odesláno")
|
||||
#COMPLETED = "completed", _("Dokončeno")
|
||||
|
||||
class Role(models.TextChoices):
|
||||
ZASILKOVNA = "packeta", "Zásilkovna"
|
||||
STORE = "store", "Osobní odběr"
|
||||
|
||||
choice = models.CharField(max_length=20, choices=Role.choices, default=Role.STORE)
|
||||
|
||||
# prodejce to přidá později
|
||||
zasilkovna = models.ForeignKey(
|
||||
'thirdparty.zasilkovna.Zasilkovna', on_delete=models.DO_NOTHING, null=True, blank=True, related_name="carriers"
|
||||
status = models.CharField(
|
||||
max_length=20, choices=Status.choices, default=Status.PENDING
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} ({self.base_price} Kč)"
|
||||
# Stored order grand total; recalculated on save
|
||||
total_price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
|
||||
currency = models.CharField(max_length=10, default="CZK")
|
||||
|
||||
# fakturační údaje (zkopírované z user profilu při objednávce) FIXME: rozhodnout se co dát do on_delete
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING, related_name="orders", null=True, blank=True
|
||||
)
|
||||
|
||||
first_name = models.CharField(max_length=100)
|
||||
last_name = models.CharField(max_length=100)
|
||||
email = models.EmailField()
|
||||
phone = models.CharField(max_length=20, blank=True)
|
||||
address = models.CharField(max_length=255)
|
||||
city = models.CharField(max_length=100)
|
||||
postal_code = models.CharField(max_length=20)
|
||||
country = models.CharField(max_length=100, default="Czech Republic")
|
||||
|
||||
note = models.TextField(blank=True)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
carrier = models.OneToOneField(
|
||||
"Carrier",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="orders",
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
|
||||
payment = models.OneToOneField(
|
||||
"Payment",
|
||||
on_delete=models.CASCADE,
|
||||
related_name="orders",
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
|
||||
discount = models.ManyToManyField("DiscountCode", blank=True, related_name="orders")
|
||||
|
||||
def calculate_total_price(self):
|
||||
carrier_price = self.carrier.get_price() if self.carrier else Decimal("0.0")
|
||||
|
||||
if self.discount.exists():
|
||||
for discount in self.discount.all():
|
||||
total = Decimal('0.0')
|
||||
|
||||
# getting all prices from order items (with discount applied if valid)
|
||||
for item in self.items.all():
|
||||
total = total + item.get_total_price(discount)
|
||||
|
||||
return total + carrier_price
|
||||
else:
|
||||
total = Decimal('0.0')
|
||||
# getting all prices from order items (without discount)
|
||||
|
||||
for item in self.items.all():
|
||||
total = total + (item.product.price * item.quantity)
|
||||
|
||||
return total + carrier_price
|
||||
|
||||
def import_data_from_user(self):
|
||||
"""Import user data into order for billing purposes."""
|
||||
self.first_name = self.user.first_name
|
||||
self.last_name = self.user.last_name
|
||||
self.email = self.user.email
|
||||
self.phone = self.user.phone
|
||||
self.address = f"{self.user.street} {self.user.street_number}"
|
||||
self.city = self.user.city
|
||||
self.postal_code = self.user.postal_code
|
||||
self.country = self.user.country
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# Keep total_price always in sync with items and discount
|
||||
self.total_price = self.calculate_total_price()
|
||||
|
||||
if self.user and self.pk is None:
|
||||
self.import_data_from_user()
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
# ------------------ DOPRAVCI A ZPŮSOBY DOPRAVY ------------------
|
||||
class Carrier(models.Model):
|
||||
class SHIPPING(models.TextChoices):
|
||||
ZASILKOVNA = "packeta", "Zásilkovna"
|
||||
STORE = "store", "Osobní odběr"
|
||||
shipping_method = models.CharField(max_length=20, choices=SHIPPING.choices, default=SHIPPING.STORE)
|
||||
|
||||
# prodejce to přidá později
|
||||
zasilkovna = models.ForeignKey(
|
||||
ZasilkovnaPacket, on_delete=models.DO_NOTHING, null=True, blank=True, related_name="carriers"
|
||||
)
|
||||
|
||||
weight = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, help_text="Hmotnost zásilky v kg")
|
||||
|
||||
returning = models.BooleanField(default=False, help_text="Zda je tato zásilka na vrácení")
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def get_price(self):
|
||||
if self.shipping_method == self.SHIPPING.ZASILKOVNA:
|
||||
return ShopConfiguration.get_solo().zasilkovna_shipping_price
|
||||
else:
|
||||
return Decimal('0.0')
|
||||
|
||||
|
||||
#tohle bude vyvoláno pomocí admina přes api!!!
|
||||
def start_ordering_shipping(self):
|
||||
if self.shipping_method == self.SHIPPING.ZASILKOVNA:
|
||||
#už při vytvoření se volá na api Zásilkovny
|
||||
self.zasilkovna = ZasilkovnaPacket.objects.create()
|
||||
|
||||
#... další logika pro jiné způsoby dopravy
|
||||
#TODO: přidat notifikace uživateli, jak pro zásilkovnu, tak pro vyzvednutí v obchodě!
|
||||
|
||||
def returning_shipping(self):
|
||||
self.returning = True
|
||||
|
||||
if self.shipping_method == self.SHIPPING.ZASILKOVNA:
|
||||
#volá se na api Zásilkovny
|
||||
self.zasilkovna.returning_packet()
|
||||
|
||||
|
||||
# ------------------ PLATEBNÍ MODELY ------------------
|
||||
|
||||
class Payment(models.Model):
|
||||
class PAYMENT(models.TextChoices):
|
||||
SHOP = "shop", "Platba v obchodě"
|
||||
STRIPE = "stripe", "Bankovní převod"
|
||||
CASH_ON_DELIVERY = "cash_on_delivery", "Dobírka"
|
||||
payment_method = models.CharField(max_length=30, choices=PAYMENT.choices, default=PAYMENT.SHOP)
|
||||
|
||||
#active = models.BooleanField(default=True)
|
||||
|
||||
#FIXME: potvrdit že logika platby funguje správně
|
||||
#veškera logika a interakce bude na stripu (třeba aktualizovaní objednávky na zaplacenou apod.)
|
||||
stripe = models.OneToOneField(
|
||||
StripePayment, on_delete=models.CASCADE, null=True, blank=True, related_name="payment"
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
order = Order.objects.get(payment=self)
|
||||
|
||||
if self.payment_method == self.PAYMENT.SHOP and Carrier.objects.get(orders=order).shipping_method != Carrier.SHIPPING.STORE:
|
||||
raise ValueError("Platba v obchodě je možná pouze pro osobní odběr.")
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
# ------------------ SLEVOVÉ KÓDY ------------------
|
||||
|
||||
class DiscountCode(models.Model):
|
||||
code = models.CharField(max_length=50, unique=True)
|
||||
@@ -139,102 +286,17 @@ class DiscountCode(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.code} ({self.percent}% or {self.amount} CZK)"
|
||||
|
||||
|
||||
|
||||
class Order(models.Model):
|
||||
class Status(models.TextChoices):
|
||||
PENDING = "pending", _("Čeká na platbu")
|
||||
PAID = "paid", _("Zaplaceno")
|
||||
CANCELLED = "cancelled", _("Zrušeno")
|
||||
SHIPPED = "shipped", _("Odesláno")
|
||||
#COMPLETED = "completed", _("Dokončeno")
|
||||
|
||||
status = models.CharField(
|
||||
max_length=20, choices=Status.choices, default=Status.PENDING
|
||||
)
|
||||
|
||||
carrier = models.ForeignKey(
|
||||
Carrier, on_delete=models.CASCADE, null=True, blank=True, related_name="orders"
|
||||
)
|
||||
|
||||
#itemy
|
||||
order_items = models.ManyToManyField(
|
||||
'OrderItem', related_name='orders',
|
||||
)
|
||||
|
||||
# Stored order grand total; recalculated on save
|
||||
total_price = models.DecimalField(max_digits=10, decimal_places=2, default=0)
|
||||
currency = models.CharField(max_length=10, default="CZK")
|
||||
|
||||
# fakturační údaje (zkopírované z user profilu při objednávce)
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING, related_name="orders", null=True, blank=True
|
||||
)
|
||||
|
||||
first_name = models.CharField(max_length=100)
|
||||
last_name = models.CharField(max_length=100)
|
||||
email = models.EmailField()
|
||||
phone = models.CharField(max_length=20, blank=True)
|
||||
address = models.CharField(max_length=255)
|
||||
city = models.CharField(max_length=100)
|
||||
postal_code = models.CharField(max_length=20)
|
||||
country = models.CharField(max_length=100, default="Czech Republic")
|
||||
|
||||
note = models.TextField(blank=True)
|
||||
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
# Oprava: ManyToOneRel není pole. Potřebujeme iterovat přes self.discount.all(), proto ManyToManyField.
|
||||
discount = models.ManyToManyField("DiscountCode", blank=True, related_name="orders")
|
||||
|
||||
def __str__(self):
|
||||
return f"Order #{self.id} - {self.user.email} ({self.status})"
|
||||
|
||||
def calculate_total_price(self):
|
||||
if self.discount.exists():
|
||||
for discount in self.discount.all():
|
||||
total = Decimal('0.0')
|
||||
|
||||
# getting all prices from order items (with discount applied if valid)
|
||||
for item in self.items.all():
|
||||
total = total + item.get_total_price(discount)
|
||||
|
||||
return total
|
||||
else:
|
||||
total = Decimal('0.0')
|
||||
# getting all prices from order items (without discount)
|
||||
|
||||
for item in self.items.all():
|
||||
total = total + (item.product.price * item.quantity)
|
||||
|
||||
return total
|
||||
|
||||
def import_data_from_user(self):
|
||||
"""Import user data into order for billing purposes."""
|
||||
self.first_name = self.user.first_name
|
||||
self.last_name = self.user.last_name
|
||||
self.email = self.user.email
|
||||
self.phone = self.user.phone
|
||||
self.address = f"{self.user.street} {self.user.street_number}"
|
||||
self.city = self.user.city
|
||||
self.postal_code = self.user.postal_code
|
||||
self.country = self.user.country
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# Keep total_price always in sync with items and discount
|
||||
self.total_price = self.calculate_total_price()
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
# ------------------ OBJEDNANÉ POLOŽKY ------------------
|
||||
|
||||
class OrderItem(models.Model):
|
||||
order = models.ForeignKey(Order, related_name="items", on_delete=models.CASCADE)
|
||||
product = models.ForeignKey("products.Product", on_delete=models.PROTECT)
|
||||
quantity = models.PositiveIntegerField(default=1)
|
||||
|
||||
def get_total_price(self, discount_object:DiscountCode):
|
||||
def get_total_price(self, discount_object:DiscountCode = None):
|
||||
#FIXME: přidat logiku pro slevové kódy
|
||||
"""Calculate total price for this item, applying discount if valid."""
|
||||
|
||||
if discount_object and discount_object.is_valid():
|
||||
@@ -253,20 +315,7 @@ class OrderItem(models.Model):
|
||||
return self.quantity * self.product.price
|
||||
|
||||
else:
|
||||
return ValueError("Invalid discount code.")
|
||||
raise ValueError("Invalid discount code.")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.product.name} x{self.quantity}"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Returning_order(models.Model):
|
||||
#FIXME: dodělat !!!
|
||||
order = models.ForeignKey(Order, related_name="returning_orders", on_delete=models.CASCADE)
|
||||
reason = models.TextField(blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
def __str__(self):
|
||||
return f"Returning Order #{self.order.id} - {self.created_at.strftime('%Y-%m-%d')}"
|
||||
return f"{self.product.name} x{self.quantity}"
|
||||
@@ -2,89 +2,3 @@ from rest_framework import serializers
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
from .models import Category, Product, ProductImage, DiscountCode, Order, OrderItem, Carrier
|
||||
|
||||
# NOTE: Carrier intentionally skipped per request (TODO below)
|
||||
|
||||
class CategorySerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Category
|
||||
fields = ["id", "name", "url", "parent", "description", "image"]
|
||||
|
||||
|
||||
class ProductImageSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ProductImage
|
||||
fields = ["id", "image", "alt_text", "is_main"]
|
||||
|
||||
|
||||
class ProductSerializer(serializers.ModelSerializer):
|
||||
images = ProductImageSerializer(many=True, read_only=True)
|
||||
available = serializers.BooleanField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Product
|
||||
fields = [
|
||||
"id","name","description","code","category","price","currency","url","stock","is_active","limited_to","default_carrier","available","images","created_at","updated_at"
|
||||
]
|
||||
read_only_fields = ["created_at","updated_at","available"]
|
||||
|
||||
|
||||
class DiscountCodeSerializer(serializers.ModelSerializer):
|
||||
is_valid = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = DiscountCode
|
||||
fields = ["id","code","description","percent","amount","valid_from","valid_to","active","usage_limit","used_count","specific_products","specific_categories","is_valid"]
|
||||
read_only_fields = ["used_count","is_valid"]
|
||||
|
||||
def get_is_valid(self, obj):
|
||||
return obj.is_valid()
|
||||
|
||||
|
||||
class OrderItemSerializer(serializers.ModelSerializer):
|
||||
product = serializers.PrimaryKeyRelatedField(queryset=Product.objects.all())
|
||||
line_total = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = OrderItem
|
||||
fields = ["id","product","quantity","line_total"]
|
||||
read_only_fields = ["line_total"]
|
||||
|
||||
def get_line_total(self, obj):
|
||||
# Uses existing model logic for discount via order context (kept minimal)
|
||||
# Since discount resolution logic is custom & currently incomplete, just returns base price * qty
|
||||
return obj.product.price * obj.quantity
|
||||
|
||||
|
||||
class OrderSerializer(serializers.ModelSerializer):
|
||||
items = OrderItemSerializer(many=True)
|
||||
total_price = serializers.DecimalField(max_digits=10, decimal_places=2, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Order
|
||||
fields = [
|
||||
"id","user","status","total_price","currency","first_name","last_name","email","phone","address","city","postal_code","country","note","discount","items","created_at","updated_at"
|
||||
]
|
||||
read_only_fields = ["total_price","created_at","updated_at"]
|
||||
|
||||
def create(self, validated_data):
|
||||
items_data = validated_data.pop("items", [])
|
||||
order = Order.objects.create(**validated_data)
|
||||
for item in items_data:
|
||||
OrderItem.objects.create(order=order, **item)
|
||||
order.total_price = order.calculate_total_price() if hasattr(order, "calculate_total_price") else order.total_price
|
||||
order.save()
|
||||
return order
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
items_data = validated_data.pop("items", None)
|
||||
for attr, value in validated_data.items():
|
||||
setattr(instance, attr, value)
|
||||
if items_data is not None:
|
||||
instance.items.all().delete()
|
||||
for item in items_data:
|
||||
OrderItem.objects.create(order=instance, **item)
|
||||
instance.total_price = instance.calculate_total_price() if hasattr(instance, "calculate_total_price") else instance.total_price
|
||||
instance.save()
|
||||
return instance
|
||||
|
||||
# TODO: CarrierSerializer (Carrier API not requested yet)
|
||||
@@ -2,70 +2,3 @@ from rest_framework import viewsets
|
||||
from rest_framework.permissions import AllowAny
|
||||
from drf_spectacular.utils import extend_schema, extend_schema_view
|
||||
|
||||
from .models import Category, Product, ProductImage, DiscountCode, Order, OrderItem, Carrier
|
||||
from .serializers import (
|
||||
CategorySerializer,
|
||||
ProductSerializer,
|
||||
ProductImageSerializer,
|
||||
DiscountCodeSerializer,
|
||||
OrderSerializer,
|
||||
)
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(tags=["Commerce", "Categories"], summary="List categories"),
|
||||
retrieve=extend_schema(tags=["Commerce", "Categories"], summary="Retrieve category"),
|
||||
create=extend_schema(tags=["Commerce", "Categories"], summary="Create category"),
|
||||
update=extend_schema(tags=["Commerce", "Categories"], summary="Update category"),
|
||||
partial_update=extend_schema(tags=["Commerce", "Categories"], summary="Partial update category"),
|
||||
destroy=extend_schema(tags=["Commerce", "Categories"], summary="Delete category"),
|
||||
)
|
||||
class CategoryViewSet(viewsets.ModelViewSet):
|
||||
queryset = Category.objects.all()
|
||||
serializer_class = CategorySerializer
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(tags=["Commerce", "Products"], summary="List products"),
|
||||
retrieve=extend_schema(tags=["Commerce", "Products"], summary="Retrieve product"),
|
||||
create=extend_schema(tags=["Commerce", "Products"], summary="Create product"),
|
||||
update=extend_schema(tags=["Commerce", "Products"], summary="Update product"),
|
||||
partial_update=extend_schema(tags=["Commerce", "Products"], summary="Partial update product"),
|
||||
destroy=extend_schema(tags=["Commerce", "Products"], summary="Delete product"),
|
||||
)
|
||||
class ProductViewSet(viewsets.ModelViewSet):
|
||||
queryset = Product.objects.all()
|
||||
serializer_class = ProductSerializer
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(tags=["Commerce", "Discounts"], summary="List discount codes"),
|
||||
retrieve=extend_schema(tags=["Commerce", "Discounts"], summary="Retrieve discount code"),
|
||||
create=extend_schema(tags=["Commerce", "Discounts"], summary="Create discount code"),
|
||||
update=extend_schema(tags=["Commerce", "Discounts"], summary="Update discount code"),
|
||||
partial_update=extend_schema(tags=["Commerce", "Discounts"], summary="Partial update discount code"),
|
||||
destroy=extend_schema(tags=["Commerce", "Discounts"], summary="Delete discount code"),
|
||||
)
|
||||
class DiscountCodeViewSet(viewsets.ModelViewSet):
|
||||
queryset = DiscountCode.objects.all()
|
||||
serializer_class = DiscountCodeSerializer
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
|
||||
@extend_schema_view(
|
||||
list=extend_schema(tags=["Commerce", "Orders"], summary="List orders"),
|
||||
retrieve=extend_schema(tags=["Commerce", "Orders"], summary="Retrieve order"),
|
||||
create=extend_schema(tags=["Commerce", "Orders"], summary="Create order"),
|
||||
update=extend_schema(tags=["Commerce", "Orders"], summary="Update order"),
|
||||
partial_update=extend_schema(tags=["Commerce", "Orders"], summary="Partial update order"),
|
||||
destroy=extend_schema(tags=["Commerce", "Orders"], summary="Delete order"),
|
||||
)
|
||||
class OrderViewSet(viewsets.ModelViewSet):
|
||||
queryset = Order.objects.all()
|
||||
serializer_class = OrderSerializer
|
||||
permission_classes = [AllowAny]
|
||||
|
||||
|
||||
# TODO: CarrierViewSet & CarrierSerializer when requested
|
||||
|
||||
0
backend/configuration/__init__.py
Normal file
0
backend/configuration/__init__.py
Normal file
3
backend/configuration/admin.py
Normal file
3
backend/configuration/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
backend/configuration/apps.py
Normal file
6
backend/configuration/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ConfigurationConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'configuration'
|
||||
0
backend/configuration/migrations/__init__.py
Normal file
0
backend/configuration/migrations/__init__.py
Normal file
29
backend/configuration/models.py
Normal file
29
backend/configuration/models.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
|
||||
class ShopConfiguration(models.Model):
|
||||
name = models.CharField(max_length=100, default="Shop name", unique=True)
|
||||
|
||||
zasilkovna_shipping_price = models.DecimalField(max_digits=10, decimal_places=2, default=50)
|
||||
zasilkovna_address_id = models.CharField(max_length=100, blank=True, null=True, help_text="ID výdejního místa Zásilkovny pro odesílání zásilek")
|
||||
free_shipping_over = models.DecimalField(max_digits=10, decimal_places=2, default=2000)
|
||||
|
||||
class CURRENCY(models.TextChoices):
|
||||
CZK = "CZK", "Czech Koruna"
|
||||
EUR = "EUR", "Euro"
|
||||
currency = models.CharField(max_length=10, default=CURRENCY.CZK, choices=CURRENCY.choices)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Shop Configuration"
|
||||
verbose_name_plural = "Shop Configuration"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# zajištění singletonu
|
||||
self.pk = 1
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@classmethod
|
||||
def get_solo(cls):
|
||||
obj, _ = cls.objects.get_or_create(pk=1)
|
||||
return obj
|
||||
3
backend/configuration/tests.py
Normal file
3
backend/configuration/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
3
backend/configuration/views.py
Normal file
3
backend/configuration/views.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
4
backend/thirdparty/stripe/models.py
vendored
4
backend/thirdparty/stripe/models.py
vendored
@@ -1,8 +1,10 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
|
||||
#TODO: logika a interakce bude na stripu (třeba aktualizovaní objednávky na zaplacenou apod.)
|
||||
|
||||
class Order(models.Model):
|
||||
class StripePayment(models.Model):
|
||||
STATUS_CHOICES = [
|
||||
("pending", "Pending"),
|
||||
("paid", "Paid"),
|
||||
|
||||
2
backend/thirdparty/zasilkovna/apps.py
vendored
2
backend/thirdparty/zasilkovna/apps.py
vendored
@@ -4,3 +4,5 @@ from django.apps import AppConfig
|
||||
class ZasilkovnaConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'zasilkovna'
|
||||
name = 'thirdparty.zasilkovna'
|
||||
label = "zasilkovna"
|
||||
|
||||
2
backend/thirdparty/zasilkovna/client.py
vendored
2
backend/thirdparty/zasilkovna/client.py
vendored
@@ -7,7 +7,7 @@ import os
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
WSDL_URL = os.getenv("PACKETA_WSDL_URL", "https://soap.api.packeta.com/api/soap-bugfix.wsdl")
|
||||
WSDL_URL = os.getenv("PACKETA_WSDL_URL", "https://www.zasilkovna.cz/api/soap.wsdl")
|
||||
PACKETA_API_PASSWORD = os.getenv("PACKETA_API_PASSWORD")
|
||||
|
||||
zeepZasClient = Client(wsdl=WSDL_URL)
|
||||
|
||||
23
backend/thirdparty/zasilkovna/models.py
vendored
23
backend/thirdparty/zasilkovna/models.py
vendored
@@ -23,6 +23,8 @@ from django.core.validators import RegexValidator
|
||||
from django.core.files.base import ContentFile
|
||||
|
||||
from .client import PacketaAPI
|
||||
from commerce.models import Order, Carrier
|
||||
from configuration.models import Configuration
|
||||
|
||||
packeta_client = PacketaAPI() # single reusable instance
|
||||
|
||||
@@ -71,8 +73,27 @@ class ZasilkovnaPacket(models.Model):
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
# On first save, create the packet remotely if packet_id is not set
|
||||
carrier = Carrier.objects.get(zasilkovna=self)
|
||||
order = Order.objects.get(carrier=carrier)
|
||||
|
||||
if not self.packet_id:
|
||||
response = packeta_client.create_packet(**kwargs)
|
||||
response = packeta_client.create_packet(
|
||||
address_id=self.addressId,
|
||||
weight=self.weight,
|
||||
number=order.id,
|
||||
name=order.first_name,
|
||||
surname=order.last_name,
|
||||
company=order.company,
|
||||
email=order.email,
|
||||
addressId=Configuration.get_solo().zasilkovna_address_id,
|
||||
|
||||
#FIXME: udělat logiku pro počítaní dobírky a hodnoty zboží
|
||||
cod=100.00,
|
||||
value=100.00,
|
||||
|
||||
currency=Configuration.get_solo().currency,
|
||||
eshop= Configuration.get_solo().name,
|
||||
)
|
||||
self.packet_id = response['packet_id']
|
||||
self.barcode = response['barcode']
|
||||
|
||||
|
||||
8
backend/thirdparty/zasilkovna/views.py
vendored
8
backend/thirdparty/zasilkovna/views.py
vendored
@@ -0,0 +1,8 @@
|
||||
#views.py
|
||||
|
||||
"""
|
||||
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
|
||||
"""
|
||||
@@ -332,6 +332,7 @@ MY_CREATED_APPS = [
|
||||
'thirdparty.downloader',
|
||||
'thirdparty.stripe', # register Stripe app so its models are recognized
|
||||
'thirdparty.trading212',
|
||||
'thirdparty.zasilkovna',
|
||||
'thirdparty.gopay', # add GoPay app
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user