diff --git a/.idea/misc.xml b/.idea/misc.xml index 430eb9f..eecf704 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,7 @@ + + \ No newline at end of file diff --git a/backend/account/migrations/0001_initial.py b/backend/account/migrations/0001_initial.py index 9e0eabf..3730b23 100644 --- a/backend/account/migrations/0001_initial.py +++ b/backend/account/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.7 on 2025-12-18 15:11 +# Generated by Django 5.2.7 on 2026-01-24 22:44 import account.models import django.contrib.auth.validators @@ -36,6 +36,7 @@ class Migration(migrations.Migration): ('email', models.EmailField(db_index=True, max_length=254, unique=True)), ('email_verification_token', models.CharField(blank=True, db_index=True, max_length=128, null=True)), ('email_verification_sent_at', models.DateTimeField(blank=True, null=True)), + ('newsletter', models.BooleanField(default=True)), ('gdpr', models.BooleanField(default=False)), ('is_active', models.BooleanField(default=False)), ('create_time', models.DateTimeField(auto_now_add=True)), diff --git a/backend/advertisement/migrations/0001_initial.py b/backend/advertisement/migrations/0001_initial.py index b2360dc..c875f9a 100644 --- a/backend/advertisement/migrations/0001_initial.py +++ b/backend/advertisement/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.9 on 2025-12-14 02:23 +# Generated by Django 5.2.7 on 2026-01-24 22:44 from django.db import migrations, models diff --git a/backend/commerce/migrations/0001_initial.py b/backend/commerce/migrations/0001_initial.py index c0f0d33..e9bceff 100644 --- a/backend/commerce/migrations/0001_initial.py +++ b/backend/commerce/migrations/0001_initial.py @@ -1,8 +1,9 @@ -# Generated by Django 5.2.7 on 2025-12-19 08:55 +# Generated by Django 5.2.7 on 2026-01-24 22:44 import django.core.validators import django.db.models.deletion import django.utils.timezone +from decimal import Decimal from django.conf import settings from django.db import migrations, models @@ -12,8 +13,10 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('configuration', '0001_initial'), + ('deutschepost', '0002_deutschepostbulkorder_bulk_label_pdf_and_more'), ('stripe', '0001_initial'), - ('zasilkovna', '0001_initial'), + ('zasilkovna', '0002_alter_zasilkovnapacket_state'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] @@ -32,14 +35,29 @@ class Migration(migrations.Migration): name='Carrier', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('shipping_method', models.CharField(choices=[('packeta', 'cz#Zásilkovna'), ('store', 'cz#Osobní odběr')], default='store', max_length=20)), - ('state', models.CharField(choices=[('ordered', 'cz#Objednávka se připravuje'), ('shipped', 'cz#Odesláno'), ('delivered', 'cz#Doručeno'), ('ready_to_pickup', 'cz#Připraveno k vyzvednutí')], default='ordered', max_length=20)), + ('shipping_method', models.CharField(choices=[('packeta', 'Zásilkovna'), ('deutschepost', 'Deutsche Post'), ('store', 'Osobní odběr')], default='store', max_length=20)), + ('state', models.CharField(choices=[('ordered', 'Objednávka se připravuje'), ('shipped', 'Odesláno'), ('delivered', 'Doručeno'), ('ready_to_pickup', 'Připraveno k vyzvednutí')], default='ordered', max_length=20)), ('weight', models.DecimalField(blank=True, decimal_places=2, help_text='Hmotnost zásilky v kg', max_digits=10, null=True)), ('returning', models.BooleanField(default=False, help_text='Zda je tato zásilka na vrácení')), - ('shipping_price', models.DecimalField(decimal_places=2, default=0, max_digits=10)), + ('shipping_price', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=10)), + ('deutschepost', models.ManyToManyField(blank=True, related_name='carriers', to='deutschepost.deutschepostorder')), ('zasilkovna', models.ManyToManyField(blank=True, related_name='carriers', to='zasilkovna.zasilkovnapacket')), ], ), + migrations.CreateModel( + name='Cart', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('session_key', models.CharField(blank=True, help_text='Session key for anonymous users', max_length=40, null=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='cart', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Cart', + 'verbose_name_plural': 'Carts', + }, + ), migrations.CreateModel( name='Category', fields=[ @@ -61,7 +79,7 @@ class Migration(migrations.Migration): ('code', models.CharField(max_length=50, unique=True)), ('description', models.CharField(blank=True, max_length=255)), ('percent', models.PositiveIntegerField(blank=True, help_text='Procento sleva 0-100', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])), - ('amount', models.DecimalField(blank=True, decimal_places=2, help_text='Fixní sleva v CZK', max_digits=10, null=True)), + ('amount', models.DecimalField(blank=True, decimal_places=2, help_text='Fixed discount amount in site currency', max_digits=10, null=True)), ('valid_from', models.DateTimeField(default=django.utils.timezone.now)), ('valid_to', models.DateTimeField(blank=True, null=True)), ('active', models.BooleanField(default=True)), @@ -74,7 +92,8 @@ class Migration(migrations.Migration): name='Payment', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('payment_method', models.CharField(choices=[('Site', 'cz#Platba v obchodě'), ('stripe', 'cz#Bankovní převod'), ('cash_on_delivery', 'cz#Dobírka')], default='Site', max_length=30)), + ('payment_method', models.CharField(choices=[('shop', 'Platba v obchodě'), ('stripe', 'Platební Brána'), ('cash_on_delivery', 'Dobírka')], default='shop', max_length=30)), + ('payed_at_shop', models.BooleanField(blank=True, default=False, null=True)), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), ('stripe', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='payment', to='stripe.stripemodel')), @@ -84,9 +103,9 @@ class Migration(migrations.Migration): name='Order', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('status', models.CharField(blank=True, choices=[('created', 'cz#Vytvořeno'), ('cancelled', 'cz#Zrušeno'), ('completed', 'cz#Dokončeno'), ('refunding', 'cz#Vrácení v procesu'), ('refunded', 'cz#Vráceno')], default='created', max_length=20, null=True)), - ('total_price', models.DecimalField(decimal_places=2, default=0, max_digits=10)), - ('currency', models.CharField(default='CZK', max_length=10)), + ('status', models.CharField(blank=True, choices=[('created', 'Vytvořeno'), ('cancelled', 'Zrušeno'), ('completed', 'Dokončeno'), ('refunding', 'Vrácení v procesu'), ('refunded', 'Vráceno')], default='created', max_length=20, null=True)), + ('total_price', models.DecimalField(decimal_places=2, default=Decimal('0.00'), max_digits=10)), + ('currency', models.CharField(default='', help_text='Order currency - captured from site configuration at order creation and never changes', max_length=10)), ('first_name', models.CharField(max_length=100)), ('last_name', models.CharField(max_length=100)), ('email', models.EmailField(max_length=254)), @@ -98,11 +117,11 @@ class Migration(migrations.Migration): ('note', models.TextField(blank=True)), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), - ('carrier', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='orders', to='commerce.carrier')), + ('carrier', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='order', to='commerce.carrier')), ('discount', models.ManyToManyField(blank=True, related_name='orders', to='commerce.discountcode')), ('invoice', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='order', to='commerce.invoice')), ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='orders', to=settings.AUTH_USER_MODEL)), - ('payment', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='orders', to='commerce.payment')), + ('payment', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='order', to='commerce.payment')), ], ), migrations.CreateModel( @@ -112,16 +131,18 @@ class Migration(migrations.Migration): ('name', models.CharField(max_length=200)), ('description', models.TextField(blank=True)), ('code', models.CharField(blank=True, max_length=100, null=True, unique=True)), - ('price', models.DecimalField(decimal_places=2, max_digits=10)), + ('price', models.DecimalField(decimal_places=2, help_text='Net price (without VAT)', max_digits=10)), ('url', models.SlugField(unique=True)), ('stock', models.PositiveIntegerField(default=0)), ('is_active', models.BooleanField(default=True)), ('limited_to', models.DateTimeField(blank=True, null=True)), + ('include_in_week_summary_email', models.BooleanField(default=False)), ('created_at', models.DateTimeField(auto_now_add=True)), ('updated_at', models.DateTimeField(auto_now=True)), ('category', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='products', to='commerce.category')), ('default_carrier', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='default_for_products', to='commerce.carrier')), - ('variants', models.ManyToManyField(blank=True, help_text='Symetrické varianty produktu: pokud přidáte variantu A → B, Django automaticky přidá i variantu B → A. Všechny varianty jsou rovnocenné a zobrazí se vzájemně.', related_name='variant_of', to='commerce.product')), + ('variants', models.ManyToManyField(blank=True, help_text='Symetrické varianty produktu: pokud přidáte variantu A → B, Django automaticky přidá i variantu B → A. Všechny varianty jsou rovnocenné a zobrazí se vzájemně.', to='commerce.product')), + ('vat_rate', models.ForeignKey(blank=True, help_text='VAT rate for this product. Leave empty to use default rate.', null=True, on_delete=django.db.models.deletion.PROTECT, to='configuration.vatrate')), ], ), migrations.CreateModel( @@ -145,18 +166,67 @@ class Migration(migrations.Migration): ('image', models.ImageField(upload_to='products/')), ('alt_text', models.CharField(blank=True, max_length=150)), ('is_main', models.BooleanField(default=False)), + ('order', models.PositiveIntegerField(default=0, help_text='Display order (lower numbers first)')), ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='commerce.product')), ], + options={ + 'ordering': ['order', '-is_main', 'id'], + }, ), migrations.CreateModel( name='Refund', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('reason_choice', models.CharField(choices=[('retuning_before_fourteen_day_period', 'cz#Vrácení před uplynutím 14-ti denní lhůty'), ('damaged_product', 'cz#Poškozený produkt'), ('wrong_item', 'cz#Špatná položka'), ('other', 'cz#Jiný důvod')], max_length=40)), + ('reason_choice', models.CharField(choices=[('retuning_before_fourteen_day_period', 'Vrácení před uplynutím 14-ti denní lhůty'), ('damaged_product', 'Poškozený produkt'), ('wrong_item', 'Špatná položka'), ('other', 'Jiný důvod')], max_length=40)), ('reason_text', models.TextField(blank=True)), ('verified', models.BooleanField(default=False)), ('created_at', models.DateTimeField(auto_now_add=True)), ('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='refunds', to='commerce.order')), ], ), + migrations.CreateModel( + name='Wishlist', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('products', models.ManyToManyField(blank=True, help_text='Products saved by the user', related_name='wishlisted_by', to='commerce.product')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='wishlist', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Wishlist', + 'verbose_name_plural': 'Wishlists', + }, + ), + migrations.CreateModel( + name='CartItem', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quantity', models.PositiveIntegerField(default=1)), + ('added_at', models.DateTimeField(auto_now_add=True)), + ('cart', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='commerce.cart')), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='commerce.product')), + ], + options={ + 'verbose_name': 'Cart Item', + 'verbose_name_plural': 'Cart Items', + 'unique_together': {('cart', 'product')}, + }, + ), + migrations.CreateModel( + name='Review', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('rating', models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(5)])), + ('comment', models.TextField(blank=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='commerce.product')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'indexes': [models.Index(fields=['product', 'rating'], name='commerce_re_product_9cd1a8_idx'), models.Index(fields=['created_at'], name='commerce_re_created_fe14ef_idx')], + 'unique_together': {('product', 'user')}, + }, + ), ] diff --git a/backend/commerce/migrations/0002_alter_productimage_options_carrier_deutschepost_and_more.py b/backend/commerce/migrations/0002_alter_productimage_options_carrier_deutschepost_and_more.py deleted file mode 100644 index 41016c5..0000000 --- a/backend/commerce/migrations/0002_alter_productimage_options_carrier_deutschepost_and_more.py +++ /dev/null @@ -1,83 +0,0 @@ -# Generated by Django 5.2.7 on 2026-01-17 01:37 - -import django.core.validators -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('commerce', '0001_initial'), - ('deutschepost', '0001_initial'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.AlterModelOptions( - name='productimage', - options={'ordering': ['order', '-is_main', 'id']}, - ), - migrations.AddField( - model_name='carrier', - name='deutschepost', - field=models.ManyToManyField(blank=True, related_name='carriers', to='deutschepost.deutschepostorder'), - ), - migrations.AddField( - model_name='productimage', - name='order', - field=models.PositiveIntegerField(default=0, help_text='Display order (lower numbers first)'), - ), - migrations.AlterField( - model_name='carrier', - name='shipping_method', - field=models.CharField(choices=[('packeta', 'cz#Zásilkovna'), ('deutschepost', 'cz#Deutsche Post'), ('store', 'cz#Osobní odběr')], default='store', max_length=20), - ), - migrations.AlterField( - model_name='product', - name='variants', - field=models.ManyToManyField(blank=True, help_text='Symetrické varianty produktu: pokud přidáte variantu A → B, Django automaticky přidá i variantu B → A. Všechny varianty jsou rovnocenné a zobrazí se vzájemně.', to='commerce.product'), - ), - migrations.CreateModel( - name='Cart', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('session_key', models.CharField(blank=True, help_text='Session key for anonymous users', max_length=40, null=True)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='cart', to=settings.AUTH_USER_MODEL)), - ], - options={ - 'verbose_name': 'Cart', - 'verbose_name_plural': 'Carts', - }, - ), - migrations.CreateModel( - name='Review', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('rating', models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(5)])), - ('comment', models.TextField(blank=True)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('updated_at', models.DateTimeField(auto_now=True)), - ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to='commerce.product')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reviews', to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='CartItem', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('quantity', models.PositiveIntegerField(default=1)), - ('added_at', models.DateTimeField(auto_now_add=True)), - ('cart', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='commerce.cart')), - ('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='commerce.product')), - ], - options={ - 'verbose_name': 'Cart Item', - 'verbose_name_plural': 'Cart Items', - 'unique_together': {('cart', 'product')}, - }, - ), - ] diff --git a/backend/commerce/migrations/0003_remove_product_currency.py b/backend/commerce/migrations/0003_remove_product_currency.py deleted file mode 100644 index 3e47ff7..0000000 --- a/backend/commerce/migrations/0003_remove_product_currency.py +++ /dev/null @@ -1,36 +0,0 @@ -# Generated migration to remove Product.currency field and use global currency system -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('commerce', '0002_alter_productimage_options_carrier_deutschepost_and_more'), - ] - - operations = [ - migrations.RemoveField( - model_name='product', - name='currency', - ), - migrations.AlterField( - model_name='order', - name='currency', - field=models.CharField( - default='', - help_text='Order currency - auto-filled from site configuration', - max_length=10 - ), - ), - migrations.AlterField( - model_name='discountcode', - name='amount', - field=models.DecimalField( - blank=True, - decimal_places=2, - help_text='Fixed discount amount in site currency', - max_digits=10, - null=True - ), - ), - ] \ No newline at end of file diff --git a/backend/commerce/models.py b/backend/commerce/models.py index 07e7e51..fc84954 100644 --- a/backend/commerce/models.py +++ b/backend/commerce/models.py @@ -165,8 +165,9 @@ class Order(models.Model): # Stored order grand total; recalculated on save total_price = models.DecimalField(max_digits=10, decimal_places=2, default=Decimal('0.00')) -# Currency - captured from site configuration at creation time, never changes - currency = models.CharField(max_length=10, default="", help_text="Order currency - captured from site configuration at order creation and never changes") + + # Currency - captured from site configuration at creation time, never changes + currency = models.CharField(max_length=10, default="", help_text="Order currency - captured from site configuration at order creation and never changes") # fakturační údaje (zkopírované z user profilu při objednávce) user = models.ForeignKey( @@ -261,23 +262,23 @@ class Order(models.Model): raise ValidationError("Order must have at least one item.") def get_currency(self): - \"\"\"Get order currency - falls back to site configuration if not set\"\"\" + """Get order currency - falls back to site configuration if not set""" if self.currency: return self.currency config = SiteConfiguration.get_solo() return config.currency def save(self, *args, **kwargs): - is_new = self.pk is None - - # CRITICAL: Set currency from site configuration ONLY at creation time - # Once set, currency should NEVER change to maintain order integrity - if is_new and not self.currency: - config = SiteConfiguration.get_solo() - self.currency = config.currency - - # Keep total_price always in sync with items and discount - self.total_price = self.calculate_total_price() + is_new = self.pk is None + + # CRITICAL: Set currency from site configuration ONLY at creation time + # Once set, currency should NEVER change to maintain order integrity + if is_new and not self.currency: + config = SiteConfiguration.get_solo() + self.currency = config.currency + + # Keep total_price always in sync with items and discount + self.total_price = self.calculate_total_price() if self.user and is_new: self.import_data_from_user() @@ -503,7 +504,7 @@ class DiscountCode(models.Model): ) # nebo fixní částka - amount = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, help_text=\"Fixed discount amount in site currency\") + amount = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True, help_text="Fixed discount amount in site currency") valid_from = models.DateTimeField(default=timezone.now) valid_to = models.DateTimeField(null=True, blank=True) diff --git a/backend/configuration/migrations/0001_initial.py b/backend/configuration/migrations/0001_initial.py index 182adee..d3f9406 100644 --- a/backend/configuration/migrations/0001_initial.py +++ b/backend/configuration/migrations/0001_initial.py @@ -1,5 +1,7 @@ -# Generated by Django 5.2.7 on 2025-12-18 15:11 +# Generated by Django 5.2.7 on 2026-01-24 22:44 +import django.core.validators +from decimal import Decimal from django.db import migrations, models @@ -27,17 +29,39 @@ class Migration(migrations.Migration): ('youtube_url', models.URLField(blank=True, null=True)), ('tiktok_url', models.URLField(blank=True, null=True)), ('whatsapp_number', models.CharField(blank=True, max_length=20, null=True)), - ('zasilkovna_shipping_price', models.DecimalField(decimal_places=2, default=50, max_digits=10)), + ('zasilkovna_shipping_price', models.DecimalField(decimal_places=2, default=Decimal('50.00'), max_digits=10)), ('zasilkovna_api_key', models.CharField(blank=True, help_text='API klíč pro přístup k Zásilkovna API (zatím není využito)', max_length=255, null=True)), ('zasilkovna_api_password', models.CharField(blank=True, help_text='API heslo pro přístup k Zásilkovna API (zatím není využito)', max_length=255, null=True)), - ('free_shipping_over', models.DecimalField(decimal_places=2, default=2000, max_digits=10)), + ('free_shipping_over', models.DecimalField(decimal_places=2, default=Decimal('2000.00'), max_digits=10)), + ('deutschepost_api_url', models.URLField(default='https://gw.sandbox.deutschepost.com', help_text='Deutsche Post API URL (sandbox/production)', max_length=255)), + ('deutschepost_client_id', models.CharField(blank=True, help_text='Deutsche Post OAuth Client ID', max_length=255, null=True)), + ('deutschepost_client_secret', models.CharField(blank=True, help_text='Deutsche Post OAuth Client Secret', max_length=255, null=True)), + ('deutschepost_customer_ekp', models.CharField(blank=True, help_text='Deutsche Post Customer EKP number', max_length=20, null=True)), + ('deutschepost_shipping_price', models.DecimalField(decimal_places=2, default=Decimal('6.00'), help_text='Default Deutsche Post shipping price in EUR', max_digits=10)), ('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')), - ('currency', models.CharField(choices=[('CZK', 'cz#Czech Koruna'), ('EUR', 'cz#Euro')], default='CZK', max_length=10)), + ('currency', models.CharField(choices=[('EUR', 'Euro'), ('CZK', 'Czech Koruna'), ('USD', 'US Dollar'), ('GBP', 'British Pound'), ('PLN', 'Polish Zloty'), ('HUF', 'Hungarian Forint'), ('SEK', 'Swedish Krona'), ('DKK', 'Danish Krone'), ('NOK', 'Norwegian Krone'), ('CHF', 'Swiss Franc')], default='EUR', max_length=10)), ], options={ 'verbose_name': 'Shop Configuration', 'verbose_name_plural': 'Shop Configuration', }, ), + migrations.CreateModel( + name='VATRate', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(help_text="E.g. 'German Standard', 'German Reduced', 'Czech Standard'", max_length=100)), + ('description', models.TextField(blank=True, help_text="Optional description: 'Standard rate for most products', 'Books and food', etc.")), + ('rate', models.DecimalField(decimal_places=4, help_text='VAT rate as percentage (e.g. 19.00 for 19%)', max_digits=5, validators=[django.core.validators.MinValueValidator(Decimal('0')), django.core.validators.MaxValueValidator(Decimal('100'))])), + ('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)), + ], + options={ + 'verbose_name': 'VAT Rate', + 'verbose_name_plural': 'VAT Rates', + 'ordering': ['-is_default', 'rate', 'name'], + }, + ), ] diff --git a/backend/configuration/migrations/0002_siteconfiguration_deutschepost_api_url_and_more.py b/backend/configuration/migrations/0002_siteconfiguration_deutschepost_api_url_and_more.py deleted file mode 100644 index 76d1c32..0000000 --- a/backend/configuration/migrations/0002_siteconfiguration_deutschepost_api_url_and_more.py +++ /dev/null @@ -1,38 +0,0 @@ -# Generated by Django 5.2.7 on 2026-01-17 01:37 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('configuration', '0001_initial'), - ] - - operations = [ - migrations.AddField( - model_name='siteconfiguration', - name='deutschepost_api_url', - field=models.URLField(default='https://gw.sandbox.deutschepost.com', help_text='Deutsche Post API URL (sandbox/production)', max_length=255), - ), - migrations.AddField( - model_name='siteconfiguration', - name='deutschepost_client_id', - field=models.CharField(blank=True, help_text='Deutsche Post OAuth Client ID', max_length=255, null=True), - ), - migrations.AddField( - model_name='siteconfiguration', - name='deutschepost_client_secret', - field=models.CharField(blank=True, help_text='Deutsche Post OAuth Client Secret', max_length=255, null=True), - ), - migrations.AddField( - model_name='siteconfiguration', - name='deutschepost_customer_ekp', - field=models.CharField(blank=True, help_text='Deutsche Post Customer EKP number', max_length=20, null=True), - ), - migrations.AddField( - model_name='siteconfiguration', - name='deutschepost_shipping_price', - field=models.DecimalField(decimal_places=2, default=150, help_text='Default Deutsche Post shipping price', max_digits=10), - ), - ] diff --git a/backend/social/chat/migrations/0001_initial.py b/backend/social/chat/migrations/0001_initial.py index 54b4b7a..4663f07 100644 --- a/backend/social/chat/migrations/0001_initial.py +++ b/backend/social/chat/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.2.7 on 2026-01-17 01:37 +# Generated by Django 5.2.7 on 2026-01-24 22:44 import django.db.models.deletion from django.conf import settings diff --git a/backend/thirdparty/deutschepost/migrations/0002_deutschepostbulkorder_bulk_label_pdf_and_more.py b/backend/thirdparty/deutschepost/migrations/0002_deutschepostbulkorder_bulk_label_pdf_and_more.py new file mode 100644 index 0000000..a34517c --- /dev/null +++ b/backend/thirdparty/deutschepost/migrations/0002_deutschepostbulkorder_bulk_label_pdf_and_more.py @@ -0,0 +1,43 @@ +# Generated by Django 5.2.7 on 2026-01-24 22:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('deutschepost', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='deutschepostbulkorder', + name='bulk_label_pdf', + field=models.FileField(blank=True, help_text='Bulk shipment label PDF', null=True, upload_to='deutschepost/bulk_labels/'), + ), + migrations.AddField( + model_name='deutschepostbulkorder', + name='paperwork_pdf', + field=models.FileField(blank=True, help_text='Bulk shipment paperwork PDF', null=True, upload_to='deutschepost/paperwork/'), + ), + migrations.AddField( + model_name='deutschepostorder', + name='label_pdf', + field=models.FileField(blank=True, help_text='Shipping label PDF', null=True, upload_to='deutschepost/labels/'), + ), + migrations.AddField( + model_name='deutschepostorder', + name='label_size', + field=models.CharField(choices=[('A4', 'A4 (210x297mm)'), ('A5', 'A5 (148x210mm)'), ('A6', 'A6 (105x148mm)')], default='A4', max_length=10), + ), + migrations.AlterField( + model_name='deutschepostbulkorder', + name='status', + field=models.CharField(choices=[('CREATED', 'Vytvořeno'), ('PROCESSING', 'Zpracovává se'), ('COMPLETED', 'Dokončeno'), ('ERROR', 'Chyba')], default='CREATED', max_length=20), + ), + migrations.AlterField( + model_name='deutschepostorder', + name='state', + field=models.CharField(choices=[('CREATED', 'Vytvořeno'), ('FINALIZED', 'Dokončeno'), ('SHIPPED', 'Odesláno'), ('DELIVERED', 'Doručeno'), ('CANCELLED', 'Zrušeno'), ('ERROR', 'Chyba')], default='CREATED', max_length=20), + ), + ] diff --git a/backend/thirdparty/zasilkovna/migrations/0002_alter_zasilkovnapacket_state.py b/backend/thirdparty/zasilkovna/migrations/0002_alter_zasilkovnapacket_state.py new file mode 100644 index 0000000..82cdbf6 --- /dev/null +++ b/backend/thirdparty/zasilkovna/migrations/0002_alter_zasilkovnapacket_state.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2026-01-24 22:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('zasilkovna', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='zasilkovnapacket', + name='state', + field=models.CharField(choices=[('WAITING_FOR_ORDERING_SHIPMENT', 'Čeká na objednání zásilkovny'), ('PENDING', 'Podáno'), ('SENDED', 'Odesláno'), ('ARRIVED', 'Doručeno'), ('CANCELED', 'Zrušeno'), ('RETURNING', 'Posláno zpátky'), ('RETURNED', 'Vráceno')], default='PENDING', max_length=35), + ), + ] diff --git a/backups/backup-20260124-224819.sql b/backups/backup-20260124-224819.sql new file mode 100644 index 0000000..e69de29