From 1544938c005834cc507e9e35408491f5e7dcc6d2 Mon Sep 17 00:00:00 2001 From: Vlad Date: Tue, 6 May 2025 20:30:51 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20PDF=20=D0=BE=D1=82=D1=87=D0=B5=D1=82=D0=BE=D0=B2?= =?UTF-8?q?=20=D0=BE=20=D1=81=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D0=BD=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=B8=D1=81=D1=82=D0=BE=D1=80=D0=B8=D0=B8=20=D0=B0?= =?UTF-8?q?=D0=B2=D1=82=D0=BE=D0=BC=D0=BE=D0=B1=D0=B8=D0=BB=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 260 +++++++++++++++++++++++++++++- requirements.txt | 3 +- templates/report_printable.html | 274 ++++++++++++++++++++++++++++++++ templates/search.html | 7 + 4 files changed, 540 insertions(+), 4 deletions(-) create mode 100644 templates/report_printable.html diff --git a/app.py b/app.py index 431bd0c..9e51878 100644 --- a/app.py +++ b/app.py @@ -13,6 +13,15 @@ import json from expiring_dict import ExpiringDict import uuid from flask_swagger_ui import get_swaggerui_blueprint +import datetime +from reportlab.pdfgen import canvas +from reportlab.lib.pagesizes import A4 +from reportlab.lib import colors +from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, Image +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +from reportlab.lib.units import inch, mm +from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT +import sys @@ -748,9 +757,6 @@ def api_reqimage(): return 'bad request!', 500 - - - @app.route("/ads.txt") def ads_txt(): try: @@ -833,6 +839,254 @@ def serve_static(filename): app.logger.error(f'Ошибка доступа к файлу {filename}: {str(e)}') return 'File not found', 404 +@app.route("/salvagereport/") +def generate_pdf_report(vin): + try: + conn = pool.acquire() + cur = conn.cursor() + user_ip = get_ip(request) + + try: + returnVal = cur.callfunc("checkip", int, [user_ip, request.headers.get("CF-IPCountry", 'None'), get_addr(user_ip), 1, 0, 0]) + except: + app.logger.error(traceback.format_exc()) + + # Get vehicle details + cur.execute("""select 'None', COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='26'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Make'),'UNKNOWN') make, + COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='28'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Model'),'UNKNOWN') model, + COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='29'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Model Year'),'UNKNOWN') year, + COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='5'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Body Class'),'UNKNOWN') body, + COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='13'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Engine Model'),'UNKNOWN') engine, + COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='9'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Engine Number of Cylinders'),'UNKNOWN') celindr, + COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='15'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Drive Type'),'-') drive + from (select substr(:p1,1,10) svin, :p1 vin from dual) s """, {'p1': vin}) + det = cur.fetchall() + + # Get salvage history + cur.execute("""select rownum, t.vin, t.title, t.odo, t.odos, t.dem1, t.dem2, t.year||'/'||t.month from salvagedb t where vin = :p1 and svin = substr(:p1,1,10) """, {'p1': vin}) + his = cur.fetchall() + + # Generate current date and report ID + now = datetime.datetime.now() + report_date = now.strftime("%Y-%m-%d") # Только дата без времени + report_id = uuid.uuid4().hex[:8].upper() + current_year = now.year + + try: + # Создаем буфер для PDF + buffer = io.BytesIO() + + # Создаем PDF документ + doc = SimpleDocTemplate( + buffer, + pagesize=A4, + rightMargin=20*mm, + leftMargin=20*mm, + topMargin=15*mm, + bottomMargin=20*mm + ) + + # Создаем стили для текста + styles = getSampleStyleSheet() + styles.add(ParagraphStyle( + name='ReportTitle', + parent=styles['Heading1'], + fontSize=16, + alignment=TA_CENTER + )) + styles.add(ParagraphStyle( + name='ReportSubtitle', + parent=styles['Normal'], + fontSize=12, + alignment=TA_CENTER, + textColor=colors.HexColor('#0066CC') # Синий цвет для акцента + )) + styles.add(ParagraphStyle( + name='Right', + parent=styles['Normal'], + fontSize=10, + alignment=TA_RIGHT + )) + styles.add(ParagraphStyle( + name='Disclaimer', + parent=styles['Normal'], + fontSize=8, + textColor=colors.gray + )) + styles.add(ParagraphStyle( + name='SalvageDBFooter', + parent=styles['Normal'], + fontSize=9, + alignment=TA_CENTER, + textColor=colors.HexColor('#0066CC'), # Синий цвет для акцента + fontName='Helvetica-Bold' + )) + + # Список элементов для PDF + elements = [] + + # Путь к логотипу + # logo_path = os.path.join(app_path, 'static', 'icons', 'icon-144x144.png') + + # Добавляем логотип, если файл существует + # if os.path.exists(logo_path): + # logo_img = Image(logo_path, width=30*mm, height=30*mm) + # logo_img.hAlign = 'CENTER' + # elements.append(logo_img) + # elements.append(Spacer(1, 5*mm)) + + # Заголовок отчета с акцентом на SALVAGEDB.COM + elements.append(Paragraph("Vehicle History Report", styles['ReportTitle'])) + elements.append(Paragraph("SALVAGEDB.COM - Comprehensive Vehicle History Check", styles['ReportSubtitle'])) + elements.append(Spacer(1, 10*mm)) + + # Информация об отчете + elements.append(Paragraph(f"Report Date: {report_date}", styles['Right'])) + elements.append(Paragraph(f"Report ID: {report_id}", styles['Right'])) + elements.append(Spacer(1, 15*mm)) + + # Информация о транспортном средстве + elements.append(Paragraph("Vehicle Information", styles['Heading2'])) + + # Создаем таблицу с информацией о транспортном средстве + vehicle_data = [ + ["VIN", vin], + ["Make", det[0][1]], + ["Model", det[0][2]], + ["Year", det[0][3]], + ["Body Style", det[0][4]], + ["Engine", det[0][5]], + ["Cylinders", det[0][6]], + ["Drive", det[0][7]] + ] + + vehicle_table = Table(vehicle_data, colWidths=[100, 350]) + vehicle_table.setStyle(TableStyle([ + ('BACKGROUND', (0, 0), (0, -1), colors.lightgrey), + ('TEXTCOLOR', (0, 0), (0, -1), colors.black), + ('ALIGN', (0, 0), (0, -1), 'LEFT'), + ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'), + ('FONTSIZE', (0, 0), (0, -1), 10), + ('BOTTOMPADDING', (0, 0), (-1, -1), 6), + ('BACKGROUND', (1, 0), (-1, -1), colors.white), + ('GRID', (0, 0), (-1, -1), 0.5, colors.black), + ])) + + elements.append(vehicle_table) + elements.append(Spacer(1, 10*mm)) + + # Информация об истории + if his: + elements.append(Paragraph("Salvage History", styles['Heading2'])) + + # Создаем стиль для ячеек с переносом текста + table_style = TableStyle([ + ('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey), + ('TEXTCOLOR', (0, 0), (-1, 0), colors.black), + ('ALIGN', (0, 0), (-1, 0), 'CENTER'), + ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), + ('FONTSIZE', (0, 0), (-1, 0), 9), + ('BOTTOMPADDING', (0, 0), (-1, 0), 6), + ('BACKGROUND', (0, 1), (-1, -1), colors.white), + ('GRID', (0, 0), (-1, -1), 0.5, colors.black), + ('VALIGN', (0, 0), (-1, -1), 'TOP'), + ('LEFTPADDING', (0, 0), (-1, -1), 3), + ('RIGHTPADDING', (0, 0), (-1, -1), 3), + ('FONTSIZE', (0, 1), (-1, -1), 8), + ('WORDWRAP', (0, 0), (-1, -1), True), + ]) + + # Оптимизированные заголовки без VIN + history_headers = ["Title", "Mileage", "Odometer Status", "Primary\nDamage", "Secondary\nDamage"] + + # Формируем данные для таблицы истории + history_data = [history_headers] + + for item in his: + title_value = item[2] + primary_damage = item[5] if item[5] else "N/A" + secondary_damage = item[6] if item[6] else "N/A" + + # Добавляем строку с данными, убрав VIN + history_data.append([ + title_value, + str(item[3]), + item[4], + primary_damage, + secondary_damage + ]) + + # Устанавливаем ширину колонок оптимально для A4 + available_width = doc.width # Доступная ширина страницы + col_widths = [ + available_width * 0.20, # 20% для Title + available_width * 0.12, # 12% для Mileage + available_width * 0.18, # 18% для Odometer Status + available_width * 0.25, # 25% для Primary Damage + available_width * 0.25 # 25% для Secondary Damage + ] + + # Создаем таблицу с правильными размерами + history_table = Table(history_data, colWidths=col_widths, repeatRows=1) + history_table.setStyle(table_style) + + elements.append(history_table) + else: + elements.append(Paragraph("Salvage History", styles['Heading2'])) + elements.append(Paragraph("Salvage history not found.", styles['Normal'])) + + elements.append(Spacer(1, 15*mm)) + + # Дисклеймер + disclaimer_text = """ + Disclaimer: This report provides information about salvage or junk vehicles; damage from hail, flood or fire; + mileage discrepancies or odometer rollback; and gray market vehicles. We do not claim that the car got in our + databank has salvage title, but the fact that it has been damaged for sure. Our site helps people avoid buying + a damaged vehicle in the past. + """ + elements.append(Paragraph(disclaimer_text, styles['Disclaimer'])) + + # Футер с акцентом на SALVAGEDB.COM + elements.append(Spacer(1, 15*mm)) + elements.append(Paragraph(f"© {current_year} SALVAGEDB.COM - All Rights Reserved", styles['SalvageDBFooter'])) + elements.append(Paragraph("Visit SALVAGEDB.COM for more information about vehicle history", styles['SalvageDBFooter'])) + elements.append(Paragraph("This report is provided as is without any guarantees or warranty.", styles['Disclaimer'])) + + # Строим PDF документ + doc.build(elements) + + # Получаем содержимое буфера + pdf_data = buffer.getvalue() + buffer.close() + + # Создаем HTTP-ответ с PDF + response = make_response(pdf_data) + response.headers['Content-Type'] = 'application/pdf' + response.headers['Content-Disposition'] = f'attachment; filename=vehicle_report_{vin}.pdf' + + return response + + except Exception as pdf_error: + # Если возникла ошибка при генерации PDF, логируем ее + app.logger.error(f"PDF generation error: {str(pdf_error)}") + app.logger.error(traceback.format_exc()) + + # Показываем печатную версию в браузере + return render_template( + 'report_printable.html', + vin=vin, + det=det, + his=his, + report_date=report_date, + report_id=report_id, + current_year=current_year, + error_message=str(pdf_error) + ) + + except Exception as e: + app.logger.error(traceback.format_exc()) + return 'Report generation failed: ' + str(e), 500 + if __name__ == '__main__': # Start a pool of connections pool = start_pool() diff --git a/requirements.txt b/requirements.txt index 51f4858..4f657ca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ flask oracledb -cacheing \ No newline at end of file +cacheing +reportlab \ No newline at end of file diff --git a/templates/report_printable.html b/templates/report_printable.html new file mode 100644 index 0000000..bc9795a --- /dev/null +++ b/templates/report_printable.html @@ -0,0 +1,274 @@ + + + + + + Vehicle History Report - {{report_id}} + + + + +
+ + + {% if error_message %} +
+

