commit
						32880a2178
					
				@ -1,3 +1,8 @@
 | 
				
			||||
**/__pycache__/
 | 
				
			||||
 | 
				
			||||
*.pyc
 | 
				
			||||
*.pyo
 | 
				
			||||
 | 
				
			||||
*.log
 | 
				
			||||
 | 
				
			||||
inventory/migrations/__pycache__/0001_initial.cpython-310.pyc
 | 
				
			||||
@ -0,0 +1,3 @@
 | 
				
			||||
from django.contrib import admin
 | 
				
			||||
 | 
				
			||||
# Register your models here.
 | 
				
			||||
@ -0,0 +1,6 @@
 | 
				
			||||
from django.apps import AppConfig
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
class HRConfig(AppConfig):
 | 
				
			||||
    default_auto_field = 'django.db.models.BigAutoField'
 | 
				
			||||
    name = 'hr'
 | 
				
			||||
@ -0,0 +1,92 @@
 | 
				
			||||
import datetime
 | 
				
			||||
from django.db import models
 | 
				
			||||
from io import BytesIO
 | 
				
			||||
from reportlab.lib.pagesizes import letter
 | 
				
			||||
from reportlab.pdfgen import canvas
 | 
				
			||||
 | 
				
			||||
# Employee Directory
 | 
				
			||||
class Employee(models.Model):
 | 
				
			||||
    first_name = models.CharField(max_length=50)
 | 
				
			||||
    last_name = models.CharField(max_length=50)
 | 
				
			||||
    position = models.CharField(max_length=100)
 | 
				
			||||
    email = models.EmailField(unique=True)
 | 
				
			||||
    hired_date = models.DateField()
 | 
				
			||||
    work_schedule = models.JSONField(default=dict)
 | 
				
			||||
 | 
				
			||||
    def __str__(self):
 | 
				
			||||
        return f"{self.first_name} {self.last_name}"
 | 
				
			||||
 | 
				
			||||
# Time Tracking System
 | 
				
			||||
class WorkTimeLog(models.Model):
 | 
				
			||||
    employee = models.ForeignKey(Employee, on_delete=models.CASCADE)
 | 
				
			||||
    date = models.DateField()
 | 
				
			||||
    check_in = models.TimeField(null=True, blank=True)
 | 
				
			||||
    check_out = models.TimeField(null=True, blank=True)
 | 
				
			||||
    worked_hours = models.FloatField(null=True, blank=True)
 | 
				
			||||
 | 
				
			||||
    def calculate_worked_hours(self):
 | 
				
			||||
        if self.check_in and self.check_out:
 | 
				
			||||
            delta = datetime.combine(datetime.date.min, self.check_out) - datetime.combine(datetime.date.min, self.check_in)
 | 
				
			||||
            self.worked_hours = delta.total_seconds() / 3600
 | 
				
			||||
        else:
 | 
				
			||||
            self.worked_hours = 0
 | 
				
			||||
 | 
				
			||||
    def save(self, *args, **kwargs):
 | 
				
			||||
        self.calculate_worked_hours()
 | 
				
			||||
        super().save(*args, **kwargs)
 | 
				
			||||
 | 
				
			||||
    def __str__(self):
 | 
				
			||||
        return f"{self.employee}: {self.date}"
 | 
				
			||||
 | 
				
			||||
# Leave Management
 | 
				
			||||
class Leave(models.Model):
 | 
				
			||||
    LEAVE_TYPE_CHOICES = [
 | 
				
			||||
        ("vacation", "Vacation"),
 | 
				
			||||
        ("sick", "Sick Leave"),
 | 
				
			||||
    ]
 | 
				
			||||
 | 
				
			||||
    employee = models.ForeignKey(Employee, on_delete=models.CASCADE)
 | 
				
			||||
    leave_type = models.CharField(max_length=10, choices=LEAVE_TYPE_CHOICES)
 | 
				
			||||
    start_date = models.DateField()
 | 
				
			||||
    end_date = models.DateField()
 | 
				
			||||
    reason = models.TextField(blank=True)
 | 
				
			||||
 | 
				
			||||
    def __str__(self):
 | 
				
			||||
        return f"{self.employee}: {self.leave_type} ({self.start_date} - {self.end_date})"
 | 
				
			||||
 | 
				
			||||
    def duration(self):
 | 
				
			||||
        return (self.end_date - self.start_date).days + 1
 | 
				
			||||
 | 
				
			||||
# Overtime Reporting
 | 
				
			||||
