17 Commits

Author SHA1 Message Date
Shaxobff
617bed99ae update 2026-05-05 00:18:41 +05:00
github-actions[bot]
9b782fe7bd 🔄 Update image to 152 [CI SKIP] 2026-05-04 12:51:51 +00:00
135f580db2 Merge pull request 'shaxob' (#132) from shaxob into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m23s
Reviewed-on: #132
2026-05-04 12:49:58 +00:00
Shaxobff
0c622759cc update 2026-05-04 17:41:50 +05:00
github-actions[bot]
1d750b1c1c 🔄 Update image to 151 [CI SKIP] 2026-05-04 12:33:34 +00:00
2b26c52a5c Merge pull request 'behruz' (#131) from behruz into main
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m20s
Reviewed-on: #131
2026-05-04 12:31:44 +00:00
xoliqberdiyev
51b3535a80 write 2026-05-04 17:29:20 +05:00
Shaxobff
9028e2f102 Merge branch 'main' of https://gitea.felixits.uz/sifatbaho/backend-v1 into shaxob 2026-05-04 16:17:12 +05:00
Shaxobff
0c9c726756 add generation_pdf pdf , fix 500 error , install reportlab 2026-05-04 16:15:12 +05:00
xoliqberdiyev
c88ea1aa77 Merge branch 'main' of https://gitea.felixits.uz/sifatbaho/backend-v1 into behruz 2026-05-04 14:47:05 +05:00
github-actions[bot]
581021cbb7 🔄 Update image to 150 [CI SKIP] 2026-05-04 05:38:53 +00:00
62f65385e1 fix: DB_HOST=postgres (stack.yaml service nomi bilan mos)
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m13s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-04 10:37:11 +05:00
github-actions[bot]
76d2fe5090 🔄 Update image to 149 [CI SKIP] 2026-05-04 05:14:11 +00:00
92d23901a1 ci cd uchun test commit
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 4m28s
2026-05-04 10:12:19 +05:00
github-actions[bot]
42987e4154 🔄 Update image to 148 [CI SKIP] 2026-05-04 04:50:59 +00:00
84b14da3f4 ci cd uchun test commit
All checks were successful
Deploy to Production / build-and-deploy (push) Successful in 2m10s
2026-05-04 09:49:08 +05:00
xoliqberdiyev
cb53924f9b change 2026-04-30 16:33:00 +05:00
19 changed files with 241 additions and 49 deletions

View File

@@ -23,7 +23,7 @@ DB_ENGINE=django.db.backends.postgresql_psycopg2
DB_NAME=django DB_NAME=django
DB_USER=postgres DB_USER=postgres
DB_PASSWORD=2309 DB_PASSWORD=2309
DB_HOST=db DB_HOST=postgres
DB_PORT=5432 DB_PORT=5432
# Cache # Cache

View File

@@ -151,6 +151,11 @@ jobs:
git fetch origin main git fetch origin main
git reset --hard origin/main git reset --hard origin/main
cp .env.example .env if [ ! -f .env ]; then
cp .env.example .env
echo ".env yaratildi, production qiymatlarini kiriting!"
fi
export PORT=8085 export PORT=8085
docker pull ${{ secrets.DOCKER_USERNAME }}/${{ env.PROJECT_NAME }}:${{ github.run_number }}
docker stack deploy -c stack.yaml ${{ env.PROJECT_NAME }} --with-registry-auth docker stack deploy -c stack.yaml ${{ env.PROJECT_NAME }} --with-registry-auth

View File

@@ -13,7 +13,7 @@ from config.env import env
def home(request): def home(request):
return HttpResponse("OK: #65ab51e65224a92a4b6d488d3e8f9b21d3256876") return HttpResponse("OK: #135f580db2234f2af65e32ac4b2525506a7a033a")
urlpatterns = [ urlpatterns = [

View File

@@ -87,14 +87,8 @@ class DeleteAdminUserApiView(APIView):
permission_classes = [IsAuthenticated] permission_classes = [IsAuthenticated]
def delete(self, request, pk): def delete(self, request, pk):
if request.user.role != RoleChoice.SUPERUSER:
return Response({'detail': 'Forbidden'}, status=403)
user = get_object_or_404(User, pk=pk) user = get_object_or_404(User, pk=pk)
if user.role != RoleChoice.ADMIN:
return Response({'detail': 'This user is not an admin'}, status=400)
user.delete() user.delete()
return Response(status=204) return Response(status=204)

View File

@@ -1,8 +0,0 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
class EvaluationCategory(models.TextChoices):
AUTO = "auto_transport", _("Avtotransport")
REAL_ESTATE = "real estate", _("ko'chmas mulk")
EQUIPMENT = "equipment", _("uskuna va jihozlar")

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.7 on 2026-05-04 12:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('evaluation', '0041_rename_bonustype_bonuscategory'),
]
operations = [
migrations.AlterField(
model_name='bonuscategory',
name='category',
field=models.CharField(choices=[('lightweight_auto', 'Yengil automobil'), ('truck_car', 'Yuk automobil'), ('special_tech', 'Maxsus texnika')], max_length=50),
),
]

View File

@@ -2,7 +2,7 @@ from django.db import models
from django.db.models.fields import PositiveIntegerField from django.db.models.fields import PositiveIntegerField
from django_core.models import AbstractBaseModel from django_core.models import AbstractBaseModel
from core.apps.evaluation.choices.bonus import EvaluationCategory from core.apps.evaluation.choices.auto import AutoObjectType
class BaseValueBonus(AbstractBaseModel): class BaseValueBonus(AbstractBaseModel):
@@ -16,7 +16,7 @@ class BonusCategory(AbstractBaseModel):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
category = models.CharField( category = models.CharField(
max_length=50, max_length=50,
choices=EvaluationCategory.choices choices=AutoObjectType.choices
) )
percentage = PositiveIntegerField() percentage = PositiveIntegerField()

View File

@@ -6,7 +6,7 @@ from core.apps.evaluation.models.bonus import BonusCategory, EmployeeBonus, Base
class BaseBonusSerializer(serializers.ModelSerializer): class BaseBonusSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = BaseValueBonus model = BaseValueBonus
fields = 'id', 'base_price' fields = ['id', 'base_price']
def create(self, validated_data): def create(self, validated_data):
if BaseValueBonus.objects.exists(): if BaseValueBonus.objects.exists():
@@ -18,7 +18,7 @@ class BaseBonusSerializer(serializers.ModelSerializer):
class BonusCategorySerializer(serializers.ModelSerializer): class BonusCategorySerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = BonusCategory model = BonusCategory
fields = 'name', 'category', 'percentage' fields = ['name', 'category', 'percentage']
class BonusCategoryListSerializer(serializers.ModelSerializer): class BonusCategoryListSerializer(serializers.ModelSerializer):
@@ -26,7 +26,7 @@ class BonusCategoryListSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = BonusCategory model = BonusCategory
fields = 'id', 'name', 'category', 'percentage' , 'price' fields = ['id', 'name', 'category', 'percentage' , 'price']
def get_price(self, obj): def get_price(self, obj):
base_obj = BaseValueBonus.objects.first() base_obj = BaseValueBonus.objects.first()
@@ -39,7 +39,7 @@ class BonusCategoryListSerializer(serializers.ModelSerializer):
class BonusEmployeeBonusSerializer(serializers.ModelSerializer): class BonusEmployeeBonusSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = EmployeeBonus model = EmployeeBonus
fields = 'user', 'bonus_type', 'percentage' fields = ['user', 'bonus_type', 'percentage']
class EmployeeBonusListSerializer(serializers.ModelSerializer): class EmployeeBonusListSerializer(serializers.ModelSerializer):
@@ -47,7 +47,7 @@ class EmployeeBonusListSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = EmployeeBonus model = EmployeeBonus
fields = 'id', 'user', 'bonus_type', 'percentage' , 'price' fields = ['id', 'user', 'bonus_type', 'percentage' , 'price']
def get_price(self, obj): def get_price(self, obj):
base_obj = BaseValueBonus.objects.first() base_obj = BaseValueBonus.objects.first()

View File

@@ -87,3 +87,29 @@ class CreateVehicleSerializer(serializers.ModelSerializer):
"condition", "condition",
"position", "position",
] ]
class VehicleApplicationSerializer(serializers.Serializer):
person_name = serializers.CharField()
property_owner = serializers.CharField(max_length=100)
address = serializers.CharField(max_length=255)
marka = serializers.CharField(max_length=100)
model = serializers.CharField(max_length=100)
configuration = serializers.CharField(max_length=100)
auto_number = serializers.CharField(max_length=100)
date_created = serializers.DateTimeField()
mileage = serializers.IntegerField()
vehicle_identification = serializers.CharField(max_length=100)
engine_number = serializers.CharField(max_length=100)
colour = serializers.CharField(max_length=100)
registration_certificate_series = serializers.CharField(max_length=100)
tec_passport_number = serializers.CharField(max_length=100)
tec_passport_date = serializers.DateTimeField()
tec_passport_place = serializers.CharField(max_length=255)
body_type = serializers.CharField(max_length=100)
chassis = serializers.CharField(max_length=100)
plate = serializers.CharField(max_length=100)
value_type = serializers.CharField(max_length=100)
evaluation_purpose = serializers.CharField(max_length=100)
personal_id_number = serializers.IntegerField()
id_number = serializers.CharField(max_length=9)

View File

@@ -88,4 +88,5 @@ urlpatterns = [
)), )),
path("calculate_avg_cost/", views.AvgCostAPIView.as_view()), path("calculate_avg_cost/", views.AvgCostAPIView.as_view()),
path("vehicle_document/", views.GeneratePDFView.as_view()),
] ]

