Refactored commerce models to remove language prefixes from status choices, improved order and payment validation, and enforced business rules for payment and shipping combinations. Updated order item and cart calculations to use VAT-inclusive prices, added unique constraints and indexes to reviews, and improved stock management logic. Added new Stripe client methods for session and refund management, and updated Zasilkovna and Deutsche Post models for consistency. Minor fixes and improvements across related tasks, URLs, and configuration models.
125 lines
5.5 KiB
Python
125 lines
5.5 KiB
Python
import decimal
|
|
from django.db import models
|
|
from decimal import Decimal
|
|
from django.core.validators import MinValueValidator, MaxValueValidator
|
|
|
|
# Create your models here.
|
|
|
|
class SiteConfiguration(models.Model):
|
|
name = models.CharField(max_length=100, default="Shop name", unique=True)
|
|
|
|
logo = models.ImageField(upload_to='shop_logos/', blank=True, null=True)
|
|
favicon = models.ImageField(upload_to='shop_favicons/', blank=True, null=True)
|
|
|
|
contact_email = models.EmailField(max_length=254, blank=True, null=True)
|
|
contact_phone = models.CharField(max_length=20, blank=True, null=True)
|
|
contact_address = models.TextField(blank=True, null=True)
|
|
opening_hours = models.JSONField(blank=True, null=True) #FIXME: vytvoř JSON tvar pro otvírací dobu, přes validátory
|
|
|
|
#Social
|
|
facebook_url = models.URLField(max_length=200, blank=True, null=True)
|
|
instagram_url = models.URLField(max_length=200, blank=True, null=True)
|
|
youtube_url = models.URLField(max_length=200, blank=True, null=True)
|
|
tiktok_url = models.URLField(max_length=200, blank=True, null=True)
|
|
whatsapp_number = models.CharField(max_length=20, blank=True, null=True)
|
|
|
|
#zasilkovna settings
|
|
zasilkovna_shipping_price = models.DecimalField(max_digits=10, decimal_places=2, default=decimal.Decimal("50.00"))
|
|
#FIXME: není implementováno ↓↓↓
|
|
zasilkovna_api_key = models.CharField(max_length=255, blank=True, null=True, help_text="API klíč pro přístup k Zásilkovna API (zatím není využito)")
|
|
#FIXME: není implementováno ↓↓↓
|
|
zasilkovna_api_password = models.CharField(max_length=255, blank=True, null=True, help_text="API heslo pro přístup k Zásilkovna API (zatím není využito)")
|
|
#FIXME: není implementováno ↓↓↓
|
|
free_shipping_over = models.DecimalField(max_digits=10, decimal_places=2, default=decimal.Decimal("2000.00"))
|
|
|
|
# Deutsche Post settings
|
|
deutschepost_api_url = models.URLField(max_length=255, default="https://gw.sandbox.deutschepost.com", help_text="Deutsche Post API URL (sandbox/production)")
|
|
deutschepost_client_id = models.CharField(max_length=255, blank=True, null=True, help_text="Deutsche Post OAuth Client ID")
|
|
deutschepost_client_secret = models.CharField(max_length=255, blank=True, null=True, help_text="Deutsche Post OAuth Client Secret")
|
|
deutschepost_customer_ekp = models.CharField(max_length=20, blank=True, null=True, help_text="Deutsche Post Customer EKP number")
|
|
deutschepost_shipping_price = models.DecimalField(max_digits=10, decimal_places=2, default=decimal.Decimal("6.00"), help_text="Default Deutsche Post shipping price in EUR")
|
|
|
|
#coupon settings
|
|
multiplying_coupons = models.BooleanField(default=True, help_text="Násobení kupónů v objednávce (ano/ne), pokud ne tak se použije pouze nejvyšší slevový kupón")
|
|
addition_of_coupons_amount = models.BooleanField(default=False, help_text="Sčítání slevových kupónů v objednávce (ano/ne), pokud ne tak se použije pouze nejvyšší slevový kupón")
|
|
|
|
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
|
|
|
|
|
|
class VATRate(models.Model):
|
|
"""Business owner configurable VAT rates"""
|
|
name = models.CharField(
|
|
max_length=100,
|
|
help_text="E.g. 'German Standard', 'German Reduced', 'Czech Standard'"
|
|
)
|
|
description = models.TextField(
|
|
blank=True,
|
|
help_text="Optional description: 'Standard rate for most products', 'Books and food', etc."
|
|
)
|
|
|
|
rate = models.DecimalField(
|
|
max_digits=5,
|
|
decimal_places=4, # Allows rates like 19.5000%
|
|
validators=[MinValueValidator(Decimal('0')), MaxValueValidator(Decimal('100'))],
|
|
help_text="VAT rate as percentage (e.g. 19.00 for 19%)"
|
|
)
|
|
|
|
|
|
|
|
is_default = models.BooleanField(
|
|
default=False,
|
|
help_text="Default rate for new products"
|
|
)
|
|
is_active = models.BooleanField(
|
|
default=True,
|
|
help_text="Whether this VAT rate is active and available for use"
|
|
)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
verbose_name = "VAT Rate"
|
|
verbose_name_plural = "VAT Rates"
|
|
ordering = ['-is_default', 'rate', 'name']
|
|
|
|
def __str__(self):
|
|
return f"{self.name} ({self.rate}%)"
|
|
|
|
def save(self, *args, **kwargs):
|
|
# Ensure only one default rate
|
|
if self.is_default:
|
|
VATRate.objects.filter(is_default=True).update(is_default=False)
|
|
super().save(*args, **kwargs)
|
|
|
|
# If no default exists, make first active one default
|
|
if not VATRate.objects.filter(is_default=True).exists():
|
|
first_active = VATRate.objects.filter(is_active=True).first()
|
|
if first_active:
|
|
first_active.is_default = True
|
|
first_active.save()
|
|
|
|
@property
|
|
def rate_decimal(self):
|
|
"""Returns rate as decimal for calculations (19.00% -> 0.19)"""
|
|
return self.rate / Decimal('100')
|
|
|
|
@classmethod
|
|
def get_default(cls):
|
|
"""Get the default VAT rate"""
|
|
return cls.objects.filter(is_default=True, is_active=True).first() |