commerce
This commit is contained in:
@@ -6,7 +6,7 @@ from .serializers import *
|
||||
from .permissions import *
|
||||
from .models import CustomUser
|
||||
from .tokens import *
|
||||
from .tasks import send_password_reset_email_task
|
||||
from .tasks import send_password_reset_email_task, send_email_verification_task, send_email_clerk_accepted_task
|
||||
from django.conf import settings
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -4,6 +4,8 @@ from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from decimal import Decimal
|
||||
|
||||
from thirdparty.zasilkovna.models import PacketaShipment
|
||||
|
||||
|
||||
class Category(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
@@ -32,19 +34,20 @@ class Category(models.Model):
|
||||
class Product(models.Model):
|
||||
name = models.CharField(max_length=200)
|
||||
description = models.TextField(blank=True)
|
||||
|
||||
code = models.CharField(max_length=100, unique=True, blank=True, null=True)
|
||||
|
||||
|
||||
category = models.ForeignKey(Category, related_name='products', on_delete=models.PROTECT)
|
||||
|
||||
price = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
currency = models.CharField(max_length=10, default="czk")
|
||||
|
||||
url = models.SlugField(unique=True)
|
||||
|
||||
stock = models.PositiveIntegerField(default=0)
|
||||
is_active = models.BooleanField(default=True)
|
||||
|
||||
#limitka
|
||||
#limitka (volitelné)
|
||||
limited_to = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
default_carrier = models.ForeignKey(
|
||||
@@ -74,22 +77,37 @@ class ProductImage(models.Model):
|
||||
return f"{self.product.name} image"
|
||||
|
||||
|
||||
# ------------------ OBJENDÁVKOVÉ MODELY (dole) ------------------
|
||||
|
||||
|
||||
# Dopravci a způsoby dopravy
|
||||
class Carrier(models.Model):
|
||||
name = models.CharField(max_length=100) # název dopravce (Zásilkovna, Česká pošta…)
|
||||
base_price = models.DecimalField(max_digits=10, decimal_places=2, default=0) # základní cena dopravy
|
||||
delivery_time = models.CharField(max_length=100, blank=True) # např. "2–3 pracovní dny"
|
||||
is_active = models.BooleanField(default=True)
|
||||
|
||||
# pole pro logo
|
||||
logo = models.ImageField(upload_to="carriers/", blank=True, null=True)
|
||||
class Role(models.TextChoices):
|
||||
ZASILKOVNA = "packeta", "Zásilkovna"
|
||||
STORE = "store", "Osobní odběr"
|
||||
|
||||
# pole pro propojení s externím API (např. ID služby u Zásilkovny)
|
||||
external_id = models.CharField(max_length=50, blank=True, null=True)
|
||||
choice = models.CharField(max_length=20, choices=Role.choices, default=Role.STORE)
|
||||
|
||||
|
||||
zasilkovna = models.ForeignKey(
|
||||
'thirdparty.zasilkovna.Zasilkovna', on_delete=models.DO_NOTHING, null=True, blank=True, related_name="carriers"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.name} ({self.base_price} Kč)"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
#zásilkovna instance je vytvořena
|
||||
if self.choice == self.Role.ZASILKOVNA:
|
||||
self.packeta = PacketaShipment.objects.create()
|
||||
|
||||
elif self.choice == self.Role.STORE:
|
||||
self.packeta = None
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
|
||||
class DiscountCode(models.Model):
|
||||
@@ -130,20 +148,30 @@ class DiscountCode(models.Model):
|
||||
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")
|
||||
#COMPLETED = "completed", _("Dokončeno")
|
||||
|
||||
status = models.CharField(
|
||||
max_length=20, choices=Status.choices, default=Status.PENDING
|
||||
)
|
||||
|
||||
user = models.ForeignKey(
|
||||
settings.AUTH_USER_MODEL, related_name="orders", on_delete=models.CASCADE
|
||||
)
|
||||
|
||||
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
|
||||
@@ -165,22 +193,33 @@ class Order(models.Model):
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
discount = models.ManyToOneRel("DiscountCode", null=True, blank=True, on_delete=models.PROTECT)
|
||||
# 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):
|
||||
for discount in self.discount.all():
|
||||
total = 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
|
||||
else:
|
||||
total = Decimal('0.0')
|
||||
# getting all prices from order items (without discount)
|
||||
|
||||
# getting all prices from order items (with discount applied if valid)
|
||||
for item in self.items.all():
|
||||
total = total + item.get_total_price(discount)
|
||||
total = total + (item.product.price * item.quantity)
|
||||
|
||||
return total
|
||||
|
||||
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
# Keep total_price always in sync with items and discount
|
||||
@@ -208,6 +247,10 @@ class OrderItem(models.Model):
|
||||
|
||||
else:
|
||||
raise ValueError("Discount code must have either percent or amount defined.")
|
||||
|
||||
elif not discount_object:
|
||||
return self.quantity * self.product.price
|
||||
|
||||
else:
|
||||
return ValueError("Invalid discount code.")
|
||||
|
||||
|
||||
0
backend/thirdparty/zasilkovna/__init__.py
vendored
Normal file
0
backend/thirdparty/zasilkovna/__init__.py
vendored
Normal file
3
backend/thirdparty/zasilkovna/admin.py
vendored
Normal file
3
backend/thirdparty/zasilkovna/admin.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
backend/thirdparty/zasilkovna/apps.py
vendored
Normal file
6
backend/thirdparty/zasilkovna/apps.py
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ZasilkovnaConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'zasilkovna'
|
||||
0
backend/thirdparty/zasilkovna/migrations/__init__.py
vendored
Normal file
0
backend/thirdparty/zasilkovna/migrations/__init__.py
vendored
Normal file
18
backend/thirdparty/zasilkovna/models.py
vendored
Normal file
18
backend/thirdparty/zasilkovna/models.py
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
|
||||
class Zasilkovna(models.Model):
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
packet_id = models.CharField(max_length=255, unique=True) # identifikátor od Packety
|
||||
label_pdf = models.BinaryField(null=True, blank=True) # uložit PDF binárně nebo odkaz
|
||||
|
||||
status = models.CharField(max_length=50) # např. „created“, „in_transit“, „delivered“
|
||||
cancelled = models.BooleanField(default=False)
|
||||
metadata = models.JSONField(default=dict, blank=True) # pro případná extra data
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
||||
# případná logika před uložením
|
||||
super().save(*args, **kwargs)
|
||||
3
backend/thirdparty/zasilkovna/tests.py
vendored
Normal file
3
backend/thirdparty/zasilkovna/tests.py
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
4
backend/thirdparty/zasilkovna/views.py
vendored
Normal file
4
backend/thirdparty/zasilkovna/views.py
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
|
||||
@@ -5,19 +5,23 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vontor.cz – Creative Tech & Design Portfolio</title>
|
||||
<meta name="description" content="Vontor.cz showcases creative technology, design engineering, drone visuals, web applications, and self‑hosted infrastructure by Bruno Vontor." />
|
||||
<meta name="theme-color" content="#031D44" />
|
||||
<meta property="og:title" content="Vontor.cz – Creative Tech & Design" />
|
||||
<meta property="og:description" content="Full‑stack development, infrastructure, automation, and visual technology projects." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://vontor.cz" />
|
||||
<meta property="og:image" content="/portfolio/perlica.png" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="Vontor.cz – Creative Tech & Design" />
|
||||
<meta name="twitter:description" content="Engineering + design portfolio: backend, frontend, infrastructure, drone and automation." />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="preload" href="/portfolio/perlica.png" as="image" />
|
||||
|
||||
<title>Vontor.cz – Creative Tech & Design Portfolio</title>
|
||||
|
||||
<meta name="description" content="Vontor.cz showcases creative technology, design engineering, drone visuals, web applications, and self‑hosted infrastructure by Bruno Vontor." />
|
||||
<meta name="theme-color" content="#031D44" />
|
||||
<meta property="og:title" content="Vontor.cz – Creative Tech & Design" />
|
||||
<meta property="og:description" content="Full‑stack development, infrastructure, automation, and visual technology projects." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://vontor.cz" />
|
||||
|
||||
<meta property="og:image" content="/portfolio/perlica.png" />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content="Vontor.cz – Creative Tech & Design" />
|
||||
<meta name="twitter:description" content="Engineering + design portfolio: backend, frontend, infrastructure, drone and automation." />
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
Reference in New Issue
Block a user