Files
backend-v1/core/apps/documents/views/contract.py
xoliqberdiyev bd1e8d4d66 change
2026-05-07 15:52:20 +05:00

456 lines
15 KiB
Python

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/<pk>/
POST /api/documents/generate-contract-pdf/<pk>/
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": "",
}