View File

@@ -1,16 +1,19 @@
# django core # django core
from django.http import HttpResponse
from django_core.mixins import BaseViewSetMixin from django_core.mixins import BaseViewSetMixin
# swagger # swagger
from drf_spectacular.utils import extend_schema from drf_spectacular.utils import extend_schema
# rest framework # rest framework
from rest_framework.permissions import AllowAny from rest_framework.permissions import AllowAny
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.viewsets import ReadOnlyModelViewSet from rest_framework.viewsets import ReadOnlyModelViewSet
# core apps # core apps
from core.apps.evaluation.models import VehicleModel from core.apps.evaluation.models import VehicleModel
from core.apps.evaluation.serializers import vehicle as serialziers from core.apps.evaluation.serializers import vehicle as serialziers, VehicleApplicationSerializer
from core.utils.generation_pdf import PDFService
@extend_schema(tags=["Vehicle"]) @extend_schema(tags=["Vehicle"])
@@ -27,3 +30,19 @@ class VehicleView(BaseViewSetMixin, ReadOnlyModelViewSet):
"retrieve": serialziers.RetrieveVehicleSerializer, "retrieve": serialziers.RetrieveVehicleSerializer,
"create": serialziers.CreateVehicleSerializer, "create": serialziers.CreateVehicleSerializer,
} }
@extend_schema(tags=['GenerationDocument'], request=VehicleApplicationSerializer)
class GeneratePDFView(APIView):
permission_classes = [IsAuthenticated]
def post(self, request):
serializer = VehicleApplicationSerializer(data=request.data)
if not serializer.is_valid():
return Response(serializer.errors, status=400)
pdf_buffer = PDFService.generate_vehicle_pdf(serializer.validated_data)
response = HttpResponse(pdf_buffer, content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="ariza.pdf"'
return response

View File

@@ -2,7 +2,7 @@ from rest_framework import serializers
from core.apps.tasks.serializers.comment import CommentSerializer from core.apps.tasks.serializers.comment import CommentSerializer
from core.apps.tasks.serializers.task import TaskSerializer from core.apps.tasks.serializers.task import TaskSerializer
from core.apps.tasks.models import Column, Task from core.apps.tasks.models import Column
class BoardTaskSerializer(TaskSerializer): class BoardTaskSerializer(TaskSerializer):

View File

@@ -1,16 +1,17 @@
from django.db import transaction from django.db import transaction
from rest_framework import serializers from rest_framework import serializers
from core.apps.accounts.serializers import UserSerializer
from core.apps.tasks.models.comment import Comment from core.apps.tasks.models.comment import Comment
from core.apps.tasks.models.task import Task
class CommentSerializer(serializers.ModelSerializer): class CommentSerializer(serializers.ModelSerializer):
created_by = UserSerializer(read_only=True)
class Meta: class Meta:
model = Comment model = Comment
fields = [ fields = [
'id', 'message', 'file', 'type', 'created_by' 'id', 'message', 'file', 'type', 'created_at', 'created_by'
] ]
def get_created_by(self, obj): def get_created_by(self, obj):
@@ -30,15 +31,10 @@ class CommentCreateSerializer(serializers.ModelSerializer):
'id', 'message', 'file', 'type', 'task' 'id', 'message', 'file', 'type', 'task'
] ]
def validate(self, data):
task = Task.objects.filter(id=data['task']).first()
if not task:
raise serializers.ValidationError("Task not found")
data['task'] = task
return data
def create(self, validated_data): def create(self, validated_data):
with transaction.atomic(): with transaction.atomic():
task = validated_data.pop('task') comment = Comment.objects.create(
comment = Comment.objects.create(task=task, created_by=self.context['request'].user, **validated_data) created_by=self.context['request'].user,
**validated_data
)
return comment return comment

