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.models import ExecutorInfoModel from core.apps.documents.serializers.contract import ContractPDFRequestSerializer from core.services import CurrencyService from core.services.didox import DidoxService 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): return self._generate_pdf(request, pk, payload={}) def post(self, request, pk): 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 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(auto) 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) executor_ctx = self._executor_context() 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": executor_ctx.get("evaluator_full_name") or "—", }, "executor": executor_ctx, "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": auto.full_weight if auto.full_weight is not None else "", "empty_weight": auto.empty_weight if auto.empty_weight is not None else "", } def _customer_context(self, auto): is_legal = auto.customer_type == ObjectOwnerType.LEGAL if is_legal: name = auto.customer_legal_entity or "" tin = auto.customer_legal_inn or "" else: name = " ".join( filter(None, [ auto.customer_individual_person_l_name, auto.customer_individual_person_f_name, auto.customer_individual_person_p_name, ]) ).strip() tin = auto.customer_individual_person_passport_num or "" ctx = { "name": name, "address": auto.customer_residence or "", "phone": "", "tin": tin, "account": "", "bank": "", "mfo": "", } if is_legal and auto.customer_legal_inn: self._enrich_customer_from_didox(ctx, auto.customer_legal_inn) return ctx def _enrich_customer_from_didox(self, ctx, lookup_id): try: tin_int = int(str(lookup_id).strip()) except (TypeError, ValueError): return data = DidoxService.get_company_info(tin_int) or {} if not data: return def pick(*keys): for key in keys: value = data.get(key) if value: return str(value) return "" if not ctx["name"]: ctx["name"] = pick("name", "fullName", "shortName") if not ctx["address"]: ctx["address"] = pick("address") if not ctx["account"]: ctx["account"] = pick("account", "bankAccount") if not ctx["mfo"]: ctx["mfo"] = pick("mfo", "bankCode") 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 = CurrencyService.get_rates(target_date) return { "rur": rates.get("RUB", ""), "usd": rates.get("USD", ""), "eur": rates.get("EUR", ""), } def _executor_context(self): instance = ExecutorInfoModel.objects.order_by("-created_at").first() if not instance: return { "name": "", "address": "", "account_number": "", "tin": "", "bank": "", "mfo": "", "oked": "", "email": "", "phone": "", "evaluator_full_name": "", "evaluator_certificate": "", "license_info": "", "insurance_info": "", } return { "name": instance.name or "", "address": instance.address or "", "account_number": instance.account_number or "", "tin": instance.tin or "", "bank": instance.bank or "", "mfo": instance.mfo or "", "oked": instance.oked or "", "email": instance.email or "", "phone": instance.phone or "", "evaluator_full_name": instance.evaluator_full_name or "", "evaluator_certificate": instance.evaluator_certificate or "", "license_info": instance.license_info or "", "insurance_info": instance.insurance_info or "", } def _empty_analog(self): return { "source": "", "phone": "", "description": "", "year": "", "mileage": "", "price": "", "adjusted_price_1": "", "final_price": "", "weight": "", }