diff --git a/README.md b/README.md index 232b1e5..da7b3a6 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,27 @@ pip install -r requirements.txt Процесс разделен на три простых шага в терминале: +### ШАГ 0: Подготовить пакетнйы скрипт, если файлов больше чем 1. + +Нужно сохранить все файлы с КМ в одну папку. + +потом запустить команду, которая достанет имена файлов из этой директории: + +```bash +python batch_extractor.py get-filenames "C:\путь\к\архиву\" "C:\путь\к\временным файлам\filenames.xlsx" +``` + +эта команда создаст файл структуры Префикс/Имя файла + +затем надо открыть файл filenames_exaple.xlsx из папки resources и дополнить созданный файл формулами, которые создадут полную питон команду для экстракции КМ + +Затем открыть файл resources\run_task_example.ps1 - и вставить в него получившиеся команды + +### Важно!! Обратить внимание на тип файла, для pdf надо использовать команду from-pdf и аналогично для zip. Кроме того, обязательно надо заменить путь к файлам (он забит в формуле) + +потом можно запустить скрипт пакетной обработки. + + ### ШАГ 1: Извлечение кодов в Excel (Создание списка КМ) Возьмите файл, который вы скачали из системы «Честный ЗНАК», и натравите на него утилиту-экстрактор. diff --git a/batch_extractor.py b/batch_extractor.py index c876b8e..7ed7ab6 100644 --- a/batch_extractor.py +++ b/batch_extractor.py @@ -6,6 +6,23 @@ from read_image import read_datamatrix_zxing, extract_barcodes_from_pdf from pathlib import Path import click import render_eps +import hashlib +import random +from datetime import datetime + +def get_dated_hash(text: str) -> str: + """ + Генерирует хэш с префиксом даты в формате ГГ_ММ_ДД. + Пример вывода: 26_02_21_d6f7a6b2c1 + """ + # 1. Получаем текущую дату в формате ГГ_ММ_ДД (например, 26_02_21) + date_prefix = datetime.now().strftime("%y_%m_%d") + text = f"{text}_{random.randint(0,100000000)}" + # 2. Генерируем хэш-часть (SHA-256, 10 символов) + hash_part = hashlib.sha256(text.encode()).hexdigest()[:10] + + # 3. Соединяем через нижнее подчеркивание + return f"{date_prefix}_{hash_part}" def extract_eps_from_zip(zip_path: str) -> list: """ @@ -124,5 +141,15 @@ def from_pdf(input_pdf: Path, output_xlsx: Path): except Exception as e: click.secho(f"Ошибка при обработке PDF: {e}", fg="red") +@cli.command(help="Подготавливает excel с именами файлов в директории") +@click.argument('input_dir', type=click.Path(exists=True, file_okay=False, dir_okay=True, path_type=Path)) +@click.argument('output_xlsx', type=click.Path(dir_okay=False, writable=True, path_type=Path)) +def get_filenames(input_dir: Path, output_xlsx: Path): + # Используем .glob('*') или .iterdir() + filenames = [f.name for f in input_dir.iterdir() if f.is_file()] + filenames_with_hashes = [(get_dated_hash(f), f) for f in filenames] + save_to_excel(filenames_with_hashes, str(output_xlsx)) + + if __name__ == "__main__": cli() diff --git a/build_pdf.py b/build_pdf.py index 18b6e98..d4c42ca 100644 --- a/build_pdf.py +++ b/build_pdf.py @@ -17,6 +17,7 @@ from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT from read_image import read_datamatrix_zxing import render_eps import treepoem +import html import click from pathlib import Path @@ -184,7 +185,7 @@ def place_text(c: canvas.Canvas, text: str, font_name: str, while True: # Заменяем явные переносы на HTML-тег для ReportLab - text_for_pdf = current_text.replace('\n', '
') + text_for_pdf = html.escape(current_text) p = Paragraph(text_for_pdf, style) # Вычисляем реальные размеры @@ -262,13 +263,13 @@ def draw_label_page(c: canvas.Canvas, font_name: str, dm_image: ImageReader, lab eac_image_y = CHZ_image_y c.drawImage(load_png('resources/eac-conformity-mark-seeklogo.png'), eac_image_x, eac_image_y, width=eac_image_h, height=CHZ_image_h, mask='auto') - page_num_h = 2 * mm + page_num_h = 3 * mm page_num_y = CHZ_image_y - page_num_h - 0.3 * mm place_text(c, f'№ {label_data.page_num}', font_name, - x=img_x, y=page_num_y, width=10 * mm, height=page_num_h, - font_size=8, font_min = 3, font_type='bold', align='center') - #c.drawBoundary(c, img_x, page_num_y, width=10 * mm, height=page_num_h) + x=img_x, y=page_num_y, width=20 * mm, height=page_num_h, + font_size=8, font_min = 3, font_type='bold', align='left') + #c.drawBoundary(c, img_x, page_num_y, width=20 * mm, height=page_num_h) # Ниже на левой панели у вас останется место под логотипы ЧЗ, EAC и №1 # ... (резерв места от y=0 до y=10мм) @@ -295,28 +296,28 @@ def draw_label_page(c: canvas.Canvas, font_name: str, dm_image: ImageReader, lab ean_barcode_h += 0.5 # 5. Артикул (одна строка, жирно) - art_h = 2 * mm + art_h = 3 * mm art_y = ean_barcode_y + ean_barcode_h place_text(c, f"арт.: {label_data.article}", font_name, x=right_x, y=art_y, width=right_w, height=art_h, - font_size=5, font_type='bold', align='center') + font_size=7, font_type='bold', align='center') # 4. Размер (одна строка, жирно) - size_h = 2 * mm + size_h = 3 * mm size_y = art_y + art_h place_text(c, f"размер: {label_data.size}", font_name, x=right_x, y=size_y, width=right_w, height=size_h, - font_size=5, font_type='bold', align='center') + font_size=7, font_type='bold', align='center') # 3. Цвет (одна строка, жирно) - color_h = 2 * mm + color_h = 3 * mm color_y = size_y + size_h place_text(c, f"цвет: {label_data.color}", font_name, x=right_x, y=color_y, width=right_w, height=color_h, - font_size=5, font_type='bold', align='center') + font_size=7, font_type='bold', align='center') # 2. Описание (Крупно, жирно, центр, занимает всё среднее пространство) - desc_h = 16 * mm + desc_h = 13 * mm desc_y = color_y + color_h place_text(c, f"{label_data.description}", font_name, x=right_x, y=desc_y, width=right_w, height=desc_h, @@ -360,6 +361,7 @@ def process_batch(base64_codes: list[str], excel_path: str, output_dir: str): print(f"Ошибка парсинга кода {b64[:10]}... : {e}") font_name = init_pdf_font() + total_files = len(grouped_codes) # 3. Оркестрация: 1 GTIN = 1 Многостраничный PDF файл for search_key, raw_bytes_list in grouped_codes.items(): @@ -379,7 +381,7 @@ def process_batch(base64_codes: list[str], excel_path: str, output_dir: str): label_data = LabelData.from_excel_row(target_row) # 3.2. Создаем холст PDF для группы - pdf_path = os.path.join(output_dir, f"Labels_{search_key}.pdf") + pdf_path = os.path.join(output_dir, f"{label_data.article}_{label_data.size}_{search_key}.pdf") # Размер страницы 58х40 мм c = canvas.Canvas(pdf_path, pagesize=(58*mm, 40*mm)) @@ -398,9 +400,13 @@ def process_batch(base64_codes: list[str], excel_path: str, output_dir: str): # Рисуем страницу, передавая ПОЛНЫЙ КОД МАРКИРОВКИ вместо короткого search_key draw_label_page(c, font_name, dm_image, label_data, readable_km) + print(' ', end='\r') + print(f'Обработана страница {label_data.page_num} из {len(raw_bytes_list)}', end='\r') + # Сохраняем и закрываем сгенерированный PDF документ c.save() - print(f" -> Сохранен файл: {pdf_path}") + total_files -= 1 + click.echo(f" -> Сохранен файл: {pdf_path} \n Осталось {total_files}") @click.command(help="Генерирует PDF-этикетки на основе извлеченных Base64-кодов и Excel-шаблона.") @@ -455,4 +461,9 @@ def cli(codes_xlsx: Path, template_xlsx: Path, output_dir: Path): click.secho(f"Критическая ошибка при генерации PDF: {e}", fg="red") if __name__ == "__main__": - cli() \ No newline at end of file + cli() +# Left priamry for testing reasons! +# test_str = Path('c:/Python/CRPT/LabelExtractor/data/штучка_на_печать.xlsx') +# template_path = Path('c:/Python/CRPT/LabelExtractor/data/Шаблон для загрузки этикеток.xlsx') +# output_path = Path('c:/Python/CRPT/LabelExtractor/data/output/units') +# cli(test_str,template_path, output_path) \ No newline at end of file diff --git a/resources/filenames_example.xlsx b/resources/filenames_example.xlsx new file mode 100644 index 0000000..8b6844a Binary files /dev/null and b/resources/filenames_example.xlsx differ diff --git a/resources/run_task_example.ps1 b/resources/run_task_example.ps1 new file mode 100644 index 0000000..240853e --- /dev/null +++ b/resources/run_task_example.ps1 @@ -0,0 +1,30 @@ +# 1. Устанавливаем кодировку вывода (UTF8) +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + +# 2. Переходим в корневую директорию проекта, где лежит batch_extractor.py +# Скрипт находится в \data\, поэтому поднимаемся на один уровень вверх +Set-Location "$PSScriptRoot\.." + +# 3. Активация виртуального окружения +& "c:/Python/CRPT/LabelExtractor/.venv/Scripts/Activate.ps1" + +Write-Host "--- Окружение активировано. Начинаю обработку файлов... ---" -ForegroundColor Green + +# 4. Список команд +# Используем '--' перед аргументами, если пути содержат сложные символы +python batch_extractor.py from-pdf "C:\Python\CRPT\LabelExtractor\data\тнск001, р. XL 110 шт_c090cb9d-02fa-412b-ac91-ed7446f48d6d_gtin_04639970975443_quantity_110.pdf" "C:\Python\CRPT\LabelExtractor\data\f\output\тнск001, р. XL _04639970975443_110.xlsx" +python batch_extractor.py from-pdf "C:\Python\CRPT\LabelExtractor\data\тнск001, р. XXL 20шт _c090cb9d-02fa-412b-ac91-ed7446f48d6d_gtin_04639970975450_quantity_20.pdf" "C:\Python\CRPT\LabelExtractor\data\f\output\тнск001, р. XXL _04639970975450_20.xlsx" +python batch_extractor.py from-pdf "C:\Python\CRPT\LabelExtractor\data\тнск002, р. M 20 шт_c090cb9d-02fa-412b-ac91-ed7446f48d6d_gtin_04639970975467_quantity_20.pdf" "C:\Python\CRPT\LabelExtractor\data\f\output\тнск002, р. M _04639970975467_20.xlsx" +python batch_extractor.py from-pdf "C:\Python\CRPT\LabelExtractor\data\тнск002, р. XL 90шт _c090cb9d-02fa-412b-ac91-ed7446f48d6d_gtin_04639970975481_quantity_90.pdf" "C:\Python\CRPT\LabelExtractor\data\f\output\тнск002, р. XL _04639970975481_90.xlsx" +python batch_extractor.py from-pdf "C:\Python\CRPT\LabelExtractor\data\тнск002, р. XXL 70шт_c090cb9d-02fa-412b-ac91-ed7446f48d6d_gtin_04639970975498_quantity_70.pdf" "C:\Python\CRPT\LabelExtractor\data\f\output\тнск002, р. XXL _04639970975498_70.xlsx" +python batch_extractor.py from-pdf "C:\Python\CRPT\LabelExtractor\data\тнск003, р. L 35шт_c090cb9d-02fa-412b-ac91-ed7446f48d6d_gtin_04639970975511_quantity_35.pdf" "C:\Python\CRPT\LabelExtractor\data\f\output\тнск003, р. L _04639970975511_35.xlsx" +python batch_extractor.py from-pdf "C:\Python\CRPT\LabelExtractor\data\тнск003, р. M 20 шт_c090cb9d-02fa-412b-ac91-ed7446f48d6d_gtin_04639970975504_quantity_20.pdf" "C:\Python\CRPT\LabelExtractor\data\f\output\тнск003, р. M _04639970975504_20.xlsx" +python batch_extractor.py from-pdf "C:\Python\CRPT\LabelExtractor\data\тнск003, р. XL 60 шт_c090cb9d-02fa-412b-ac91-ed7446f48d6d_gtin_04639970975528_quantity_60.pdf" "C:\Python\CRPT\LabelExtractor\data\f\output\тнск003, р. XL _04639970975528_60.xlsx" +python batch_extractor.py from-pdf "C:\Python\CRPT\LabelExtractor\data\тнск003, р. XXL 50 шт_c090cb9d-02fa-412b-ac91-ed7446f48d6d_gtin_04639970975535_quantity_50.pdf" "C:\Python\CRPT\LabelExtractor\data\f\output\тнск003, р. XXL _04639970975535_50.xlsx" +python batch_extractor.py from-pdf "C:\Python\CRPT\LabelExtractor\data\тнск004, р. L 10_a8f6b321-2601-4664-bcc0-fad7b529db76_gtin_04639970975559_quantity_10.pdf" "C:\Python\CRPT\LabelExtractor\data\f\output\тнск004, р. L _04639970975559_10.xlsx" +python batch_extractor.py from-pdf "C:\Python\CRPT\LabelExtractor\data\тнск004, р. M 5шт_c090cb9d-02fa-412b-ac91-ed7446f48d6d_gtin_04639970975542_quantity_5.pdf" "C:\Python\CRPT\LabelExtractor\data\f\output\тнск004, р. M _04639970975542_5.xlsx" +python batch_extractor.py from-pdf "C:\Python\CRPT\LabelExtractor\data\тнск004, р. XL 30шт _a8f6b321-2601-4664-bcc0-fad7b529db76_gtin_04639970975566_quantity_30.pdf" "C:\Python\CRPT\LabelExtractor\data\f\output\тнск004, р. XL _04639970975566_30.xlsx" +python batch_extractor.py from-pdf "C:\Python\CRPT\LabelExtractor\data\тнск004, р. XXL 20 шт_a8f6b321-2601-4664-bcc0-fad7b529db76_gtin_04639970975573_quantity_20.pdf" "C:\Python\CRPT\LabelExtractor\data\f\output\тнск004, р. XXL _04639970975573_20.xlsx" + + +Write-Host "--- Все задачи выполнены успешно! ---" -ForegroundColor Cyan \ No newline at end of file diff --git a/resources/ШАблон для загрузки этикеток.xlsx b/resources/ШАблон для загрузки этикеток.xlsx index 80c51d1..53c240c 100644 Binary files a/resources/ШАблон для загрузки этикеток.xlsx and b/resources/ШАблон для загрузки этикеток.xlsx differ