diff --git a/core/apps/orders/serializers/order.py b/core/apps/orders/serializers/order.py index 78e6055..fe125ee 100644 --- a/core/apps/orders/serializers/order.py +++ b/core/apps/orders/serializers/order.py @@ -55,4 +55,9 @@ class OrderListSerializer(serializers.ModelSerializer): fields = [ 'id', 'pharmacy', 'total_price', 'paid_price', 'advance', 'employee_name', 'order_items' - ] \ No newline at end of file + ] + + +class OrderUpdateSerializer(serializers.Serializer): + paid_price = serializers.IntegerField() + \ No newline at end of file diff --git a/core/apps/orders/urls.py b/core/apps/orders/urls.py index c046f38..8ad0fd5 100644 --- a/core/apps/orders/urls.py +++ b/core/apps/orders/urls.py @@ -16,6 +16,7 @@ urlpatterns = [ [ path('list/', order_view.OrderListApiView.as_view(), name='order-list-api'), path('create/', order_view.OrderCreateApiView.as_view(), name='order-create-api'), + path('/update/', order_view.OrderUpdateApiView.as_view(), name='order-update-api'), ] )), diff --git a/core/apps/orders/views/order.py b/core/apps/orders/views/order.py index c1eff8e..dd7e0e9 100644 --- a/core/apps/orders/views/order.py +++ b/core/apps/orders/views/order.py @@ -1,3 +1,6 @@ +# django +from django.shortcuts import get_object_or_404 + # rest framework from rest_framework import generics, permissions @@ -6,7 +9,7 @@ from drf_yasg.utils import swagger_auto_schema # orders from core.apps.orders.models import Order, OrderItem -from core.apps.orders.serializers.order import OrderCreateSerializer, OrderListSerializer +from core.apps.orders.serializers.order import OrderCreateSerializer, OrderListSerializer, OrderUpdateSerializer # shared from core.apps.shared.utils.response_mixin import ResponseMixin from core.apps.shared.serializers.base import BaseResponseSerializer, SuccessResponseSerializer @@ -54,3 +57,36 @@ class OrderListApiView(generics.GenericAPIView, ResponseMixin): return self.success_response(data=serializer.data, message='malumotlar fetch qilindi') except Exception as e: return self.error_response(data=str(e), message='xatolik') + + +class OrderUpdateApiView(generics.GenericAPIView, ResponseMixin): + serializer_class = OrderUpdateSerializer + queryset = Order.objects.all() + permission_classes = [permissions.IsAuthenticated] + + @swagger_auto_schema( + responses={ + 200: SuccessResponseSerializer(), + 400: BaseResponseSerializer(), + 500: BaseResponseSerializer(), + } + ) + def patch(self, request, id): + try: + obj = get_object_or_404(Order, id=id, user=request.user) + serializer = self.serializer_class(data=request.data) + if serializer.is_valid(): + paid_price = serializer.validated_data.get('paid_price') + obj.paid_price = paid_price + obj.save() + return self.success_response( + data=OrderListApiView(obj).data, + message='malumot tahrirlandi' + ) + return self.failure_response( + data=serializer.errors, + message='malumot tahrirlanmadi' + ) + + except Exception as e: + return self.error_response(data=str(e), message='xatolik') \ No newline at end of file diff --git a/core/apps/shared/admin/__init__.py b/core/apps/shared/admin/__init__.py index c6d16de..9217bff 100644 --- a/core/apps/shared/admin/__init__.py +++ b/core/apps/shared/admin/__init__.py @@ -3,4 +3,6 @@ from .district import * from .place import * from .pharmacy import * from .doctor import * -from .plan import * \ No newline at end of file +from .plan import * +from .tour_plan import * +from .location import * \ No newline at end of file diff --git a/core/apps/shared/admin/location.py b/core/apps/shared/admin/location.py new file mode 100644 index 0000000..f4e1e10 --- /dev/null +++ b/core/apps/shared/admin/location.py @@ -0,0 +1,15 @@ +from django.contrib import admin + +# shared +from core.apps.shared.models import Location, UserLocation + + + +@admin.register(Location) +class LocationAdmin(admin.ModelAdmin): + list_display = ['id', 'user', 'longitude', 'latitude', 'created_at'] + + +@admin.register(UserLocation) +class UserLocationAdmin(admin.ModelAdmin): + list_display = ['id', 'user', 'longitude', 'latitude'] \ No newline at end of file diff --git a/core/apps/shared/admin/tour_plan.py b/core/apps/shared/admin/tour_plan.py new file mode 100644 index 0000000..b966855 --- /dev/null +++ b/core/apps/shared/admin/tour_plan.py @@ -0,0 +1,10 @@ +from django.contrib import admin + +# shared +from core.apps.shared.models import TourPlan + + +@admin.register(TourPlan) +class TourPlanAdmin(admin.ModelAdmin): + list_display = ['id', 'district', 'longitude', 'latitude', 'user'] + \ No newline at end of file diff --git a/core/apps/shared/migrations/0007_location_tourplan_userlocation.py b/core/apps/shared/migrations/0007_location_tourplan_userlocation.py new file mode 100644 index 0000000..be59b6a --- /dev/null +++ b/core/apps/shared/migrations/0007_location_tourplan_userlocation.py @@ -0,0 +1,63 @@ +# Generated by Django 5.2 on 2025-11-25 10:50 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('shared', '0006_plan_is_done'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Location', + 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)), + ('longitude', models.FloatField()), + ('langitude', models.FloatField()), + ('district', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='locations', to='shared.district')), + ('doctor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='locations', to='shared.doctor')), + ('pharmacy', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='locations', to='shared.pharmacy')), + ('place', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='locations', to='shared.place')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='locations', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='TourPlan', + 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)), + ('latitude', models.FloatField(blank=True, null=True)), + ('longitude', models.FloatField(blank=True, null=True)), + ('district', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tour_plans', to='shared.district')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tour_plans', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + migrations.CreateModel( + name='UserLocation', + 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)), + ('latitude', models.FloatField()), + ('longitude', models.FloatField()), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_locations', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/core/apps/shared/migrations/0008_rename_langitude_location_latitude_and_more.py b/core/apps/shared/migrations/0008_rename_langitude_location_latitude_and_more.py new file mode 100644 index 0000000..bc40afb --- /dev/null +++ b/core/apps/shared/migrations/0008_rename_langitude_location_latitude_and_more.py @@ -0,0 +1,39 @@ +# Generated by Django 5.2 on 2025-11-25 11:40 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('shared', '0007_location_tourplan_userlocation'), + ] + + operations = [ + migrations.RenameField( + model_name='location', + old_name='langitude', + new_name='latitude', + ), + migrations.AlterField( + model_name='location', + name='district', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='locations', to='shared.district'), + ), + migrations.AlterField( + model_name='location', + name='doctor', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='locations', to='shared.doctor'), + ), + migrations.AlterField( + model_name='location', + name='pharmacy', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='locations', to='shared.pharmacy'), + ), + migrations.AlterField( + model_name='location', + name='place', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='locations', to='shared.place'), + ), + ] diff --git a/core/apps/shared/models/__init__.py b/core/apps/shared/models/__init__.py index 9e83596..ca4d702 100644 --- a/core/apps/shared/models/__init__.py +++ b/core/apps/shared/models/__init__.py @@ -4,4 +4,6 @@ from .district import * from .place import * from .doctor import * from .pharmacy import * -from .plan import * \ No newline at end of file +from .plan import * +from .location import * +from .tour_plan import * \ No newline at end of file diff --git a/core/apps/shared/models/location.py b/core/apps/shared/models/location.py new file mode 100644 index 0000000..8b646f5 --- /dev/null +++ b/core/apps/shared/models/location.py @@ -0,0 +1,30 @@ +from django.db import models + +# shared +from core.apps.shared.models import BaseModel, District, Doctor, Place, Pharmacy +# accounts +from core.apps.accounts.models import User + + +class Location(BaseModel): + district = models.ForeignKey(District, on_delete=models.SET_NULL, related_name='locations', null=True, blank=True) + place = models.ForeignKey(Place, on_delete=models.SET_NULL, related_name='locations', null=True, blank=True) + doctor = models.ForeignKey(Doctor, on_delete=models.SET_NULL, related_name='locations', null=True, blank=True) + pharmacy = models.ForeignKey(Pharmacy, on_delete=models.SET_NULL, related_name='locations', null=True, blank=True) + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='locations') + + longitude = models.FloatField() + latitude = models.FloatField() + + def __str__(self): + return f"{self.user} sended location from {self.longitude} long and {self.langitude} lat" + + +class UserLocation(BaseModel): + latitude = models.FloatField() + longitude = models.FloatField() + + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_locations') + + def __str__(self): + return f"{self.user}'s location: {self.longitude} long and {self.latitude} lat at {self.created_at.date}/{self.created_at.time}" \ No newline at end of file diff --git a/core/apps/shared/models/tour_plan.py b/core/apps/shared/models/tour_plan.py new file mode 100644 index 0000000..18cfcef --- /dev/null +++ b/core/apps/shared/models/tour_plan.py @@ -0,0 +1,17 @@ +from django.db import models + +# shared +from core.apps.shared.models import BaseModel, District +# accounts +from core.apps.accounts.models import User + + +class TourPlan(BaseModel): + district = models.ForeignKey(District, on_delete=models.CASCADE, related_name='tour_plans') + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='tour_plans') + + latitude = models.FloatField(null=True, blank=True) + longitude = models.FloatField(null=True, blank=True) + + def __str__(self): + return f"{self.user.first_name}'s tour plan to {self.district.name}" \ No newline at end of file diff --git a/core/apps/shared/serializers/location.py b/core/apps/shared/serializers/location.py new file mode 100644 index 0000000..44d655c --- /dev/null +++ b/core/apps/shared/serializers/location.py @@ -0,0 +1,111 @@ +# django +from django.db import transaction + +# rest framework +from rest_framework import serializers + +# shared +from core.apps.shared.models import Location, UserLocation, District, Place, Doctor, Pharmacy + + + +class LocationSerializer(serializers.ModelSerializer): + district = serializers.SerializerMethodField(method_name='get_district') + place = serializers.SerializerMethodField(method_name='get_place') + doctor = serializers.SerializerMethodField(method_name='get_doctor') + pharmacy = serializers.SerializerMethodField(method_name='get_pharmacy') + + class Meta: + model = Location + fields = [ + 'id', 'longitude', 'latitude', 'created_at', + 'district', 'place', 'doctor', 'pharmacy', + ] + + def get_district(self, obj): + return { + 'id': obj.district.id, + 'name': obj.district.name, + } if obj.district else None + + + def get_place(self, obj): + return { + 'id': obj.place.id, + 'name': obj.place.name, + } if obj.place else None + + def get_doctor(self, obj): + return { + 'id': obj.doctor.id, + 'first_name': obj.doctor.first_name, + 'last_name': obj.doctor.last_name, + } if obj.doctor else None + + def get_pharmacy(self, obj): + return { + 'id': obj.pharmacy.id, + 'name': obj.pharmacy.name, + } if obj.pharmacy else None + + +class CreateLocationSerializer(serializers.Serializer): + district_id = serializers.IntegerField(required=False) + place_id = serializers.IntegerField(required=False) + doctor_id = serializers.IntegerField(required=False) + pharmacy_id = serializers.IntegerField(required=False) + longitude = serializers.FloatField() + latitude = serializers.FloatField() + + def validate(self, data): + user = self.context.get('user') + if data.get('district_id'): + district = District.objects.filter(user=user, id=data['district_id']).first() + if not district: + raise serializers.ValidationError({"district_id": "District not found"}) + data['district'] = district + + if data.get('place_id'): + place = Place.objects.filter(user=user, id=data['place_id']).first() + if not place: + raise serializers.ValidationError({"place_id": "Place not found"}) + data['place'] = place + + if data.get('doctor_id'): + doctor = Doctor.objects.filter(user=user, id=data['doctor_id']).first() + if not doctor: + raise serializers.ValidationError({"doctor_id": "Doctor not found"}) + data['doctor'] = doctor + + if data.get('pharmacy_id'): + pharmacy = Pharmacy.objects.filter(user=user, id=data['pharmacy_id']).first() + if not pharmacy: + raise serializers.ValidationError({"pharmacy_id": "Pharmacy not found"}) + data['pharmacy'] = pharmacy + + return data + + def create(self, validated_data): + with transaction.atomic(): + return Location.objects.create( + district=validated_data.get('district'), + place=validated_data.get('place'), + doctor=validated_data.get('doctor'), + pharmacy=validated_data.get('pharmacy'), + user=self.context.get('user'), + longitude=validated_data.get('longitude'), + latitude=validated_data.get('latitude'), + ) + + +class UserLocationSerializer(serializers.Serializer): + longitude = serializers.FloatField() + latitude = serializers.FloatField() + + def create(self, validated_data): + with transaction.atomic(): + return UserLocation.objects.create( + user=self.context.get('user'), + longitude=validated_data.get('longitude'), + latitude=validated_data.get('latitude'), + ) \ No newline at end of file diff --git a/core/apps/shared/serializers/tour_plan.py b/core/apps/shared/serializers/tour_plan.py new file mode 100644 index 0000000..03fbc13 --- /dev/null +++ b/core/apps/shared/serializers/tour_plan.py @@ -0,0 +1,21 @@ +from rest_framework import serializers + +# shared +from core.apps.shared.models import TourPlan + + + +class TourPlanSerializer(serializers.ModelSerializer): + district = serializers.SerializerMethodField() + + class Meta: + model = TourPlan + fields = [ + 'id', 'district', 'longitude', 'latitude', 'created_at' + ] + + def get_district(self, obj): + return { + 'id': obj.district.id, + 'name': obj.district.name, + } \ No newline at end of file diff --git a/core/apps/shared/urls.py b/core/apps/shared/urls.py index 7a86934..f8cdd2b 100644 --- a/core/apps/shared/urls.py +++ b/core/apps/shared/urls.py @@ -12,6 +12,11 @@ from core.apps.shared.views import doctor as dc_view from core.apps.shared.views import pharmacy as ph_view # shared plan view from core.apps.shared.views import plan as plan_view +# shared location view +from core.apps.shared.views import location as location_view +# shared tour plan view +from core.apps.shared.views import tour_plan as tp_view + urlpatterns = [ # region @@ -55,5 +60,17 @@ urlpatterns = [ path('', plan_view.PlanApiView.as_view(), name='plan-create-list-api'), path('/complite/', plan_view.ComplitePlanApiView.as_view(), name='complite-plan-api'), ] + )), + path('location/', include( + [ + path('list/', location_view.LocationListApiView.as_view(), name='location-list-api'), + path('create/', location_view.LocationCreateApiView.as_view(), name='create-location-api'), + path('send/', location_view.UserLocationApiView.as_view(), name='send-user-location-api'), + ] )), -] \ No newline at end of file + path('tour_plan/', include( + [ + path('list/', tp_view.TourPlanListApiView.as_view(), name='tour-plan-list-api'), + ] + )) +] \ No newline at end of file diff --git a/core/apps/shared/views/location.py b/core/apps/shared/views/location.py new file mode 100644 index 0000000..3c34046 --- /dev/null +++ b/core/apps/shared/views/location.py @@ -0,0 +1,93 @@ +# rest framework +from rest_framework import generics, permissions + +# drf yasg +from drf_yasg.utils import swagger_auto_schema + +# shared +from core.apps.shared.models import Location, UserLocation +from core.apps.shared.serializers.base import BaseResponseSerializer, SuccessResponseSerializer +from core.apps.shared.serializers.location import CreateLocationSerializer, LocationSerializer, UserLocationSerializer +from core.apps.shared.utils.response_mixin import ResponseMixin + + + +class LocationListApiView(generics.GenericAPIView, ResponseMixin): + serializer_class = LocationSerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + return Location.objects.filter(user=self.request.user).select_related('district', 'place', 'doctor', 'pharmacy') + + @swagger_auto_schema( + responses={ + 200: SuccessResponseSerializer(), + 400: BaseResponseSerializer(), + 500: BaseResponseSerializer(), + } + ) + def get(self, request): + try: + serializer = self.serializer_class(self.get_queryset(), many=True) + return self.success_response(data=serializer.data, message='malumotlar fetch qilindi') + except Exception as e: + return self.error_response(data=str(e), message='xatolik') + + +class LocationCreateApiView(generics.GenericAPIView, ResponseMixin): + serializer_class = CreateLocationSerializer + queryset = Location.objects.all() + permission_classes = [permissions.IsAuthenticated] + + @swagger_auto_schema( + responses={ + 201: SuccessResponseSerializer(), + 400: BaseResponseSerializer(), + 500: BaseResponseSerializer(), + } + ) + def post(self, request): + try: + serializer = self.serializer_class(data=request.data, context={'user': request.user}) + if serializer.is_valid(): + obj = serializer.save() + return self.success_response( + data=LocationSerializer(obj).data, + message='malumot qoshildi' + ) + return self.failure_response( + data=serializer.errors, + message='malumot qoshilmadi' + ) + except Exception as e: + return self.error_response(data=str(e), message='xatolik') + + +# UserLocation +class UserLocationApiView(generics.GenericAPIView, ResponseMixin): + serializer_class = UserLocationSerializer + queryset = UserLocation.objects.all() + permission_classes = [permissions.IsAuthenticated] + + @swagger_auto_schema( + responses={ + 201: SuccessResponseSerializer(), + 400: BaseResponseSerializer(), + 500: BaseResponseSerializer(), + } + ) + def post(self, request): + try: + serializer = self.serializer_class(data=request.data, context={'user': request.user}) + if serializer.is_valid(): + serializer.save() + return self.success_response( + message='malumot qoshildi' + ) + return self.failure_response( + data=serializer.errors, + message='malumot qoshilmadi' + ) + except Exception as e: + return self.error_response(data=str(e), message='xatolik') + diff --git a/core/apps/shared/views/tour_plan.py b/core/apps/shared/views/tour_plan.py new file mode 100644 index 0000000..456f6c7 --- /dev/null +++ b/core/apps/shared/views/tour_plan.py @@ -0,0 +1,33 @@ +# rest framework +from rest_framework import generics, permissions + +# drf yasg +from drf_yasg.utils import swagger_auto_schema + +# shared +from core.apps.shared.models import TourPlan +from core.apps.shared.serializers.tour_plan import TourPlanSerializer +from core.apps.shared.serializers.base import BaseResponseSerializer, SuccessResponseSerializer +from core.apps.shared.utils.response_mixin import ResponseMixin + + + +class TourPlanListApiView(generics.GenericAPIView, ResponseMixin): + serializer_class = TourPlanSerializer + queryset = TourPlan.objects.all() + permission_classes = [permissions.IsAuthenticated] + + @swagger_auto_schema( + responses={ + 200: SuccessResponseSerializer(), + 400: BaseResponseSerializer(), + 500: BaseResponseSerializer(), + } + ) + def get(self, request): + try: + queryset = self.queryset.filter(user=request.user) + serializer = self.serializer_class(queryset, many=True) + return self.success_response(data=serializer.data, message='malumot fetch qilindi') + except Exception as e: + return self.error_response(data=str(e), message='xatolik') \ No newline at end of file