from datetime import date from decimal import Decimal from django.shortcuts import get_object_or_404 from django.template.loader import render_to_string from django.http import HttpResponse from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status from weasyprint import HTML from core.apps.evaluation.models import AutoEvaluationModel from core.apps.evaluation.choices.auto import ObjectOwnerType from core.apps.documents.serializers.contract import ContractPDFRequestSerializer from core.apps.documents.services.cbu_rates import fetch_rates UZ_MONTHS = { 1: "yanvar", 2: "fevral", 3: "mart", 4: "aprel", 5: "may", 6: "iyun", 7: "iyul", 8: "avgust", 9: "sentabr", 10: "oktabr", 11: "noyabr", 12: "dekabr", } UZ_ONES = [ "", "bir", "ikki", "uch", "to'rt", "besh", "olti", "yetti", "sakkiz", "to'qqiz", ] UZ_TENS = [ "", "o'n", "yigirma", "o'ttiz", "qirq", "ellik", "oltmish", "yetmish", "sakson", "to'qson", ] DEFAULT_INSPECTION = { "tires": "Qoniqarli", "engine": "Qoniqarli", "chassis": "Qoniqarli", "transmission": "Qoniqarli", "body": "Qoniqarli", } def _format_currency(value): if value is None: return "0" try: int_val = int(Decimal(value)) except (ValueError, TypeError): return "0" return f"{int_val:,}".replace(",", " ") def _format_date(value): if not value: return "" return value.strftime("%d.%m.%Y") def _three_digit_words(num): if num == 0: return "" words = [] hundreds = num // 100 rest = num % 100 if hundreds: if hundreds == 1: words.append("bir yuz") else: words.append(f"{UZ_ONES[hundreds]} yuz") tens = rest // 10 ones = rest % 10 if tens: words.append(UZ_TENS[tens]) if ones: words.append(UZ_ONES[ones]) return " ".join(words) def _number_to_uzbek_words(value): if value is None: return "" try: num = int(Decimal(value)) except (ValueError, TypeError): return "" if num == 0: return "nol" parts = [] billions = num // 1_000_000_000 millions = (num % 1_000_000_000) // 1_000_000 thousands = (num % 1_000_000) // 1_000 rest = num % 1_000 if billions: parts.append(f"{_three_digit_words(billions)} milliard") if millions: parts.append(f"{_three_digit_words(millions)} million") if thousands: parts.append(f"{_three_digit_words(thousands)} ming") if rest: parts.append(_three_digit_words(rest)) text = " ".join(parts).strip() return text[0].upper() + text[1:] if text else "" class ValuationReportPDFView(APIView): """ Baholash hisobotini PDF formatida yuklab olish uchun API. GET /api/documents/generate-contract-pdf// POST /api/documents/generate-contract-pdf// POST so'rov tanasida inspection malumotlarini yuborish mumkin: { "inspection": { "tires": "...", "engine": "...", "chassis": "...", "transmission": "...", "body": "..." } } """ def get(self, request, pk, *args, **kwargs): return self._generate_pdf(request, pk, payload={}) def post(self, request, pk, *args, **kwargs): serializer = ContractPDFRequestSerializer(data=request.data or {}) serializer.is_valid(raise_exception=True) return self._generate_pdf(request, pk, payload=serializer.validated_data) def _generate_pdf(self, request, pk, payload): auto_evaluation = get_object_or_404( AutoEvaluationModel.objects.select_related( "user", "vehicle", "vehicle__brand", "vehicle__model", "vehicle__color", "vehicle__fuel_type", "vehicle__body_type", ), pk=pk, ) context = self._build_context(auto_evaluation, payload) html_string = render_to_string("documents/contract.html", context) base_url = request.build_absolute_uri("/") try: pdf_file = HTML(string=html_string, base_url=base_url).write_pdf() except Exception as e: return Response( {"error": f"PDF yaratishda xatolik: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR, ) report_number = context["report"]["number"] filename = f"baholash_hisoboti_{report_number}.pdf" response = HttpResponse(pdf_file, content_type="application/pdf") response["Content-Disposition"] = f'attachment; filename="{filename}"' response["Content-Length"] = len(pdf_file) return response def _build_context(self, auto, payload): vehicle = auto.vehicle user = auto.user report_date = auto.rate_report_date or auto.contract_date or date.today() valuation_date = auto.rate_date or report_date inspection_date = auto.object_inspection_date or report_date report_number = auto.registration_number or f"{auto.pk}/{report_date.year}" # Bozor qiymati hozircha hisoblanmagan — default 0. final_value = None market_value_formatted = ( f"{_format_currency(final_value)} so'm" if final_value is not None else "0 so'm" ) market_value_words = ( f"{_number_to_uzbek_words(final_value)} so'm" if final_value is not None else "" ) cost_final = final_value comparative_final = final_value vehicle_ctx = self._vehicle_context(auto, vehicle) customer_ctx = self._customer_context(user) owner_ctx = self._owner_context(auto) contract_ctx = self._contract_context(auto, report_date) inspection_ctx = self._inspection_context(payload) rates_ctx = self._rates_context(valuation_date) ctx = { "logo_url": "", "report": { "number": report_number, "date": _format_date(report_date), "valuation_date": _format_date(valuation_date), "inspection_date": _format_date(inspection_date), "year": str(report_date.year or date.today().year), "market_value_formatted": market_value_formatted, "market_value_words": market_value_words, }, "vehicle": vehicle_ctx, "customer": customer_ctx, "owner": owner_ctx, "contract": contract_ctx, "company": { "director": "—", }, "rates": rates_ctx, "inspection": inspection_ctx, "cost": { "engine_volume": "", "factory_price": _format_currency(cost_final), "replacement_value": _format_currency(cost_final), "wear_percent": "", "final_value": _format_currency(cost_final), "final_value_words": _number_to_uzbek_words(cost_final) + (" so'm" if cost_final else ""), }, "comparative": { "final_value": _format_currency(comparative_final), "final_value_usd": "", "final_value_words": _number_to_uzbek_words(comparative_final) + (" so'm" if comparative_final else ""), }, "approach": { "cost": { "value": _format_currency(cost_final), "weight": "30%", "weighted": "", }, "comparative": { "value": _format_currency(comparative_final), "weight": "70%", "weighted": "", }, "weighted_total": _format_currency(final_value), }, "analog_1": self._empty_analog(), "analog_2": self._empty_analog(), "analog_3": self._empty_analog(), } return ctx def _vehicle_context(self, auto, vehicle): brand_name = "" model_name = "" if vehicle: brand_name = vehicle.brand.name if vehicle.brand else "" model_name = vehicle.model.name if vehicle.model else "" if not brand_name: brand_name = auto.car_brand or "" if not model_name: model_name = auto.car_model or "" full_brand = f"{brand_name} {model_name}".strip() plate_number = (vehicle.license_plate if vehicle else None) or auto.car_number or "" manufacture_year = "" if vehicle and vehicle.manufacture_year: manufacture_year = str(vehicle.manufacture_year) elif auto.manufacture_year: manufacture_year = str(auto.manufacture_year) production_date = f"{manufacture_year}-yil" if manufacture_year else "" engine_number = (vehicle.engine_number if vehicle else None) or auto.car_dvigatel_number or "" body_number = vehicle.vin_number if vehicle and vehicle.vin_number else "" color_value = "" if vehicle and vehicle.color: color_value = vehicle.color.name elif auto.car_color: color_value = auto.car_color fuel_type_value = "" if vehicle and vehicle.fuel_type: fuel_type_value = vehicle.fuel_type.name tech_passport_value = "" if vehicle and (vehicle.tech_passport_series or vehicle.tech_passport_number): tech_passport_value = ( f"{vehicle.tech_passport_series or ''} № {vehicle.tech_passport_number or ''}" ).strip() elif auto.tex_passport_serie_num: tech_passport_value = auto.tex_passport_serie_num return { "brand": full_brand, "plate_number": plate_number, "production_date": production_date, "production_year": manufacture_year, "type": auto.get_object_type_display() if auto.object_type else "", "engine_number": engine_number, "body_number": body_number, "chassis_number": body_number, "color": color_value, "tech_passport": tech_passport_value, "fuel_type": fuel_type_value, "engine_power": "", "full_weight": "", "empty_weight": "", } def _customer_context(self, user): empty = { "name": "", "address": "", "phone": "", "tin": "", "account": "", "bank": "", "mfo": "", } if not user: return empty full_name = " ".join(filter(None, [user.last_name, user.first_name])).strip() if not full_name: full_name = user.username or user.phone or "" return { "name": full_name, "address": "", "phone": user.phone or "", "tin": "", "account": "", "bank": "", "mfo": "", } def _owner_context(self, auto): if auto.object_owner_type == ObjectOwnerType.LEGAL: return { "name": auto.object_owner_legal_entity or "", "address": auto.object_owner_residence or "", } full_name = " ".join( filter(None, [ auto.object_owner_individual_person_l_name, auto.object_owner_individual_person_f_name, auto.object_owner_individual_person_p_name, ]) ).strip() return { "name": full_name, "address": auto.object_owner_residence or "", } def _contract_context(self, auto, fallback_date): contract_date = auto.contract_date or auto.rate_report_date or fallback_date return { "number": auto.registration_number or str(auto.pk), "day": f"{contract_date.day:02d}", "month": UZ_MONTHS.get(contract_date.month, ""), "year": str(contract_date.year), } def _inspection_context(self, payload): provided = (payload or {}).get("inspection") or {} return { key: provided.get(key) or default for key, default in DEFAULT_INSPECTION.items() } def _rates_context(self, target_date): rates = fetch_rates(target_date) return { "rur": rates.get("RUB", ""), "usd": rates.get("USD", ""), "eur": rates.get("EUR", ""), } def _empty_analog(self): return { "source": "", "phone": "", "description": "", "year": "", "mileage": "", "price": "", "adjusted_price_1": "", "final_price": "", "weight": "", }