Add new CLI command for file name extraction and hash generation

- Implemented `get_filenames` command in `batch_extractor.py` to extract file names from a specified directory and generate a dated hash for each file.
- Updated README.md with instructions for using the new command.
- Enhanced PDF generation in `build_pdf.py` by modifying file naming conventions to include article and size.
- Added example PowerShell script for batch processing tasks.
- Introduced new example Excel file for user reference.
This commit is contained in:
2026-02-28 12:59:56 +03:00
parent 6850f3672e
commit 6a2c0d0d35
6 changed files with 104 additions and 15 deletions

View File

@@ -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 (Создание списка КМ) ### ШАГ 1: Извлечение кодов в Excel (Создание списка КМ)
Возьмите файл, который вы скачали из системы «Честный ЗНАК», и натравите на него утилиту-экстрактор. Возьмите файл, который вы скачали из системы «Честный ЗНАК», и натравите на него утилиту-экстрактор.

View File

@@ -6,6 +6,23 @@ from read_image import read_datamatrix_zxing, extract_barcodes_from_pdf
from pathlib import Path from pathlib import Path
import click import click
import render_eps 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: 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: except Exception as e:
click.secho(f"Ошибка при обработке PDF: {e}", fg="red") 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__": if __name__ == "__main__":
cli() cli()

View File

@@ -17,6 +17,7 @@ from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT
from read_image import read_datamatrix_zxing from read_image import read_datamatrix_zxing
import render_eps import render_eps
import treepoem import treepoem
import html
import click import click
from pathlib import Path from pathlib import Path
@@ -184,7 +185,7 @@ def place_text(c: canvas.Canvas, text: str, font_name: str,
while True: while True:
# Заменяем явные переносы на HTML-тег для ReportLab # Заменяем явные переносы на HTML-тег для ReportLab
text_for_pdf = current_text.replace('\n', '<br/>') text_for_pdf = html.escape(current_text)
p = Paragraph(text_for_pdf, style) 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 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') 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 page_num_y = CHZ_image_y - page_num_h - 0.3 * mm
place_text(c, f'{label_data.page_num}', font_name, place_text(c, f'{label_data.page_num}', font_name,
x=img_x, y=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='center') font_size=8, font_min = 3, font_type='bold', align='left')
#c.drawBoundary(c, img_x, page_num_y, width=10 * mm, height=page_num_h) #c.drawBoundary(c, img_x, page_num_y, width=20 * mm, height=page_num_h)
# Ниже на левой панели у вас останется место под логотипы ЧЗ, EAC и №1 # Ниже на левой панели у вас останется место под логотипы ЧЗ, EAC и №1
# ... (резерв места от y=0 до y=10мм) # ... (резерв места от 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 ean_barcode_h += 0.5
# 5. Артикул (одна строка, жирно) # 5. Артикул (одна строка, жирно)
art_h = 2 * mm art_h = 3 * mm
art_y = ean_barcode_y + ean_barcode_h art_y = ean_barcode_y + ean_barcode_h
place_text(c, f"арт.: {label_data.article}", font_name, place_text(c, f"арт.: {label_data.article}", font_name,
x=right_x, y=art_y, width=right_w, height=art_h, 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. Размер (одна строка, жирно) # 4. Размер (одна строка, жирно)
size_h = 2 * mm size_h = 3 * mm
size_y = art_y + art_h size_y = art_y + art_h
place_text(c, f"размер: {label_data.size}", font_name, place_text(c, f"размер: {label_data.size}", font_name,
x=right_x, y=size_y, width=right_w, height=size_h, 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. Цвет (одна строка, жирно) # 3. Цвет (одна строка, жирно)
color_h = 2 * mm color_h = 3 * mm
color_y = size_y + size_h color_y = size_y + size_h
place_text(c, f"цвет: {label_data.color}", font_name, place_text(c, f"цвет: {label_data.color}", font_name,
x=right_x, y=color_y, width=right_w, height=color_h, 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. Описание (Крупно, жирно, центр, занимает всё среднее пространство) # 2. Описание (Крупно, жирно, центр, занимает всё среднее пространство)
desc_h = 16 * mm desc_h = 13 * mm
desc_y = color_y + color_h desc_y = color_y + color_h
place_text(c, f"{label_data.description}", font_name, place_text(c, f"{label_data.description}", font_name,
x=right_x, y=desc_y, width=right_w, height=desc_h, 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}") print(f"Ошибка парсинга кода {b64[:10]}... : {e}")
font_name = init_pdf_font() font_name = init_pdf_font()
total_files = len(grouped_codes)
# 3. Оркестрация: 1 GTIN = 1 Многостраничный PDF файл # 3. Оркестрация: 1 GTIN = 1 Многостраничный PDF файл
for search_key, raw_bytes_list in grouped_codes.items(): 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) label_data = LabelData.from_excel_row(target_row)
# 3.2. Создаем холст PDF для группы # 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 мм # Размер страницы 58х40 мм
c = canvas.Canvas(pdf_path, pagesize=(58*mm, 40*mm)) 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 # Рисуем страницу, передавая ПОЛНЫЙ КОД МАРКИРОВКИ вместо короткого search_key
draw_label_page(c, font_name, dm_image, label_data, readable_km) 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 документ # Сохраняем и закрываем сгенерированный PDF документ
c.save() c.save()
print(f" -> Сохранен файл: {pdf_path}") total_files -= 1
click.echo(f" -> Сохранен файл: {pdf_path} \n Осталось {total_files}")
@click.command(help="Генерирует PDF-этикетки на основе извлеченных Base64-кодов и Excel-шаблона.") @click.command(help="Генерирует PDF-этикетки на основе извлеченных Base64-кодов и Excel-шаблона.")
@@ -456,3 +462,8 @@ def cli(codes_xlsx: Path, template_xlsx: Path, output_dir: Path):
if __name__ == "__main__": if __name__ == "__main__":
cli() 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)

Binary file not shown.

View File

@@ -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