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