View File

@@ -20,7 +20,7 @@ urlpatterns = [
path('task/', include( path('task/', include(
[ [
path('list/', task.TaskListView.as_view()), path('list/', task.TaskListView.as_view()),
path('<int:id>/', task.TaskDetailView.as_view()), path('<int:pk>/', task.TaskDetailView.as_view()),
path('create/', task.TaskCreateView.as_view()), path('create/', task.TaskCreateView.as_view()),
] ]
)), )),

View File

@@ -5,6 +5,8 @@ from core.apps.tasks.serializers.board import BoardSerializer
from core.apps.tasks.models import Column from core.apps.tasks.models import Column
#test commit
class BoardListView(generics.ListAPIView): class BoardListView(generics.ListAPIView):
queryset = Column.objects.order_by('id') queryset = Column.objects.order_by('id')
serializer_class = BoardSerializer serializer_class = BoardSerializer

View File

@@ -1,9 +1,7 @@
from django.db import transaction from django.db import transaction
from rest_framework import permissions, generics
from rest_framework.response import Response
from drf_spectacular.utils import extend_schema from drf_spectacular.utils import extend_schema
from rest_framework import permissions, generics, status
from rest_framework.response import Response
from core.apps.tasks.models.task import Task from core.apps.tasks.models.task import Task
from core.apps.tasks.serializers.task import TaskSerializer, TaskCreateSerializer from core.apps.tasks.serializers.task import TaskSerializer, TaskCreateSerializer
@@ -18,7 +16,8 @@ class TaskCreateView(generics.GenericAPIView):
@transaction.atomic @transaction.atomic
def post(self, request): def post(self, request):
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) if not serializer.is_valid(raise_exception=True):
return Response(serializer.validated_data, status=status.HTTP_400_BAD_REQUEST)
serializer.save() serializer.save()
return Response(serializer.data) return Response(serializer.data)

