parent
151a96dc37
commit
55fa03d250
@ -0,0 +1,3 @@
|
||||
*.pyc
|
||||
__pycache__/*
|
||||
inventory/migrations/__pycache__/0001_initial.cpython-310.pyc
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class PricingConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'pricing'
|
@ -0,0 +1,113 @@
|
||||
# Generated by Django 5.1.4 on 2025-01-08 14:02
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Discount',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('discount_percentage', models.DecimalField(decimal_places=2, max_digits=5, verbose_name='Процент скидки')),
|
||||
('start_date', models.DateField(verbose_name='Дата начала')),
|
||||
('end_date', models.DateField(verbose_name='Дата окончания')),
|
||||
('description', models.TextField(blank=True, null=True, verbose_name='Описание акции')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PriceList',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('entry_price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Входная цена')),
|
||||
('final_price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Итоговая цена')),
|
||||
('date_effective', models.DateField(verbose_name='Дата вступления в силу')),
|
||||
('constraint_percent_limit', models.DecimalField(decimal_places=2, default=1000, max_digits=5, verbose_name='Лимит на наценку (%)')),
|
||||
('constraint_price_change', models.DecimalField(decimal_places=2, default=90, max_digits=5, verbose_name='Лимит на изменение цены (%)')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PriceType',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(choices=[('regular', 'Регулярная'), ('discount', 'Скидочная'), ('promotional', 'Акционная')], max_length=50, verbose_name='Тип цены')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Product',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='Наименование товара')),
|
||||
('manufacturer_name', models.CharField(max_length=255, verbose_name='Производитель')),
|
||||
('manufacturer_country', models.CharField(max_length=255, verbose_name='Страна производителя')),
|
||||
('manufacturer_code', models.CharField(blank=True, max_length=50, null=True, verbose_name='Код производителя')),
|
||||
('dimensions', models.CharField(blank=True, max_length=255, null=True, verbose_name='Размеры')),
|
||||
('unit_of_measure', models.CharField(max_length=50, verbose_name='Единица измерения')),
|
||||
('shelf_life_days', models.IntegerField(verbose_name='Срок годности (дни)')),
|
||||
('barcode', models.CharField(max_length=50, unique=True, verbose_name='Штрихкод')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DiscountHistory',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('old_discount', models.DecimalField(decimal_places=2, max_digits=5, verbose_name='Старая скидка')),
|
||||
('new_discount', models.DecimalField(decimal_places=2, max_digits=5, verbose_name='Новая скидка')),
|
||||
('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Дата изменения скидки')),
|
||||
('reason', models.TextField(blank=True, null=True, verbose_name='Причина изменения скидки')),
|
||||
('discount', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pricing.discount', verbose_name='Акция/Скидка')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PriceListWithDiscount',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('final_price_after_discount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Итоговая цена после скидки')),
|
||||
('discount', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pricing.discount', verbose_name='Скидка')),
|
||||
('price_list', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pricing.pricelist', verbose_name='Прайс-лист')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='pricelist',
|
||||
name='price_type',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pricing.pricetype', verbose_name='Тип цены'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PriceTag',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('tag_image', models.ImageField(blank=True, null=True, upload_to='price_tags/', verbose_name='Изображение ценника')),
|
||||
('price_effective_date', models.DateField(verbose_name='Дата вступления в силу ценника')),
|
||||
('price_list', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pricing.pricelist', verbose_name='Прайс-лист')),
|
||||
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pricing.product', verbose_name='Товар')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='pricelist',
|
||||
name='product',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pricing.product', verbose_name='Товар'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PriceChangeHistory',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('old_price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Старая цена')),
|
||||
('new_price', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Новая цена')),
|
||||
('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Дата изменения цены')),
|
||||
('reason', models.TextField(blank=True, null=True, verbose_name='Причина изменения цены')),
|
||||
('product', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pricing.product', verbose_name='Товар')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='discount',
|
||||
name='product',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='pricing.product', verbose_name='Товар'),
|
||||
),
|
||||
]
|
@ -0,0 +1,46 @@
|
||||
from rest_framework.test import APITestCase
|
||||
from rest_framework import status
|
||||
from .models import Product
|
||||
from django.urls import reverse
|
||||
|
||||
class ProductAPITest(APITestCase):
|
||||
|
||||
def setUp(self):
|
||||
# Создание тестового продукта
|
||||
self.product_data = {
|
||||
'name': 'Test Product',
|
||||
'manufacturer_name': 'Test Manufacturer',
|
||||
'manufacturer_country': 'Test Country',
|
||||
'unit_of_measure': 'pcs',
|
||||
'shelf_life_days': 365,
|
||||
'barcode': '123456789012',
|
||||
}
|
||||
self.product = Product.objects.create(**self.product_data)
|
||||
self.url = reverse('product-list') # Замени на свой URL
|
||||
|
||||
def test_create_product(self):
|
||||
"""Test creating a new product"""
|
||||
response = self.client.post(self.url, self.product_data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(Product.objects.count(), 2)
|
||||
self.assertEqual(Product.objects.latest('id').name, self.product_data['name'])
|
||||
|
||||
def test_get_product(self):
|
||||
"""Test retrieving a product"""
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertEqual(len(response.data), 1) # Только один продукт в базе
|
||||
|
||||
def test_update_product(self):
|
||||
"""Test updating a product"""
|
||||
updated_data = {'name': 'Updated Product'}
|
||||
response = self.client.put(reverse('product-detail', args=[self.product.id]), updated_data, format='json')
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.product.refresh_from_db()
|
||||
self.assertEqual(self.product.name, 'Updated Product')
|
||||
|
||||
def test_delete_product(self):
|
||||
"""Test deleting a product"""
|
||||
response = self.client.delete(reverse('product-detail', args=[self.product.id]))
|
||||
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||
self.assertEqual(Product.objects.count(), 0) # Продукт должен быть удален
|
@ -0,0 +1,39 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from drf_yasg.views import get_schema_view
|
||||
from drf_yasg import openapi
|
||||
from rest_framework.permissions import AllowAny
|
||||
from .views import (
|
||||
PriceTypeViewSet, PriceListViewSet, DiscountViewSet,
|
||||
PriceChangeHistoryViewSet, PriceTagViewSet, DiscountHistoryViewSet, PriceListWithDiscountViewSet
|
||||
)
|
||||
|
||||
# Create the router for pricing-related views
|
||||
router_pricing = DefaultRouter()
|
||||
router_pricing.register(r'price-types', PriceTypeViewSet)
|
||||
router_pricing.register(r'price-lists', PriceListViewSet)
|
||||
router_pricing.register(r'discounts', DiscountViewSet)
|
||||
router_pricing.register(r'price-change-history', PriceChangeHistoryViewSet)
|
||||
router_pricing.register(r'price-tags', PriceTagViewSet)
|
||||
router_pricing.register(r'discount-history', DiscountHistoryViewSet)
|
||||
router_pricing.register(r'price-lists-with-discounts', PriceListWithDiscountViewSet)
|
||||
|
||||
# Swagger schema view for pricing
|
||||
schema_view_pricing = get_schema_view(
|
||||
openapi.Info(
|
||||
title="Store Management API - Pricing",
|
||||
default_version='v1',
|
||||
description="API for managing pricing in the franchise store",
|
||||
terms_of_service="https://www.example.com/terms/",
|
||||
contact=openapi.Contact(email="support@example.com"),
|
||||
license=openapi.License(name="BSD License"),
|
||||
),
|
||||
public=True,
|
||||
permission_classes=(AllowAny,),
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path('', include(router_pricing.urls)),
|
||||
path('swagger/', schema_view_pricing.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui-pricing'),
|
||||
path('redoc/', schema_view_pricing.with_ui('redoc', cache_timeout=0), name='schema-redoc-pricing'),
|
||||
]
|
@ -0,0 +1,46 @@
|
||||
from rest_framework import viewsets
|
||||
from .models import Product, PriceType, PriceList, Discount, PriceChangeHistory, PriceTag, DiscountHistory, PriceListWithDiscount
|
||||
from .serializers import (
|
||||
ProductSerializer, PriceTypeSerializer, PriceListSerializer, DiscountSerializer,
|
||||
PriceChangeHistorySerializer, PriceTagSerializer, DiscountHistorySerializer, PriceListWithDiscountSerializer
|
||||
)
|
||||
|
||||
# ViewSet для модели Product
|
||||
class ProductViewSet(viewsets.ModelViewSet):
|
||||
queryset = Product.objects.all()
|
||||
serializer_class = ProductSerializer
|
||||
|
||||
# ViewSet для модели PriceType
|
||||
class PriceTypeViewSet(viewsets.ModelViewSet):
|
||||
queryset = PriceType.objects.all()
|
||||
serializer_class = PriceTypeSerializer
|
||||
|
||||
# ViewSet для модели PriceList
|
||||
class PriceListViewSet(viewsets.ModelViewSet):
|
||||
queryset = PriceList.objects.all()
|
||||
serializer_class = PriceListSerializer
|
||||
|
||||
# ViewSet для модели Discount
|
||||
class DiscountViewSet(viewsets.ModelViewSet):
|
||||
queryset = Discount.objects.all()
|
||||
serializer_class = DiscountSerializer
|
||||
|
||||
# ViewSet для модели PriceChangeHistory
|
||||
class PriceChangeHistoryViewSet(viewsets.ModelViewSet):
|
||||
queryset = PriceChangeHistory.objects.all()
|
||||
serializer_class = PriceChangeHistorySerializer
|
||||
|
||||
# ViewSet для модели PriceTag
|
||||
class PriceTagViewSet(viewsets.ModelViewSet):
|
||||
queryset = PriceTag.objects.all()
|
||||
serializer_class = PriceTagSerializer
|
||||
|
||||
# ViewSet для модели DiscountHistory
|
||||
class DiscountHistoryViewSet(viewsets.ModelViewSet):
|
||||
queryset = DiscountHistory.objects.all()
|
||||
serializer_class = DiscountHistorySerializer
|
||||
|
||||
# ViewSet для модели PriceListWithDiscount
|
||||
class PriceListWithDiscountViewSet(viewsets.ModelViewSet):
|
||||
queryset = PriceListWithDiscount.objects.all()
|
||||
serializer_class = PriceListWithDiscountSerializer
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue