Исправь все ошибки в коде ниже import os
import pandas as pd
from PyQt6.QtWidgets import (
    QApplication,
    QMainWindow,
    QVBoxLayout,
    QHBoxLayout,
    QWidget,
    QPushButton,
    QListWidget,
    QLabel,
    QFileDialog,
    QMessageBox,
    QTextEdit,
)
from PyQt6.QtCore import Qt, QThread, pyqtSignal
from reportlab.lib.pagesizes import A4
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.utils import ImageReader
from datetime import datetime
import logging

# Настройка логирования
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

# Загрузка шрифтов DejaVuSans и DejaVuSans-Bold
# Убедитесь, что файлы шрифтов находятся в рабочей директории или укажите полный путь
pdfmetrics.registerFont(TTFont('DejaVuSans', 'DejaVuSans.ttf'))
pdfmetrics.registerFont(TTFont('DejaVuSans-Bold', 'DejaVuSans-Bold.ttf'))

# Установка размера страницы 297x419 мм
custom_page_size = (297 * 2.83465, 419 * 2.83465)  # Перевод мм в точки (1 мм = 2.83465 точки)

class PDFGeneratorThread(QThread):
    log_signal = pyqtSignal(str)

    def __init__(self, folder_paths, save_path, photo_files):
        super().__init__()
        self.folder_paths = folder_paths
        self.save_path = save_path
        self.photo_files = photo_files

    def run(self):
        try:
            data = self.aggregate_data()
            if data.empty:
                self.log_signal.emit("Нет данных для обработки.")
                return
            photo_data = self.aggregate_photo_data()
            self.generate_pdfs(data, photo_data)
        except Exception as e:
            self.log_signal.emit(f"Ошибка при выполнении: {e}")

    def aggregate_data(self):
        data = pd.DataFrame()
        for folder in self.folder_paths:
            for file in os.listdir(folder):
                if file.endswith(".xlsx") or file.endswith(".xls"):
                    file_path = os.path.join(folder, file)
                    try:
                        xls = pd.ExcelFile(file_path)
                        for sheet_name in xls.sheet_names:
                            file_data = pd.read_excel(file_path, sheet_name=sheet_name)
                            self.log_signal.emit(
                                f"Прочитан файл: {file_path}, лист: {sheet_name}, столбцы: {file_data.columns.tolist()}"
                            )
                            data = pd.concat([data, file_data], ignore_index=True)
                    except Exception as e:
                        self.log_signal.emit(f"Ошибка при чтении файла {file_path}: {e}")

        # Проверка имен столбцов
        required_columns = ["Период с", "Период по"]
        for col in required_columns:
            if col not in data.columns:
                self.log_signal.emit(f"Ошибка: столбец '{col}' не найден в данных.")
                return pd.DataFrame()

        # Конвертируем даты, игнорируя ошибки
        data["Период с"] = pd.to_datetime(data["Период с"], format="%d.%m.%Y", errors='coerce')
        data["Период по"] = pd.to_datetime(data["Период по"], format="%d.%m.%Y", errors='coerce')
        return data

    def aggregate_photo_data(self):
        photo_data = pd.DataFrame()
        for file_path in self.photo_files:
            try:
                file_data = pd.read_excel(file_path)
                self.log_signal.emit(
                    f"Прочитан файл с фотографиями: {file_path}, столбцы: {file_data.columns.tolist()}"
                )
                photo_data = pd.concat([photo_data, file_data], ignore_index=True)
            except Exception as e:
                self.log_signal.emit(f"Ошибка при чтении файла {file_path}: {e}")
        return photo_data

    def generate_pdfs(self, data, photo_data):
        # Группируем данные по торговым точкам
        data_by_tt = data.groupby("Код ТТ")
        for tt_code, tt_data in data_by_tt:
            tt_name = tt_data["Название ТТ"].iloc[0]
            period_start = tt_data["Период с"].min()
            period_end = tt_data["Период по"].max()

            # Преобразуем даты в строки для имени файла
            period_start_str = period_start.strftime('%Y-%m-%d') if pd.notnull(period_start) else "unknown_start"
            period_end_str = period_end.strftime('%Y-%m-%d') if pd.notnull(period_end) else "unknown_end"

            # Имя PDF файла
            pdf_name = f"{tt_code}_{tt_name}_{period_start_str}_{period_end_str}.pdf"
            pdf_path = os.path.join(self.save_path, pdf_name)

            # Создание PDF
            c = canvas.Canvas(pdf_path, pagesize=custom_page_size)
            width, height = custom_page_size

            # Заголовок
            y_position = height - 50
            self.draw_text(c, "Администратору необходимо осуществить выкладку товара на ДМП:", 30, y_position, "DejaVuSans", 10)
            y_position -= 20

            # Группируем данные по типу оборудования
            equipment_types = ["Магнит-хит", "Оборудование КА", "Навесное оборудование", "Оборудование сети", "Желтый акционный стеллаж"]
            for equipment_type in equipment_types:
                eq_data = tt_data[tt_data["Тип оборудования"] == equipment_type]
                if not eq_data.empty:
                    # Выводим заголовок типа оборудования
                    self.draw_text(c, f"Оборудование: {equipment_type}", 30, y_position, "DejaVuSans-Bold", 10)
                    y_position -= 20

                    # Основные данные по каждому типу оборудования
                    for project, project_data in eq_data.groupby("Наименование проекта"):
                        project_start_y = y_position  # Сохраняем начальную позицию для выравнивания фото
                        self.draw_text(c, f"Проект: {project}", 30, y_position, "DejaVuSans", 8)
                        y_position -= 20

                        # Добавление информации по проекту
                        if equipment_type in ["Магнит-хит", "Оборудование сети"]:
                            self.draw_text(c, f"Название оборудования: {project_data['Наименование оборудования'].iloc[0]}", 30, y_position, "DejaVuSans", 8)
                            y_position -= 20
                            self.draw_text(c, f"Полный период размещения: {project_data['Полный период размещения'].iloc[0]}", 30, y_position, "DejaVuSans", 8)
                            y_position -= 20
                            self.draw_text(c, f"Ссылка на планограмму: {project_data['Ссылка на ПГ'].iloc[0] if pd.notnull(project_data['Ссылка на ПГ'].iloc[0]) else '-'}", 30, y_position, "DejaVuSans", 8)
                            y_position -= 20
                            self.draw_text(c, f"Комментарий: {project_data['Комментарий'].iloc[0] if 'Комментарий' in project_data.columns and pd.notnull(project_data['Комментарий'].iloc[0]) else '-'}", 30, y_position, "DejaVuSans", 8)
                            y_position -= 20
                        elif equipment_type in ["Навесное оборудование", "Оборудование КА"]:
                            self.draw_text(c, f"Зона размещения: {project_data['Зона размещения'].iloc[0] if pd.notnull(project_data['Зона размещения'].iloc[0]) else '-'}", 30, y_position, "DejaVuSans", 8)
                            y_position -= 20
                            self.draw_text(c, f"Название оборудования: {project_data['Наименование оборудования'].iloc[0]}", 30, y_position, "DejaVuSans", 8)
                            y_position -= 20
                            self.draw_text(c, f"Полный период размещения: {project_data['Полный период размещения'].iloc[0]}", 30, y_position, "DejaVuSans", 8)
                            y_position -= 20
                            self.draw_text(c, f"Действия ТТ по окончанию размещения: {project_data['Действия ТТ по окончанию размещения'].iloc[0]}", 30, y_position, "DejaVuSans", 8)
                            y_position -= 20
                            self.draw_text(c, f"Комментарий: {project_data['Комментарий'].iloc[0] if 'Комментарий' in project_data.columns and pd.notnull(project_data['Комментарий'].iloc[0]) else '-'}", 30, y_position, "DejaVuSans", 8)
                            y_position -= 20
                        elif equipment_type == "Желтый акционный стеллаж":
                            self.draw_text(c, f"Зона размещения: {project_data['Зона размещения'].iloc[0]}", 30, y_position, "DejaVuSans", 8)
                            y_position -= 20
                            self.draw_text(c, f"Ссылка на планограмму: {project_data['Ссылка на ПГ'].iloc[0] if pd.notnull(project_data['Ссылка на ПГ'].iloc[0]) else '-'}", 30, y_position, "DejaVuSans", 8)
                            y_position -= 20
                            self.draw_text(c, f"Комментарий: {project_data['Комментарий'].iloc[0] if 'Комментарий' in project_data.columns and pd.notnull(project_data['Комментарий'].iloc[0]) else '-'}", 30, y_position, "DejaVuSans", 8)
                            y_position -= 20

                        # Создание таблицы с уникальными данными
                        if equipment_type in ["Магнит-хит", "Оборудование сети", "Желтый акционный стеллаж"]:
                            table_columns = ["Код позиции", "Наименование позиции", "Загрузка в шт", "Место выкладки товара (адрес)"]
                        elif equipment_type in ["Навесное оборудование", "Оборудование КА"]:
                            table_columns = ["Код позиции", "Наименование позиции", "Загрузка в шт"]

                        if all(col in project_data.columns for col in table_columns):
                            table_data = project_data[table_columns].drop_duplicates()
                            table_data = table_data.fillna("-")
                            self.draw_text(c, "Товарные позиции:", 30, y_position, "DejaVuSans", 8)
                            y_position -= 20

                            # Динамическое вычисление ширины столбцов на основе текста
                            col_widths = []
                            for col in table_columns:
                                max_text_width = max(
                                    [self.string_width(str(item), "DejaVuSans", 7) for item in table_data[col]] +
                                    [self.string_width(col, "DejaVuSans-Bold", 7)]
                                )
                                col_width = max_text_width + 10  # Добавляем немного отступа
                                col_widths.append(col_width)

                            table_width = sum(col_widths)
                            current_x = 30  # Начальная позиция X для таблицы

                            # Максимальная ширина таблицы (140 мм в пунктах)
                            max_table_width = 140 * 2.83465  # 140 мм в пунктах
                            if table_width > max_table_width:
                                scaling_factor = max_table_width / table_width
                                col_widths = [w * scaling_factor for w in col_widths]
                                table_width = max_table_width

                            # Рисуем заголовки таблицы
                            c.setFont("DejaVuSans-Bold", 7)
                            for i, col in enumerate(table_columns):
                                c.drawString(current_x + 2, y_position, col)
                                # Рисуем границу ячейки заголовка
                                c.rect(current_x, y_position - 2, col_widths[i], 12)
                                current_x += col_widths[i]
                            y_position -= 14

                            # Рисуем строки таблицы
                            c.setFont("DejaVuSans", 7)
                            for index, row in table_data.iterrows():
                                current_x = 30
                                for i, col in enumerate(table_columns):
                                    cell_text = str(row[col])
                                    c.drawString(current_x + 2, y_position, cell_text)
                                    # Рисуем границу ячейки
                                    c.rect(current_x, y_position - 2, col_widths[i], 12)
                                    current_x += col_widths[i]
                                y_position -= 14
                                # Проверяем, не вышли ли за пределы страницы
                                if y_position < 100:
                                    c.showPage()
                                    y_position = height - 50

                            # Определяем зону для фотографий
                            photo_zone_x = 30 + table_width + (10 * 2.83465)  # 10 мм отступ
                            photo_zone_width = width - photo_zone_x - (10 * 2.83465)  # 10 мм отступ справа

                            # Получаем все фотографии для текущего проекта
                            photo_rows = photo_data.loc[photo_data['Название проекта'] == project]
                            if not photo_rows.empty:
                                for _, photo_row in photo_rows.iterrows():
                                    photo_path = photo_row['Путь']
                                    if os.path.exists(photo_path):
                                        img = ImageReader(photo_path)
                                        img_width, img_height = img.getSize()
                                        aspect_ratio = img_height / img_width

                                        # Максимальная ширина и высота фотографии
                                        max_photo_width = photo_zone_width - 20  # 10 мм отступ с каждой стороны
                                        max_photo_height = project_start_y - y_position - (10 * 2.83465)  # 10 мм отступ снизу

                                        # Масштабируем изображение, сохраняя соотношение сторон
                                        scale_factor = min(max_photo_width / img_width, max_photo_height / img_height, 1)
                                        display_width = img_width * scale_factor
                                        display_height = img_height * scale_factor

                                        # Проверяем, помещается ли фотография на текущей странице
                                        if y_position - display_height < 50:
                                            c.showPage()
                                            y_position = height - 50

                                        # Рисуем изображение в выделенной зоне, выравнивая верхнюю часть с проектом
                                        c.drawImage(
                                            img,
                                            photo_zone_x + 10,  # 10 мм отступ слева от зоны фотографий
                                            project_start_y - display_height,
                                            width=display_width,
                                            height=display_height,
                                            preserveAspectRatio=True,
                                            mask='auto'
                                        )
                                    else:
                                        self.log_signal.emit(f"Фотография для проекта {project} не найдена: {photo_path}")
                            else:
                                self.log_signal.emit(f"Фотография для проекта {project} не найдена.")

                            # Добавляем небольшой отступ между проектами
                            y_position -= 10  # 10 мм отступ

                            # Проверяем, не нужно ли начать новую страницу
                            if y_position < 100:
                                c.showPage()
                                y_position = height - 50

        def string_width(self, text, font_name, font_size):
            """Вспомогательная функция для расчета ширины текста."""
            return pdfmetrics.stringWidth(text, font_name, font_size)

        def draw_text(self, canvas_obj, text, x, y, font_name, font_size):
            """Вспомогательный метод для рисования текста."""
            canvas_obj.setFont(font_name, font_size)
            canvas_obj.drawString(x, y, text)

class PDFGeneratorApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Excel to PDF Converter")
        self.folder_paths = []
        self.save_path = ""
        self.photo_files = []

        # Основной виджет
        main_widget = QWidget()
        self.setCentralWidget(main_widget)

        # Основной макет
        main_layout = QVBoxLayout()
        main_widget.setLayout(main_layout)

        # Список для отображения выбранных путей к папкам
        self.folder_listbox = QListWidget()
        main_layout.addWidget(self.folder_listbox)

        # Кнопки для управления папками
        folder_buttons_layout = QHBoxLayout()
        main_layout.addLayout(folder_buttons_layout)

        self.add_folder_button = QPushButton("Добавить папку")
        self.add_folder_button.clicked.connect(self.add_folder)
        folder_buttons_layout.addWidget(self.add_folder_button)

        self.remove_folder_button = QPushButton("Удалить папку")
        self.remove_folder_button.clicked.connect(self.remove_folder)
        folder_buttons_layout.addWidget(self.remove_folder_button)

        # Кнопка для выбора пути сохранения
        self.save_path_button = QPushButton("Выбрать путь для сохранения PDF")
        self.save_path_button.clicked.connect(self.choose_save_path)
        main_layout.addWidget(self.save_path_button)

        # Поле для отображения пути для сохранения PDF
        self.save_path_label = QLabel("Путь для сохранения PDF не выбран")
        main_layout.addWidget(self.save_path_label)

        # Кнопка для выбора файлов с фотографиями
        self.photo_files_button = QPushButton("Выбрать файлы с фотографиями")
        self.photo_files_button.clicked.connect(self.choose_photo_files)
        main_layout.addWidget(self.photo_files_button)

        # Поле для отображения пути к файлам с фотографиями
        self.photo_files_label = QLabel("Файлы с фотографиями не выбраны")
        main_layout.addWidget(self.photo_files_label)

        # Кнопка для запуска программы
        self.start_button = QPushButton("Запуск")
        self.start_button.clicked.connect(self.start_processing)
        main_layout.addWidget(self.start_button)

        # Кнопка для закрытия программы
        self.close_button = QPushButton("Закрыть")
        self.close_button.clicked.connect(self.close)
        main_layout.addWidget(self.close_button)

        # Поле для вывода логов
        self.log_text_edit = QTextEdit()
        self.log_text_edit.setReadOnly(True)
        main_layout.addWidget(self.log_text_edit)

        # Применение стилей
        self.apply_styles()

    def apply_styles(self):
        stylesheet = """
        QMainWindow {
            background-color: #f0f0f0;
        }
        QPushButton {
            background-color: #4CAF50;
            color: white;
            padding: 10px 20px;
            border: none;
            border-radius: 5px;
            font-size: 14px;
            margin: 5px;
        }
        QPushButton:hover {
            background-color: #45a049;
        }
        QListWidget {
            background-color: white;
            border: 1px solid #ddd;
            border-radius: 5px;
            padding: 10px;
            font-size: 14px;
        }
        QLabel {
            font-size: 14px;
            color: #333;
        }
        QTextEdit {
            background-color: white;
            border: 1px solid #ddd;
            border-radius: 5px;
            padding: 10px;
            font-size: 12px;
        }
        """
        self.setStyleSheet(stylesheet)

    def add_folder(self):
        folder_path = QFileDialog.getExistingDirectory(self, "Выберите папку")
        if folder_path:
            self.folder_paths.append(folder_path)
            self.folder_listbox.addItem(folder_path)
            self.log_info(f"Добавлена папка: {folder_path}")

    def remove_folder(self):
        selected_items = self.folder_listbox.selectedItems()
        for item in selected_items:
            self.folder_paths.remove(item.text())
            self.folder_listbox.takeItem(self.folder_listbox.row(item))
            self.log_info(f"Удалена папка: {item.text()}")

    def choose_save_path(self):
        self.save_path = QFileDialog.getExistingDirectory(self, "Выберите путь для сохранения PDF")
        if self.save_path:
            self.save_path_label.setText(f"Сохранение в: {self.save_path}")
            self.log_info(f"Выбран путь для сохранения PDF: {self.save_path}")

    def choose_photo_files(self):
        file_paths, _ = QFileDialog.getOpenFileNames(self, "Выберите файлы с фотографиями", "", "Excel files (*.xlsx *.xls)")
        if file_paths:
            self.photo_files = file_paths
            self.photo_files_label.setText(f"Выбраны файлы: {', '.join(file_paths)}")
            self.log_info(f"Выбраны файлы с фотографиями: {', '.join(file_paths)}")

    def start_processing(self):
        if not self.folder_paths:
            QMessageBox.warning(self, "Ошибка", "Пожалуйста, выберите хотя бы одну папку с файлами Excel.")
            self.log_error("Ошибка: не выбрана ни одна папка с файлами Excel.")
            return
        if not self.save_path:
            QMessageBox.warning(self, "Ошибка", "Пожалуйста, выберите путь для сохранения PDF файлов.")
            self.log_error("Ошибка: не выбран путь для сохранения PDF файлов.")
            return
        if not self.photo_files:
            QMessageBox.warning(self, "Ошибка", "Пожалуйста, выберите файлы с фотографиями.")
            self.log_error("Ошибка: не выбраны файлы с фотографиями.")
            return

        self.pdf_thread = PDFGeneratorThread(self.folder_paths, self.save_path, self.photo_files)
        self.pdf_thread.log_signal.connect(self.log_info)
        self.pdf_thread.start()

    def log_info(self, message):
        self.log_text_edit.append(f"INFO: {message}")

    def log_error(self, message):
        self.log_text_edit.append(f"ERROR: {message}")

if __name__ == "__main__":
    app = QApplication([])
    window = PDFGeneratorApp()
    window.show()
    app.exec()