class OvertimeReport(models.Model):
 | 
				
			||||
    employee = models.ForeignKey(Employee, on_delete=models.CASCADE)
 | 
				
			||||
    date = models.DateField()
 | 
				
			||||
    worked_hours = models.FloatField()
 | 
				
			||||
    required_hours = models.FloatField(default=8.0)
 | 
				
			||||
    overtime = models.FloatField()
 | 
				
			||||
    comment = models.TextField(blank=True)
 | 
				
			||||
 | 
				
			||||
    def calculate_overtime(self):
 | 
				
			||||
        self.overtime = self.worked_hours - self.required_hours
 | 
				
			||||
 | 
				
			||||
    def save(self, *args, **kwargs):
 | 
				
			||||
        self.calculate_overtime()
 | 
				
			||||
        super().save(*args, **kwargs)
 | 
				
			||||
 | 
				
			||||
    def __str__(self):
 | 
				
			||||
        return f"{self.employee}: {self.overtime} hours on {self.date}"
 | 
				
			||||
 | 
				
			||||
# PDF Generation for HR Reports
 | 
				
			||||
class Report(models.Model):
 | 
				
			||||
    employee = models.ForeignKey(Employee, on_delete=models.CASCADE)
 | 
				
			||||
    report_date = models.DateField(auto_now_add=True)
 | 
				
			||||
 | 
				
			||||
    def generate_pdf(self):
 | 
				
			||||
        buffer = BytesIO()
 | 
				
			||||
        c = canvas.Canvas(buffer, pagesize=letter)
 | 
				
			||||
        c.drawString(100, 750, f"Employee: {self.employee.first_name} {self.employee.last_name}")
 | 
				
			||||
        c.drawString(100, 730, f"Date: {self.report_date}")
 | 
				
			||||
        c.showPage()
 | 
				
			||||
        c.save()
 | 
				
			||||
        buffer.seek(0)
 | 
				
			||||
        return buffer
 | 
				
			||||
@ -0,0 +1,40 @@
 | 
				
			||||
from rest_framework import serializers
 | 
				
			||||
from .models import Employee, WorkTimeLog, Leave, OvertimeReport, Report
 | 
				
			||||
 | 
				
			||||
# Сериализатор для модели Employee
 | 
				
			||||
class EmployeeSerializer(serializers.ModelSerializer):
 | 
				
			||||
    class Meta:
 | 
				
			||||
        model = Employee
 | 
				
			||||
        fields = ['id', 'first_name', 'last_name', 'position', 'email', 'hired_date', 'work_schedule']
 | 
				
			||||
 | 
				
			||||
# Сериализатор для модели WorkTimeLog
 | 
				
			||||
class WorkTimeLogSerializer(serializers.ModelSerializer):
 | 
				
			||||
    employee = EmployeeSerializer()  # Вложенный сериализатор для сотрудника
 | 
				
			||||
 | 
				
			||||
    class Meta:
 | 
				
			||||
        model = WorkTimeLog
 | 
				
			||||
        fields = ['id', 'employee', 'date', 'check_in', 'check_out', 'worked_hours']
 | 
				
			||||
 | 
				
			||||
# Сериализатор для модели Leave
 | 
				
			||||
class LeaveSerializer(serializers.ModelSerializer):
 | 
				
			||||
    employee = EmployeeSerializer()  # Вложенный сериализатор для сотрудника
 | 
				
			||||
 | 
				
			||||
    class Meta:
 | 
				
			||||
        model = Leave
 | 
				
			||||
        fields = ['id', 'employee', 'leave_type', 'start_date', 'end_date', 'reason']
 | 
				
			||||
 | 
				
			||||
# Сериализатор для модели OvertimeReport
 | 
				
			||||
class OvertimeReportSerializer(serializers.ModelSerializer):
 | 
				
			||||
    employee = EmployeeSerializer()  # Вложенный сериализатор для сотрудника
 | 
				
			||||
 | 
				
			||||
    class Meta:
 | 
				
			||||
        model = OvertimeReport
 | 
				
			||||
        fields = ['id', 'employee', 'date', 'worked_hours', 'required_hours', 'overtime', 'comment']
 | 
				
			||||
 | 
				
			||||
# Сериализатор для модели Report
 | 
				
			||||
class ReportSerializer(serializers.ModelSerializer):
 | 
				
			||||
    employee = EmployeeSerializer()  # Вложенный сериализатор для сотрудника
 | 
				
			||||
 | 
				
			||||
    class Meta:
 | 
				
			||||
        model = Report
 | 
				
			||||
        fields = ['id', 'employee', 'report_date']
 | 
				
			||||
@ -0,0 +1,37 @@
 | 
				
			||||
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 (
 | 
				
			||||
    EmployeeViewSet, WorkTimeLogViewSet, LeaveViewSet, 
 | 
				
			||||
    OvertimeReportViewSet, ReportViewSet
 | 
				
			||||
)
 | 
				
			||||
 | 
				
			||||
# Create the router for HR-related views
 | 
				
			||||
router_hr = DefaultRouter()
 | 
				
			||||
router_hr.register(r'employees', EmployeeViewSet)
 | 
				
			||||
router_hr.register(r'work-time-logs', WorkTimeLogViewSet)
 | 
				
			||||
