feat: initialize full-stack architecture with backend routes, database schema, and glassmorphic frontend UI
This commit is contained in:
@@ -1,20 +1,55 @@
|
||||
import io
|
||||
import os
|
||||
import pandas as pd
|
||||
from flask import Blueprint, send_file, jsonify, make_response
|
||||
from reportlab.lib.pagesizes import letter
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.styles import getSampleStyleSheet
|
||||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||||
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.pdfbase.ttfonts import TTFont
|
||||
|
||||
from ..db import get_db_connection, release_db_connection
|
||||
from ..middleware import token_required
|
||||
|
||||
reports_blueprint = Blueprint('reports', __name__)
|
||||
|
||||
# Попытка зарегистрировать кириллический шрифт
|
||||
FONT_PATH = os.path.join(os.path.dirname(__file__), '..', 'fonts', 'DejaVuSans.ttf')
|
||||
FONT_NAME = 'DejaVuSans'
|
||||
|
||||
try:
|
||||
if os.path.exists(FONT_PATH):
|
||||
pdfmetrics.registerFont(TTFont(FONT_NAME, FONT_PATH))
|
||||
else:
|
||||
# Пытаемся найти системный шрифт на Windows или Linux
|
||||
sys_fonts = [
|
||||
'/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf',
|
||||
'C:\\Windows\\Fonts\\arial.ttf'
|
||||
]
|
||||
for f in sys_fonts:
|
||||
if os.path.exists(f):
|
||||
pdfmetrics.registerFont(TTFont(FONT_NAME, f))
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not register Cyrillic font: {e}")
|
||||
FONT_NAME = 'Helvetica' # Fallback
|
||||
|
||||
@reports_blueprint.route('/reports/cases/csv', methods=['GET'])
|
||||
@token_required
|
||||
def export_cases_csv(current_user_id):
|
||||
"""
|
||||
Экспорт всех кейсов в CSV
|
||||
---
|
||||
tags:
|
||||
- Reports
|
||||
security:
|
||||
- APIKeyHeader: []
|
||||
responses:
|
||||
200:
|
||||
description: CSV файл
|
||||
"""
|
||||
conn = get_db_connection()
|
||||
query = """
|
||||
SELECT id, platform, violator_url, violator_article, my_article, comment, status, created_at
|
||||
@@ -38,6 +73,22 @@ def export_cases_csv(current_user_id):
|
||||
@reports_blueprint.route('/reports/case/<int:case_id>/pdf', methods=['GET'])
|
||||
@token_required
|
||||
def export_case_pdf(current_user_id, case_id):
|
||||
"""
|
||||
Генерация PDF претензии для конкретного кейса
|
||||
---
|
||||
tags:
|
||||
- Reports
|
||||
security:
|
||||
- APIKeyHeader: []
|
||||
parameters:
|
||||
- name: case_id
|
||||
in: path
|
||||
type: integer
|
||||
required: true
|
||||
responses:
|
||||
200:
|
||||
description: PDF файл претензии
|
||||
"""
|
||||
conn = get_db_connection()
|
||||
cur = conn.cursor()
|
||||
cur.execute("""
|
||||
@@ -56,34 +107,62 @@ def export_case_pdf(current_user_id, case_id):
|
||||
buffer = io.BytesIO()
|
||||
doc = SimpleDocTemplate(buffer, pagesize=letter)
|
||||
styles = getSampleStyleSheet()
|
||||
|
||||
# Custom style for Russian text
|
||||
ru_style = ParagraphStyle(
|
||||
'Russian',
|
||||
parent=styles['Normal'],
|
||||
fontName=FONT_NAME,
|
||||
fontSize=10,
|
||||
leading=12
|
||||
)
|
||||
title_style = ParagraphStyle(
|
||||
'RussianTitle',
|
||||
parent=styles['Title'],
|
||||
fontName=FONT_NAME,
|
||||
fontSize=18,
|
||||
spaceAfter=20
|
||||
)
|
||||
|
||||
elements = []
|
||||
|
||||
# Title
|
||||
elements.append(Paragraph(f"Официальная претензия #{case_id}", styles['Title']))
|
||||
elements.append(Spacer(1, 12))
|
||||
# Header / Title
|
||||
elements.append(Paragraph(f"Официальная претензия #{case_id}", title_style))
|
||||
elements.append(Paragraph(f"Система мониторинга AutoMarket (OzonGuard)", ru_style))
|
||||
elements.append(Spacer(1, 20))
|
||||
|
||||
# Info table
|
||||
data = [
|
||||
["Маркетплейс", platform.upper()],
|
||||
["Дата создания", created_at.strftime('%Y-%m-%d %H:%M') if created_at else "-"],
|
||||
["Артикул нарушителя", v_art],
|
||||
["Ссылка на товар", Paragraph(v_url, styles['Normal'])],
|
||||
["Ваш артикул", my_art or "-"],
|
||||
["Текущий статус", status.upper()]
|
||||
[Paragraph("Параметр", ru_style), Paragraph("Значение", ru_style)],
|
||||
[Paragraph("Маркетплейс", ru_style), platform.upper()],
|
||||
[Paragraph("Дата создания", ru_style), created_at.strftime('%Y-%m-%d %H:%M') if created_at else "-"],
|
||||
[Paragraph("Артикул нарушителя", ru_style), v_art],
|
||||
[Paragraph("Ссылка на товар", ru_style), Paragraph(v_url, ru_style)],
|
||||
[Paragraph("Ваш артикул", ru_style), my_art or "-"],
|
||||
[Paragraph("Текущий статус", ru_style), status.upper()]
|
||||
]
|
||||
|
||||
t = Table(data, colWidths=[150, 350])
|
||||
t.setStyle(TableStyle([
|
||||
('BACKGROUND', (0, 0), (0, -1), colors.lightgrey),
|
||||
('GRID', (0, 0), (-1, -1), 1, colors.black),
|
||||
('PADDING', (0, 0), (-1, -1), 6),
|
||||
('BACKGROUND', (0, 0), (-1, 0), colors.HexColor('#2c3e50')),
|
||||
('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
|
||||
('ALIGN', (0, 0), (-1, -1), 'LEFT'),
|
||||
('FONTNAME', (0, 0), (-1, -1), FONT_NAME),
|
||||
('FONTSIZE', (0, 0), (-1, -1), 10),
|
||||
('BOTTOMPADDING', (0, 0), (-1, 0), 12),
|
||||
('BACKGROUND', (0, 1), (-1, -1), colors.whitesmoke),
|
||||
('GRID', (0, 0), (-1, -1), 1, colors.grey),
|
||||
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
|
||||
]))
|
||||
elements.append(t)
|
||||
elements.append(Spacer(1, 24))
|
||||
|
||||
# Description
|
||||
elements.append(Paragraph("Описание нарушения:", styles['Heading2']))
|
||||
elements.append(Paragraph(comment or "Комментарий отсутствует", styles['Normal']))
|
||||
elements.append(Paragraph("Описание нарушения:", ParagraphStyle('Sub', parent=ru_style, fontSize=12, spaceAfter=10)))
|
||||
elements.append(Paragraph(comment or "Комментарий отсутствует", ru_style))
|
||||
|
||||
elements.append(Spacer(1, 40))
|
||||
elements.append(Paragraph("Сгенерировано автоматически. Данный документ является официальным уведомлением о нарушении правил платформы.", ParagraphStyle('Footer', parent=ru_style, fontSize=8, textColor=colors.grey)))
|
||||
|
||||
doc.build(elements)
|
||||
buffer.seek(0)
|
||||
|
||||
Reference in New Issue
Block a user