Files
quality_recticel/py_app/app/pdf_generator.py

452 lines
20 KiB
Python

"""
PDF Label Generator for Print Module
Generates 80x110mm labels with sequential numbering based on quantity
"""
from reportlab.lib.pagesizes import letter, A4
from reportlab.lib.units import mm
from reportlab.lib import colors
from reportlab.pdfgen import canvas
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.platypus import Paragraph
from reportlab.lib.enums import TA_CENTER, TA_LEFT
from reportlab.graphics.barcode import code128
from reportlab.graphics import renderPDF
from reportlab.graphics.shapes import Drawing
import os
from flask import current_app
import io
def mm_to_points(mm_value):
"""Convert millimeters to points (ReportLab uses points)"""
return mm_value * mm
class LabelPDFGenerator:
def __init__(self, paper_saving_mode=True):
# Label dimensions: 80mm x 105mm (reduced from 110mm by cutting 5mm from bottom)
self.label_width = mm_to_points(80)
self.label_height = mm_to_points(105)
# Paper-saving mode: positions content at top of label to minimize waste
self.paper_saving_mode = paper_saving_mode
# Match the HTML preview dimensions exactly
# Preview: 227.4px width x 321.3px height
# Convert to proportional dimensions for 80x110mm
self.content_width = mm_to_points(60) # ~227px scaled to 80mm
self.content_height = mm_to_points(68) # Reduced by 20% from 85mm to 68mm
# Position content in label - rectangle positioned 15mm from top
# Label height: 105mm, Rectangle height: 68mm
# content_y = 105mm - 15mm - 68mm = 22mm from bottom
if self.paper_saving_mode:
# Start content from top of label with 15mm top margin
self.content_x = mm_to_points(4) # 4mm from left edge
self.content_y = mm_to_points(22) # 22mm from bottom (15mm from top)
self.top_margin = mm_to_points(15) # 15mm top margin
else:
# Original positioning
self.content_x = mm_to_points(5) # 5mm from left edge
self.content_y = mm_to_points(22) # 22mm from bottom (15mm from top)
self.top_margin = mm_to_points(15) # 15mm top margin
# Row dimensions (9 rows total, row 6 is double height)
self.row_height = self.content_height / 10 # 8.5mm per standard row
self.double_row_height = self.row_height * 2
# Column split at 40% (90.96px / 227.4px = 40%)
self.left_column_width = self.content_width * 0.4
self.right_column_width = self.content_width * 0.6
# Vertical divider starts from row 3
self.vertical_divider_start_y = self.content_y + self.content_height - (2 * self.row_height)
def generate_labels_pdf(self, order_data, quantity, printer_optimized=True):
"""
Generate PDF with multiple labels based on quantity
Creates sequential labels: CP00000711-001 to CP00000711-XXX
Optimized for thermal label printers (Epson TM-T20, Citizen CTS-310)
"""
buffer = io.BytesIO()
# Create canvas with label dimensions
c = canvas.Canvas(buffer, pagesize=(self.label_width, self.label_height))
# Optimize PDF for label printers
if printer_optimized:
self._optimize_for_label_printer(c)
# Extract base production order number for sequential numbering
prod_order = order_data.get('comanda_productie', 'CP00000000')
# Generate labels for each quantity
for i in range(1, quantity + 1):
if i > 1: # Add new page for each label except first
c.showPage()
if printer_optimized:
self._optimize_for_label_printer(c)
# Create sequential label number: CP00000711/001, CP00000711/002, etc.
sequential_number = f"{prod_order}/{i:03d}"
# Draw single label
self._draw_label(c, order_data, sequential_number, i, quantity)
c.save()
buffer.seek(0)
return buffer
def generate_single_label_pdf(self, order_data, piece_number, total_pieces, printer_optimized=True):
"""
Generate PDF with single label for specific piece number
Creates sequential label: CP00000711-001, CP00000711-002, etc.
Optimized for thermal label printers via QZ Tray
"""
buffer = io.BytesIO()
# Create canvas with label dimensions
c = canvas.Canvas(buffer, pagesize=(self.label_width, self.label_height))
# Optimize PDF for label printers
if printer_optimized:
self._optimize_for_label_printer(c)
# Extract base production order number for sequential numbering
prod_order = order_data.get('comanda_productie', 'CP00000000')
# Create sequential label number with specific piece number
sequential_number = f"{prod_order}/{piece_number:03d}"
print(f"DEBUG: Generating label {sequential_number} (piece {piece_number} of {total_pieces})")
# Draw single label with specific piece number
self._draw_label(c, order_data, sequential_number, piece_number, total_pieces)
c.save()
buffer.seek(0)
return buffer
def _optimize_for_label_printer(self, canvas):
"""
Optimize PDF settings for thermal label printers
- Sets high resolution for crisp text
- Minimizes margins to save paper
- Optimizes for monochrome printing
"""
# Set high resolution for thermal printers (300 DPI)
canvas.setPageCompression(1) # Enable compression
# Add PDF metadata for printer optimization
canvas.setCreator("Recticel Label System")
canvas.setTitle("Thermal Label - Optimized for Label Printers")
canvas.setSubject("Production Label")
# Set print scaling to none (100%) to maintain exact dimensions
canvas.setPageRotation(0)
# Add custom PDF properties for label printers
canvas._doc.info.producer = "Optimized for Epson TM-T20 / Citizen CTS-310"
def _draw_label(self, canvas, order_data, sequential_number, current_num, total_qty):
"""Draw a single label matching the HTML preview layout exactly"""
# Draw main content border (like the HTML preview rectangle)
canvas.setStrokeColor(colors.black)
canvas.setLineWidth(2)
canvas.rect(self.content_x, self.content_y, self.content_width, self.content_height)
# Calculate row positions from top
current_y = self.content_y + self.content_height
# Row 1: Company Header - "INNOFA RROMANIA SRL"
row_y = current_y - self.row_height
canvas.setFont("Helvetica-Bold", 10)
text = "INNOFA RROMANIA SRL"
text_width = canvas.stringWidth(text, "Helvetica-Bold", 10)
x_centered = self.content_x + (self.content_width - text_width) / 2
canvas.drawString(x_centered, row_y + self.row_height/3, text)
current_y = row_y
# Row 2: Customer Name
row_y = current_y - self.row_height
canvas.setFont("Helvetica-Bold", 9)
customer_name = str(order_data.get('customer_name', ''))[:30]
text_width = canvas.stringWidth(customer_name, "Helvetica-Bold", 9)
x_centered = self.content_x + (self.content_width - text_width) / 2
canvas.drawString(x_centered, row_y + self.row_height/3, customer_name)
current_y = row_y
# Draw horizontal lines after rows 1 and 2
canvas.setLineWidth(1)
canvas.line(self.content_x, current_y, self.content_x + self.content_width, current_y)
canvas.line(self.content_x, current_y + self.row_height, self.content_x + self.content_width, current_y + self.row_height)
# Draw vertical divider line (starts from row 3, goes to bottom)
vertical_x = self.content_x + self.left_column_width
canvas.line(vertical_x, current_y, vertical_x, self.content_y)
# Row 3: Quantity ordered
row_y = current_y - self.row_height
canvas.setFont("Helvetica", 8)
canvas.drawString(self.content_x + mm_to_points(2), row_y + self.row_height/3, "Quantity ordered")
canvas.setFont("Helvetica-Bold", 11)
quantity = str(order_data.get('cantitate', '0'))
q_text_width = canvas.stringWidth(quantity, "Helvetica-Bold", 11)
q_x_centered = vertical_x + (self.right_column_width - q_text_width) / 2
canvas.drawString(q_x_centered, row_y + self.row_height/3, quantity)
current_y = row_y
# Row 4: Customer order - CORRECTED to match HTML preview
row_y = current_y - self.row_height
canvas.setFont("Helvetica", 8)
canvas.drawString(self.content_x + mm_to_points(2), row_y + self.row_height/3, "Customer order")
canvas.setFont("Helvetica-Bold", 10)
# Match HTML: com_achiz_client + "-" + nr_linie_com_client
com_achiz_client = str(order_data.get('com_achiz_client', ''))
nr_linie = str(order_data.get('nr_linie_com_client', ''))
if com_achiz_client and nr_linie:
customer_order = f"{com_achiz_client}-{nr_linie}"[:18]
else:
customer_order = "N/A"
co_text_width = canvas.stringWidth(customer_order, "Helvetica-Bold", 10)
co_x_centered = vertical_x + (self.right_column_width - co_text_width) / 2
canvas.drawString(co_x_centered, row_y + self.row_height/3, customer_order)
current_y = row_y
# Row 5: Delivery date
row_y = current_y - self.row_height
canvas.setFont("Helvetica", 8)
canvas.drawString(self.content_x + mm_to_points(2), row_y + self.row_height/3, "Delivery date")
canvas.setFont("Helvetica-Bold", 10)
delivery_date = str(order_data.get('data_livrare', 'N/A'))
if delivery_date != 'N/A' and delivery_date:
try:
# Format date if it's a valid date
from datetime import datetime
if isinstance(delivery_date, str) and len(delivery_date) > 8:
delivery_date = delivery_date[:10] # Take first 10 chars for date
except:
pass
dd_text_width = canvas.stringWidth(delivery_date, "Helvetica-Bold", 10)
dd_x_centered = vertical_x + (self.right_column_width - dd_text_width) / 2
canvas.drawString(dd_x_centered, row_y + self.row_height/3, delivery_date)
current_y = row_y
# Row 6: Description (double height)
row_y = current_y - self.double_row_height
canvas.setFont("Helvetica", 8)
canvas.drawString(self.content_x + mm_to_points(2), row_y + self.double_row_height/2, "Description")
# Handle description text wrapping for double height area
canvas.setFont("Helvetica-Bold", 9)
description = str(order_data.get('descr_com_prod', 'N/A'))
max_chars_per_line = 18
lines = []
words = description.split()
current_line = ""
for word in words:
if len(current_line + word + " ") <= max_chars_per_line:
current_line += word + " "
else:
if current_line:
lines.append(current_line.strip())
current_line = word + " "
if current_line:
lines.append(current_line.strip())
# Draw up to 3 lines in the double height area
line_spacing = self.double_row_height / 4
start_y = row_y + 3 * line_spacing
for i, line in enumerate(lines[:3]):
line_y = start_y - (i * line_spacing)
l_text_width = canvas.stringWidth(line, "Helvetica-Bold", 9)
l_x_centered = vertical_x + (self.right_column_width - l_text_width) / 2
canvas.drawString(l_x_centered, line_y, line)
current_y = row_y
# Row 7: Size
row_y = current_y - self.row_height
canvas.setFont("Helvetica", 8)
canvas.drawString(self.content_x + mm_to_points(2), row_y + self.row_height/3, "Size")
canvas.setFont("Helvetica-Bold", 10)
size = str(order_data.get('dimensiune', 'N/A'))[:12]
s_text_width = canvas.stringWidth(size, "Helvetica-Bold", 10)
s_x_centered = vertical_x + (self.right_column_width - s_text_width) / 2
canvas.drawString(s_x_centered, row_y + self.row_height/3, size)
current_y = row_y
# Row 8: Article Code - CORRECTED to use customer_article_number like HTML preview
row_y = current_y - self.row_height
canvas.setFont("Helvetica", 8)
canvas.drawString(self.content_x + mm_to_points(2), row_y + self.row_height/3, "Article Code")
canvas.setFont("Helvetica-Bold", 10)
# Match HTML: uses customer_article_number, not cod_articol
article_code = str(order_data.get('customer_article_number', 'N/A'))[:12]
ac_text_width = canvas.stringWidth(article_code, "Helvetica-Bold", 10)
ac_x_centered = vertical_x + (self.right_column_width - ac_text_width) / 2
canvas.drawString(ac_x_centered, row_y + self.row_height/3, article_code)
current_y = row_y
# Draw horizontal line between Article Code and Prod Order
canvas.setLineWidth(1)
canvas.line(self.content_x, current_y, self.content_x + self.content_width, current_y)
# Row 9: Prod Order - CORRECTED to match HTML preview with sequential numbering
row_y = current_y - self.row_height
canvas.setFont("Helvetica", 8)
canvas.drawString(self.content_x + mm_to_points(2), row_y + self.row_height/3, "Prod Order")
canvas.setFont("Helvetica-Bold", 10)
# Match HTML: comanda_productie + "-" + sequential number (not quantity!)
comanda_productie = str(order_data.get('comanda_productie', 'N/A'))
prod_order = f"{comanda_productie}-{current_num:03d}" # Sequential number for this specific label
po_text_width = canvas.stringWidth(prod_order, "Helvetica-Bold", 10)
po_x_centered = vertical_x + (self.right_column_width - po_text_width) / 2
canvas.drawString(po_x_centered, row_y + self.row_height/3, prod_order)
# Draw all horizontal lines between rows (from row 3 onwards)
for i in range(6): # 6 lines between 7 rows (3-9)
line_y = self.content_y + self.content_height - (2 + i + 1) * self.row_height
if i == 3: # Account for double height description row
line_y -= self.row_height
canvas.line(self.content_x, line_y, self.content_x + self.content_width, line_y)
# Bottom horizontal barcode - positioned 1.5mm below the main rectangle
barcode_area_height = mm_to_points(12) # Reserve space for barcode
barcode_y = self.content_y - mm_to_points(1.5) - barcode_area_height # 1.5mm gap below rectangle
barcode_width = self.content_width # Use full content width
barcode_x = self.content_x
try:
# Create barcode for sequential number
barcode = code128.Code128(sequential_number,
barWidth=0.25*mm, # Adjust bar width for better fit
barHeight=mm_to_points(10)) # Increase height to 10mm
# Always scale to fit the full allocated width
scale_factor = barcode_width / barcode.width
canvas.saveState()
canvas.translate(barcode_x, barcode_y)
canvas.scale(scale_factor, 1)
barcode.drawOn(canvas, 0, 0)
canvas.restoreState()
# NO TEXT BELOW BARCODE - Remove all text rendering for horizontal barcode
except Exception as e:
# Fallback: Simple barcode pattern that fills the width
canvas.setStrokeColor(colors.black)
canvas.setFillColor(colors.black)
bar_width = barcode_width / 50 # 50 bars across width
for i in range(50):
if i % 3 < 2: # Create barcode-like pattern
x_pos = barcode_x + (i * bar_width)
canvas.rect(x_pos, barcode_y, bar_width * 0.8, mm_to_points(8), fill=1)
# Right side vertical barcode - positioned 3mm further right and fill frame
vertical_barcode_x = self.content_x + self.content_width + mm_to_points(4) # Moved 3mm right (1mm + 3mm = 4mm)
vertical_barcode_y = self.content_y
vertical_barcode_height = self.content_height
vertical_barcode_width = mm_to_points(12) # Increased width for better fill
try:
# Create vertical barcode code - CORRECTED to match HTML preview
# Use same format as customer order: com_achiz_client + "/" + nr_linie_com_client
com_achiz_client = str(order_data.get('com_achiz_client', ''))
nr_linie = str(order_data.get('nr_linie_com_client', ''))
if com_achiz_client and nr_linie:
vertical_code = f"{com_achiz_client}/{nr_linie}"
else:
vertical_code = "000000/00"
# Create a vertical barcode using Code128
v_barcode = code128.Code128(vertical_code,
barWidth=0.15*mm, # Thinner bars for better fit
barHeight=mm_to_points(8)) # Increased bar height
# Draw rotated barcode - fill the entire frame height
canvas.saveState()
canvas.translate(vertical_barcode_x + mm_to_points(6), vertical_barcode_y)
canvas.rotate(90)
# Always scale to fill the frame height
scale_factor = vertical_barcode_height / v_barcode.width
canvas.scale(scale_factor, 1)
v_barcode.drawOn(canvas, 0, 0)
canvas.restoreState()
# NO TEXT FOR VERTICAL BARCODE - Remove all text rendering
except Exception as e:
# Fallback: Vertical barcode pattern that fills the frame
canvas.setStrokeColor(colors.black)
canvas.setFillColor(colors.black)
bar_height = vertical_barcode_height / 60 # 60 bars across height
for i in range(60):
if i % 3 < 2: # Create barcode pattern
y_pos = vertical_barcode_y + (i * bar_height)
canvas.rect(vertical_barcode_x, y_pos, mm_to_points(8), bar_height * 0.8, fill=1)
def generate_order_labels_pdf(order_id, order_data, paper_saving_mode=True):
"""
Main function to generate PDF for an order with multiple labels
Optimized for thermal label printers (Epson TM-T20, Citizen CTS-310)
Args:
order_id: Order identifier
order_data: Order information dictionary
paper_saving_mode: If True, positions content at top to save paper
"""
try:
generator = LabelPDFGenerator(paper_saving_mode=paper_saving_mode)
# Get quantity from order data
quantity = int(order_data.get('cantitate', 1))
# Generate PDF with printer optimization
pdf_buffer = generator.generate_labels_pdf(order_data, quantity, printer_optimized=True)
return pdf_buffer
except Exception as e:
print(f"Error generating PDF labels: {e}")
raise e
def update_order_printed_status(order_id):
"""
Update the order status to printed in the database
"""
try:
from .print_module import get_db_connection
conn = get_db_connection()
cursor = conn.cursor()
# Check if printed_labels column exists
cursor.execute("SHOW COLUMNS FROM order_for_labels LIKE 'printed_labels'")
column_exists = cursor.fetchone()
if column_exists:
# Update printed status
cursor.execute("""
UPDATE order_for_labels
SET printed_labels = 1, updated_at = NOW()
WHERE id = %s
""", (order_id,))
else:
# If column doesn't exist, we could add it or use another method
print(f"Warning: printed_labels column doesn't exist for order {order_id}")
conn.commit()
conn.close()
return True
except Exception as e:
print(f"Error updating printed status for order {order_id}: {e}")
return False