PDF Generation Error

+

The system encountered an error while generating the PDF. You can print this page or save it as PDF using your browser's print function.

+
+ {{ error_message }} +
+

Note: To install WeasyPrint dependencies on Windows, see the installation instructions.

+
+ {% endif %} + +
+

Vehicle History Report

+

SALVAGEDB.COM - Comprehensive Vehicle History Check

+
+ +
+

Report Date: {{report_date}}

+

Report ID: {{report_id}}

+
+ +
+

Vehicle Information

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VIN{{vin}}
Make{{det[0][1]}}
Model{{det[0][2]}}
Year{{det[0][3]}}
Body Style{{det[0][4]}}
Engine{{det[0][5]}}
Cylinders{{det[0][6]}}
Drive{{det[0][7]}}
+
+ + {% if his %} +
+

Salvage History

+ + + + + + + + + + + + + {% for it in his %} + + + + + + + + + {% endfor %} + +
VINTitleOdometerOdometer StatusPrimary DamageSecondary Damage
{{it[1]}}{{it[2]}}{{it[3]}}{{it[4]}}{{it[5]}}{{it[6]}}
+
+ {% else %} +
+
+

Salvage history not found.

+
+
+ {% endif %} + +
+

Disclaimer: This report provides information about salvage or junk vehicles; damage from hail, flood or fire; mileage discrepancies or odometer rollback; and gray market vehicles. We do not claim that the car got in our databank has salvage title, but the fact that it has been damaged for sure. Our site helps people avoid buying a damaged vehicle in the past.

+
+ + +
+ + \ No newline at end of file diff --git a/templates/search.html b/templates/search.html index 2e082bf..29b6aa4 100644 --- a/templates/search.html +++ b/templates/search.html @@ -54,6 +54,13 @@ + + +