View File

@@ -0,0 +1,138 @@
# services.py
from io import BytesIO
from reportlab.lib import colors
from reportlab.lib.enums import TA_RIGHT, TA_CENTER
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer
class PDFService:
@staticmethod
def generate_vehicle_pdf(data):
buffer = BytesIO()
doc = SimpleDocTemplate(
buffer, pagesize=A4,
rightMargin=40, leftMargin=40,
topMargin=40, bottomMargin=40
)
styles = getSampleStyleSheet()
cell_style = ParagraphStyle(
'CellStyle', parent=styles['Normal'],
fontSize=9, leading=11
)
cell_style_bold = ParagraphStyle(
'CellStyleBold', parent=cell_style,
fontName='Helvetica-Bold',
textColor=colors.red
)
header_style = ParagraphStyle(
'HeaderStyle', parent=styles['Normal'],
alignment=TA_RIGHT, fontSize=10, leading=12
)
title_style = ParagraphStyle(
'TitleStyle', parent=styles['Normal'],
alignment=TA_CENTER, fontSize=14, leading=16
)
elements = []
header_text = (
f'<b>"Sifat baholash" MChJ direktori<br/>'
f"T.R.To'rayevga</b><br/><br/>"
f"{data.get('address', '')}<br/>"
f"ro'yxatda turuvchi fuqaro<br/>"
f"<u>{data.get('person_name', '')}</u> tomonidan<br/>"
f"<u>Avtotransport vositasini baholash uchun</u>"
)
elements.append(Paragraph(header_text, header_style))
elements.append(Spacer(1, 25))
elements.append(Paragraph("<b>A R I Z A</b>", title_style))
elements.append(Spacer(1, 10))
elements.append(Paragraph(
"Ushbu orqali quyidagi avtotransport vositasini baholab berishingizni so'rayman:",
cell_style
))
elements.append(Spacer(1, 10))
date_created = data.get('date_created')
year_str = str(date_created.year) + " yil" if date_created else ""
tec_date = data.get('tec_passport_date')
tec_date_str = tec_date.strftime('%d.%m.%Y yil') if tec_date else ""
raw_data = [
["Mulk egasi", data.get('property_owner', '')],
["Manzil", data.get('address', '')],
["Marka", data.get('marka', '')],
["Model", data.get('model', '')],
["Komplektatsiya", data.get('configuration', '')],
["Davlat raqami", data.get('auto_number', '')],
["Ishlab chiqarilgan yili", year_str],
["Bosib o'tgan masofasi", f"{data.get('mileage', 0):,}".replace(',', ' ')],
["№ kuzov (VIN)", data.get('vehicle_identification', '')],
["№ dvigatel", data.get('engine_number', '')],
["Rang", data.get('colour', '')],
["Texnik passport seriyasi", data.get('registration_certificate_series', '')],
["Texnik passport raqami", data.get('tec_passport_number', '')],
["Texnik passport berilgan sanasi", tec_date_str],
["Texnik passport berilgan joyi", data.get('tec_passport_place', '')],
["Kuzov turi", data.get('body_type', '')],
["Shassi", data.get('chassis', '')],
["Davlat belgisi (plate)", data.get('plate', '')], # ← was missing
]
table_data = [
[Paragraph(row[0], cell_style), Paragraph(str(row[1]), cell_style_bold)]
for row in raw_data
]
table = Table(table_data, colWidths=[170, 345])
table.setStyle(TableStyle([
('GRID', (0, 0), (-1, -1), 0.5, colors.black),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('LEFTPADDING', (0, 0), (-1, -1), 6),
('TOPPADDING', (0, 0), (-1, -1), 4),
('BOTTOMPADDING', (0, 0), (-1, -1), 4),
]))
elements.append(table)
elements.append(Spacer(1, 15))
elements.append(Paragraph("<b>Baholash maqsadi:</b>", cell_style))
purpose_raw = [
["Aniqlanayotgan qiymat turi", data.get('value_type', '')],
["Baholash maqsadi", data.get('evaluation_purpose', '')],
]
purpose_data = [
[Paragraph(r[0], cell_style), Paragraph(str(r[1]), cell_style_bold)]
for r in purpose_raw
]
pt = Table(purpose_data, colWidths=[170, 345])
pt.setStyle(TableStyle([
('GRID', (0, 0), (-1, -1), 0.5, colors.black),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
]))
elements.append(pt)
elements.append(Spacer(1, 15))
elements.append(Paragraph("<b>Buyurtmachi rekvizitlari:</b>", cell_style))
footer_raw = [
["Shaxsiy raqam (PINFL)", str(data.get('personal_id_number', ''))],
["ID karta raqami", data.get('id_number', '')],
]
footer_data = [
[Paragraph(f[0], cell_style), Paragraph(str(f[1]), cell_style_bold)]
for f in footer_raw
]
ft = Table(footer_data, colWidths=[170, 345])
ft.setStyle(TableStyle([
('GRID', (0, 0), (-1, -1), 0.5, colors.black),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
]))
elements.append(ft)
doc.build(elements)
buffer.seek(0)
return buffer

View File

@@ -50,3 +50,5 @@ boto3
grpcio>=1.62.0 grpcio>=1.62.0
grpcio-tools>=1.62.0 grpcio-tools>=1.62.0
protobuf>=4.25.0 protobuf>=4.25.0
reportlab

View File

@@ -84,7 +84,7 @@ services:
max-file: "5" max-file: "5"
web: web:
image: husanjon/sifatbaho:147 image: husanjon/sifatbaho:152
env_file: env_file:
- .env - .env
environment: environment:
@@ -129,7 +129,7 @@ services:
max-file: "5" max-file: "5"
celery: celery:
image: husanjon/sifatbaho:147 image: husanjon/sifatbaho:152
env_file: env_file:
- .env - .env
environment: environment: