Files
backend-v1/core/apps/documents/views/contract.py
2026-05-05 14:37:37 +05:00

154 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
PDF generatsiya view — JSON yuborasiz, PDF olasiz.
Yuborilmagan maydonlar uchun bo'sh string ('') qaytariladi —
shunda templateda {{ ... }} qoldiqlari ko'rinmaydi.
"""
import json
from django.http import HttpResponse, JsonResponse
from django.template.loader import render_to_string
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from weasyprint import HTML
from rest_framework import permissions, views
# Kompaniya defaultlari
COMPANY_DEFAULTS = {
'company_name': 'SIFAT BAHOLASH',
'membership_number': '122',
'membership_date': '01.06.2023',
'phone_1': '(71) 278-85-85',
'phone_2': '(91) 585-77-77',
'phone_3': '(90) 535-99-99',
'email': 'sifat.baholash@gmail.com',
'bank_account': '2020 8 000 405 309 735 001',
'bank_mfo': '00440',
'inn': '307 930 412',
'director_name': 'Тураев Т.Р.',
'appraiser_name': 'Тураев Т.Р.',
'appraiser_certificate': '0988',
'appraiser_certificate_date': '17.11.2021',
'insurance_policy': '19-01-25/0000368-2025',
'insurance_start': '30.05.2025',
'insurance_end': '29.05.2026',
'market_name': 'Сергели автомобил бозори',
'city': 'Ташкент',
'year': '2026',
'total_pages': 21,
'valuation_purpose': 'для обеспечения залогом',
'valuation_purpose_full': 'определение рыночной стоимости для обеспечения залогом',
'adj_bargaining': '-9,00%',
'adj_tech': '2,00%',
'adj_equipment': '3,00%',
'cost_weight': '20',
'comp_weight': '80',
}
# Template ichida ishlatiladigan barcha maydonlar
ALL_FIELDS = [
'report_number', 'report_date', 'valuation_date',
'customer_name', 'customer_short_name', 'customer_passport',
'owner_name', 'owner_address',
'vehicle_brand', 'vehicle_model', 'vehicle_plate',
'vehicle_type', 'vehicle_year', 'vehicle_mileage',
'vehicle_engine_number', 'vehicle_body_number', 'vehicle_chassis_number',
'vehicle_color', 'vehicle_tech_passport',
'spec_type', 'spec_seats', 'spec_length', 'spec_width', 'spec_height',
'spec_wheelbase', 'spec_clearance',
'engine_type', 'engine_volume', 'engine_cylinders',
'engine_power', 'engine_torque', 'engine_max_speed',
'engine_acceleration', 'transmission',
'fuel_tank', 'trunk', 'curb_weight', 'gross_weight',
'brakes_front', 'brakes_rear', 'tires',
'cost_c0', 'cost_tf', 'cost_lf', 'cost_type_name',
'cost_kt', 'cost_kl', 'cost_omega',
'cost_wear_andrianov', 'cost_wear_expert', 'cost_wear_avg',
'cost_func_wear', 'cost_total_wear', 'cost_capital',
'cost_approach_value', 'cost_approach_value_words',
'analog_1_source', 'analog_1_year', 'analog_1_mileage', 'analog_1_price',
'analog_1_adj_1', 'analog_1_adj_2', 'analog_1_adj_3', 'analog_1_weight',
'analog_2_source', 'analog_2_year', 'analog_2_mileage', 'analog_2_price',
'analog_2_adj_1', 'analog_2_adj_2', 'analog_2_adj_3', 'analog_2_weight',
'analog_3_source', 'analog_3_year', 'analog_3_mileage', 'analog_3_price',
'analog_3_adj_1', 'analog_3_adj_2', 'analog_3_adj_3', 'analog_3_weight',
'comp_total_usd', 'comp_total_sum', 'comp_total_words',
'cost_specific', 'comp_specific', 'weighted_value',
'market_value_number', 'market_value_words',
'rate_rur', 'rate_usd', 'rate_eur',
'exploitation_period', 'retail_price',
]
def _img_or_placeholder(image_url, placeholder_text, alt='', height='200mm'):
"""Rasm URL bo'lsa <img>, bo'lmasa placeholder qaytaradi"""
if image_url:
return f'<img src="{image_url}" alt="{alt}" style="max-width:100%;max-height:{height};">'
return (
f'<div class="image-placeholder" style="height: {height};">'
f'{placeholder_text}'
f'</div>'
)
def build_context(data: dict) -> dict:
"""Templatedagi barcha o'zgaruvchilar uchun qiymat tayyorlaydi"""
# 1) Barcha maydonlarni bo'sh string bilan to'ldirish
context = {field: '' for field in ALL_FIELDS}
# 2) Kompaniya defaultlari
context.update(COMPANY_DEFAULTS)
# 3) Foydalanuvchi yuborgan ma'lumotlarni ustiga yozish
context.update(data)
# 4) Rasmlar uchun tayyor HTML
context['cover_photo_html'] = _img_or_placeholder(
data.get('vehicle_photo'),
'[ Фото автомобиля ]',
alt='Vehicle',
height='50mm',
)
context['customer_passport_html'] = _img_or_placeholder(
data.get('customer_passport_image'),
'[ Изображение паспорта заказчика ]',
alt='Паспорт заказчика',
)
context['tech_passport_html'] = _img_or_placeholder(
data.get('tech_passport_image'),
'[ Изображение технического паспорта ]',
alt='Технический паспорт',
)
context['price_list_html'] = _img_or_placeholder(
data.get('price_list_image'),
'[ Прайс-лист завода-изготовителя ]',
alt='Прайс-лист',
)
# 5) Loop maydonlar
if not context.get('analog_screenshots'):
context['analog_screenshots'] = [
{'page_num': 18, 'image': None},
{'page_num': 19, 'image': None},
{'page_num': 20, 'image': None},
]
if not context.get('vehicle_photos'):
context['vehicle_photos'] = []
return context
class GenerateApiView(views.APIView):
def post(self, request):
try:
html_string = render_to_string('report_template.html', context)
pdf_bytes = HTML(string=html_string).write_pdf()
except Exception as e:
return JsonResponse({'error': f'PDF yaratishda xato: {str(e)}'}, status=500)
filename = f'otchet_1.pdf'
response = HttpResponse(pdf_bytes, content_type='application/pdf')
response['Content-Disposition'] = f'attachment; filename="{filename}"'
return response