From 4c3630aca234296abadc71e2e8b2043613904e17 Mon Sep 17 00:00:00 2001 From: behruz-dev Date: Wed, 20 Aug 2025 13:19:55 +0500 Subject: [PATCH] add new model and change offer models --- config/settings/base.py | 1 + config/urls.py | 1 + core/apps/counterparty/__init__.py | 0 core/apps/counterparty/admin/__init__.py | 1 + core/apps/counterparty/admin/counterparty.py | 9 +++++ core/apps/counterparty/apps.py | 10 ++++++ .../counterparty/migrations/0001_initial.py | 36 +++++++++++++++++++ core/apps/counterparty/migrations/__init__.py | 0 core/apps/counterparty/models/__init__.py | 1 + core/apps/counterparty/models/conterparty.py | 22 ++++++++++++ .../counterparty/serializers/counterparty.py | 14 ++++++++ core/apps/counterparty/urls.py | 12 +++++++ core/apps/counterparty/views/counterparty.py | 17 +++++++++ core/apps/orders/admin/offer.py | 4 +-- ...08_remove_offer_name_offer_counterparty.py | 24 +++++++++++++ core/apps/orders/models/order_offer.py | 3 +- core/apps/orders/serializers/offer.py | 26 +++++++++++--- 17 files changed, 173 insertions(+), 8 deletions(-) create mode 100644 core/apps/counterparty/__init__.py create mode 100644 core/apps/counterparty/admin/__init__.py create mode 100644 core/apps/counterparty/admin/counterparty.py create mode 100644 core/apps/counterparty/apps.py create mode 100644 core/apps/counterparty/migrations/0001_initial.py create mode 100644 core/apps/counterparty/migrations/__init__.py create mode 100644 core/apps/counterparty/models/__init__.py create mode 100644 core/apps/counterparty/models/conterparty.py create mode 100644 core/apps/counterparty/serializers/counterparty.py create mode 100644 core/apps/counterparty/urls.py create mode 100644 core/apps/counterparty/views/counterparty.py create mode 100644 core/apps/orders/migrations/0008_remove_offer_name_offer_counterparty.py diff --git a/config/settings/base.py b/config/settings/base.py index 9106bac..c636b3a 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -29,6 +29,7 @@ APPS = [ 'core.apps.projects', 'core.apps.orders', 'core.apps.finance', + 'core.apps.counterparty', ] PACKAGES = [ diff --git a/config/urls.py b/config/urls.py index 65fdd3d..17191e3 100644 --- a/config/urls.py +++ b/config/urls.py @@ -34,6 +34,7 @@ urlpatterns = [ path('products/', include('core.apps.products.urls')), path('orders/', include('core.apps.orders.urls')), path('finance/', include('core.apps.finance.urls')), + path('counterparties/', include('core.apps.counterparty.urls')), ] )), diff --git a/core/apps/counterparty/__init__.py b/core/apps/counterparty/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/apps/counterparty/admin/__init__.py b/core/apps/counterparty/admin/__init__.py new file mode 100644 index 0000000..8994856 --- /dev/null +++ b/core/apps/counterparty/admin/__init__.py @@ -0,0 +1 @@ +from .counterparty import * \ No newline at end of file diff --git a/core/apps/counterparty/admin/counterparty.py b/core/apps/counterparty/admin/counterparty.py new file mode 100644 index 0000000..10431d5 --- /dev/null +++ b/core/apps/counterparty/admin/counterparty.py @@ -0,0 +1,9 @@ +from django.contrib import admin + +from core.apps.counterparty.models import Counterparty + + +@admin.register(Counterparty) +class CounterpartyAdmin(admin.ModelAdmin): + list_display = ['id', 'name', 'person'] + \ No newline at end of file diff --git a/core/apps/counterparty/apps.py b/core/apps/counterparty/apps.py new file mode 100644 index 0000000..71d0115 --- /dev/null +++ b/core/apps/counterparty/apps.py @@ -0,0 +1,10 @@ +from django.apps import AppConfig + + +class CounterpatyConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'core.apps.counterparty' + + def ready(self): + from . import admin + \ No newline at end of file diff --git a/core/apps/counterparty/migrations/0001_initial.py b/core/apps/counterparty/migrations/0001_initial.py new file mode 100644 index 0000000..acca784 --- /dev/null +++ b/core/apps/counterparty/migrations/0001_initial.py @@ -0,0 +1,36 @@ +# Generated by Django 5.2.4 on 2025-08-20 13:13 + +import django.db.models.deletion +import uuid +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Counterparty', + fields=[ + ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('name', models.CharField(max_length=200)), + ('description', models.TextField(blank=True, null=True)), + ('start_date', models.DateField()), + ('status', models.CharField(choices=[('active', 'active'), ('inactive', 'inactive')], default='active', max_length=20)), + ('type', models.CharField(choices=[('supplier', 'supplier')], max_length=20)), + ('person', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='counterparties', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'Kontragent', + 'verbose_name_plural': 'Kontragentlar', + }, + ), + ] diff --git a/core/apps/counterparty/migrations/__init__.py b/core/apps/counterparty/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/apps/counterparty/models/__init__.py b/core/apps/counterparty/models/__init__.py new file mode 100644 index 0000000..01f8444 --- /dev/null +++ b/core/apps/counterparty/models/__init__.py @@ -0,0 +1 @@ +from .conterparty import * \ No newline at end of file diff --git a/core/apps/counterparty/models/conterparty.py b/core/apps/counterparty/models/conterparty.py new file mode 100644 index 0000000..b499ffb --- /dev/null +++ b/core/apps/counterparty/models/conterparty.py @@ -0,0 +1,22 @@ +from django.db import models + +from core.apps.shared.models import BaseModel +from core.apps.accounts.models import User + + +class Counterparty(BaseModel): + name = models.CharField(max_length=200) + description = models.TextField(null=True, blank=True) + start_date = models.DateField() + status = models.CharField( + max_length=20, choices=[('active', 'active'), ('inactive', 'inactive')], default='active' + ) + type = models.CharField(max_length=20, choices=[('supplier', 'supplier')]) + person = models.ForeignKey(User, on_delete=models.CASCADE, related_name='counterparties') + + def __str__(self): + return self.name + + class Meta: + verbose_name = 'Kontragent' + verbose_name_plural = 'Kontragentlar' diff --git a/core/apps/counterparty/serializers/counterparty.py b/core/apps/counterparty/serializers/counterparty.py new file mode 100644 index 0000000..a49ff40 --- /dev/null +++ b/core/apps/counterparty/serializers/counterparty.py @@ -0,0 +1,14 @@ +from rest_framework import serializers + +from core.apps.accounts.serializers.user import UserListSerializer +from core.apps.counterparty.models import Counterparty + + +class CounterpartySerializer(serializers.ModelSerializer): + person = UserListSerializer() + + class Meta: + model = Counterparty + fields = [ + 'id', 'name', 'type', 'status', 'description', 'start_date', 'person' + ] \ No newline at end of file diff --git a/core/apps/counterparty/urls.py b/core/apps/counterparty/urls.py new file mode 100644 index 0000000..9b0ee21 --- /dev/null +++ b/core/apps/counterparty/urls.py @@ -0,0 +1,12 @@ +from django.urls import path, include + +from core.apps.counterparty.views import counterparty as cp_views + + +urlpatterns = [ + path('counterparty/', include( + [ + path('list/', cp_views.CounterpartyListApiView.as_view()), + ] + )) +] \ No newline at end of file diff --git a/core/apps/counterparty/views/counterparty.py b/core/apps/counterparty/views/counterparty.py new file mode 100644 index 0000000..95213dc --- /dev/null +++ b/core/apps/counterparty/views/counterparty.py @@ -0,0 +1,17 @@ +from rest_framework import generics, views +from rest_framework.response import Response + +from core.apps.accounts.permissions.permissions import HasRolePermission +from core.apps.shared.paginations.custom import CustomPageNumberPagination +from core.apps.counterparty.models import Counterparty +from core.apps.counterparty.serializers import counterparty as serializers + + +class CounterpartyListApiView(generics.ListAPIView): + serializer_class = serializers.CounterpartySerializer + queryset = Counterparty.objects.select_related('person') + pagination_class = [HasRolePermission] + required_permissions = [] + pagination_class = CustomPageNumberPagination + + diff --git a/core/apps/orders/admin/offer.py b/core/apps/orders/admin/offer.py index a53f291..677a7d1 100644 --- a/core/apps/orders/admin/offer.py +++ b/core/apps/orders/admin/offer.py @@ -5,6 +5,6 @@ from core.apps.orders.models import Offer @admin.register(Offer) class OfferAdmin(admin.ModelAdmin): - list_display = ['id', 'name', 'number', 'order'] - search_fields = ['name', 'phone', 'number', 'price'] + list_display = ['id', 'number', 'order'] + search_fields = ['phone', 'number', 'price'] diff --git a/core/apps/orders/migrations/0008_remove_offer_name_offer_counterparty.py b/core/apps/orders/migrations/0008_remove_offer_name_offer_counterparty.py new file mode 100644 index 0000000..144fdf0 --- /dev/null +++ b/core/apps/orders/migrations/0008_remove_offer_name_offer_counterparty.py @@ -0,0 +1,24 @@ +# Generated by Django 5.2.4 on 2025-08-20 13:16 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('counterparty', '0001_initial'), + ('orders', '0007_alter_order_project_folder'), + ] + + operations = [ + migrations.RemoveField( + model_name='offer', + name='name', + ), + migrations.AddField( + model_name='offer', + name='counterparty', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='offers', to='counterparty.counterparty'), + ), + ] diff --git a/core/apps/orders/models/order_offer.py b/core/apps/orders/models/order_offer.py index ac82dd3..d5f2250 100644 --- a/core/apps/orders/models/order_offer.py +++ b/core/apps/orders/models/order_offer.py @@ -2,6 +2,7 @@ from django.db import models from core.apps.shared.models import BaseModel from core.apps.orders.models import Order +from core.apps.counterparty.models import Counterparty class Offer(BaseModel): @@ -11,7 +12,7 @@ class Offer(BaseModel): ) order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name='offers') - name = models.CharField(max_length=200) + counterparty = models.ForeignKey(Counterparty, on_delete=models.CASCADE, related_name='offers', null=True) price = models.PositiveBigIntegerField() price_type = models.CharField(choices=PRICE_TYPE, default='uzs') phone = models.CharField(max_length=15, null=True, blank=True) diff --git a/core/apps/orders/serializers/offer.py b/core/apps/orders/serializers/offer.py index 21dfeea..8a93670 100644 --- a/core/apps/orders/serializers/offer.py +++ b/core/apps/orders/serializers/offer.py @@ -3,16 +3,24 @@ from django.db import transaction from rest_framework import serializers from core.apps.orders.models import Offer, Order +from core.apps.counterparty.models import Counterparty class OfferCreateSerializer(serializers.Serializer): - name = serializers.CharField() + counterparty_id = serializers.UUIDField() price = serializers.IntegerField() phone = serializers.CharField(required=False) comment = serializers.CharField(required=False) qqs = serializers.CharField(required=False) price_type = serializers.ChoiceField(Offer.PRICE_TYPE) + def validate(self, data): + counterparty = Counterparty.objects.filter(id=data['counterparty_id']).first() + if not counterparty: + raise serializers.ValidationError("Counterparty not found") + data['counterparty'] + return data + class MultipleOfferCreateSerializer(serializers.Serializer): order_id = serializers.UUIDField() @@ -31,7 +39,7 @@ class MultipleOfferCreateSerializer(serializers.Serializer): for offer in validated_data.pop('offers'): offer.append( Offer( - name=offer['name'], + counterparty=offer['counterparty'], price=offer['price'], phone=offer.get('phone'), comment=offer.get('comment'), @@ -44,22 +52,30 @@ class MultipleOfferCreateSerializer(serializers.Serializer): class OfferListSerializer(serializers.ModelSerializer): + counterparty = serializers.SerializerMethodField(method_name='get_counterparty') + class Meta: model = Offer fields = [ - 'id', 'name', 'price', 'number', 'phone', 'comment', 'qqs', 'price_type' + 'id', 'counterparty', 'price', 'number', 'phone', 'comment', 'qqs', 'price_type' ] + def get_counterparty(self, obj): + return { + 'id': obj.counterparty.id, + 'name': obj.counterparty.name + } + class OfferUpdateSerializer(serializers.ModelSerializer): class Meta: model = Offer fields = [ - 'name', 'price', 'number', 'phone', 'comment', 'qqs', 'price_type' + 'counterparty', 'price', 'number', 'phone', 'comment', 'qqs', 'price_type' ] def update(self, instance, validated_data): - instance.name = validated_data.get('name', instance.name) + instance.counterparty = validated_data.get('counterparty', instance.counterparty) instance.price = validated_data.get('price', instance.price) instance.number = validated_data.get('number', instance.number) instance.phone = validated_data.get('phone', instance.phone)