router_hr.register(r'leaves', LeaveViewSet)
 | 
				
			||||
router_hr.register(r'overtime-reports', OvertimeReportViewSet)
 | 
				
			||||
router_hr.register(r'reports', ReportViewSet)
 | 
				
			||||
 | 
				
			||||
# Swagger schema view for HR
 | 
				
			||||
schema_view_hr = get_schema_view(
 | 
				
			||||
    openapi.Info(
 | 
				
			||||
        title="Human Resources API",
 | 
				
			||||
        default_version='v1',
 | 
				
			||||
        description="API for managing HR-related data and operations",
 | 
				
			||||
        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_hr.urls)),
 | 
				
			||||
    path('swagger/', schema_view_hr.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui-hr'),
 | 
				
			||||
    path('redoc/', schema_view_hr.with_ui('redoc', cache_timeout=0), name='schema-redoc-hr'),
 | 
				
			||||
]
 | 
				
			||||
@ -0,0 +1,31 @@
 | 
				
			||||
from rest_framework import viewsets
 | 
				
			||||
from .models import Employee, WorkTimeLog, Leave, OvertimeReport, Report
 | 
				
			||||
from .serializers import (
 | 
				
			||||
    EmployeeSerializer, WorkTimeLogSerializer, LeaveSerializer,
 | 
				
			||||
    OvertimeReportSerializer, ReportSerializer
 | 
				
			||||
)
 | 
				
			||||
 | 
				
			||||
# ViewSet для модели Employee
 | 
				
			||||
class EmployeeViewSet(viewsets.ModelViewSet):
 | 
				
			||||
    queryset = Employee.objects.all()
 | 
				
			||||
    serializer_class = EmployeeSerializer
 | 
				
			||||
 | 
				
			||||
# ViewSet для модели WorkTimeLog
 | 
				
			||||
class WorkTimeLogViewSet(viewsets.ModelViewSet):
 | 
				
			||||
    queryset = WorkTimeLog.objects.all()
 | 
				
			||||
    serializer_class = WorkTimeLogSerializer
 | 
				
			||||
 | 
				
			||||
# ViewSet для модели Leave
 | 
				
			||||
class LeaveViewSet(viewsets.ModelViewSet):
 | 
				
			||||
    queryset = Leave.objects.all()
 | 
				
			||||
    serializer_class = LeaveSerializer
 | 
				
			||||
 | 
				
			||||
# ViewSet для модели OvertimeReport
 | 
				
			||||
class OvertimeReportViewSet(viewsets.ModelViewSet):
 | 
				
			||||
    queryset = OvertimeReport.objects.all()
 | 
				
			||||
    serializer_class = OvertimeReportSerializer
 | 
				
			||||
 | 
				
			||||
# ViewSet для модели Report
 | 
				
			||||
class ReportViewSet(viewsets.ModelViewSet):
 | 
				
			||||
    queryset = Report.objects.all()
 | 
				
			||||
    serializer_class = ReportSerializer
 | 
				
			||||
											
												Binary file not shown.
											
										
									
								
											
												Binary file not shown.
											
										
									
								
											
												Binary file not shown.
											
										
									
								@ -0,0 +1,33 @@
 | 
				
			||||
# Generated by Django 5.1.4 on 2025-01-08 16:17
 | 
				
			||||
 | 
				
			||||
import django.db.models.deletion
 | 
				
			||||
from django.db import migrations, models
 | 
				
			||||
 | 
				
			||||
 | 
				
			||||
class Migration(migrations.Migration):
 | 
				
			||||
 | 
				
			||||
    dependencies = [
 | 
				
			||||
        ('inventory', '0002_contractor_position_truck_employee_supplycontract'),
 | 
				
			||||
        ('product_directory', '0001_initial'),
 | 
				
			||||
    ]
 | 
				
			||||
 | 
				
			||||
    operations = [
 | 
				
			||||
        migrations.AlterField(
 | 
				
			||||
            model_name='stockoperation',
 | 
				
			||||
            name='product',
 | 
				
			||||
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='product_directory.product', verbose_name='Товар'),
 | 
				
			||||
        ),
 | 
				
			||||
        migrations.AlterField(
 | 
				
			||||
            model_name='inventory',
 | 
				
			||||
            name='product',
 | 
				
			||||
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='product_directory.product', verbose_name='Товар'),
 | 
				
			||||
        ),
 | 
				
			||||
        migrations.AlterField(
 | 
				
			||||
            model_name='pricelist',
 | 
				
			||||
            name='product',
 | 
				
			||||
            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='product_directory.product', verbose_name='Товар'),
 | 
				
			||||
        ),
 | 
				
			||||
        migrations.DeleteModel(
 | 
				
			||||
            name='Product',
 | 
				
			||||
        ),
 | 
				
			||||
    ]
 | 
				
			||||
											
												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