Implement print labels module with PDF generation, QZ Tray integration, and theme support
- Migrate print_labels.html and print_lost_labels.html to standalone pages with header and theme toggle - Implement dark/light theme support using data-theme attribute and CSS variables - Add PDF generation endpoints for single and batch label printing - Copy pdf_generator.py from original app with full label formatting (80mm x 105mm) - Fix data response handling to correctly access data.orders from API endpoints - Synchronize table text sizes across both print pages - Remove help buttons from print pages - Database column rename: data_livrara → data_livrare for consistency - Update routes to use correct database column names - Add 7 test orders to database (4 unprinted, 3 printed) - Implement QZ Tray integration with PDF fallback for label printing - All CSS uses theme variables for dark/light mode synchronization
This commit is contained in:
452
app/modules/labels/pdf_generator.py
Normal file
452
app/modules/labels/pdf_generator.py
Normal file
@@ -0,0 +1,452 @@
|
||||
"""
|
||||
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 - (removed)
|
||||
row_y = current_y - self.row_height
|
||||
canvas.setFont("Helvetica-Bold", 10)
|
||||
text = ""
|
||||
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
|
||||
@@ -5,6 +5,7 @@ Handles label printing pages and API endpoints
|
||||
from flask import Blueprint, render_template, session, redirect, url_for, jsonify, request
|
||||
import logging
|
||||
|
||||
from app.database import get_db
|
||||
from .print_module import (
|
||||
get_unprinted_orders_data,
|
||||
get_printed_orders_data,
|
||||
@@ -35,6 +36,15 @@ def print_module():
|
||||
return render_template('modules/labels/print_module.html')
|
||||
|
||||
|
||||
@labels_bp.route('/print-labels', methods=['GET'])
|
||||
def print_labels():
|
||||
"""Original print labels interface - complete copy from quality app"""
|
||||
if 'user_id' not in session:
|
||||
return redirect(url_for('main.login'))
|
||||
|
||||
return render_template('modules/labels/print_labels.html')
|
||||
|
||||
|
||||
@labels_bp.route('/print-lost-labels', methods=['GET'])
|
||||
def print_lost_labels():
|
||||
"""Print lost/missing labels interface"""
|
||||
@@ -74,6 +84,28 @@ def help(page='index'):
|
||||
</ol>
|
||||
'''
|
||||
},
|
||||
'print_labels': {
|
||||
'title': 'Print Labels Help',
|
||||
'content': '''
|
||||
<h3>Print Labels - Thermal Printer Guide</h3>
|
||||
<p>This module helps you print labels directly to thermal printers.</p>
|
||||
<h4>Features:</h4>
|
||||
<ul>
|
||||
<li>Live label preview in thermal format</li>
|
||||
<li>Real-time printer selection</li>
|
||||
<li>Barcode generation</li>
|
||||
<li>PDF export fallback</li>
|
||||
<li>Batch printing support</li>
|
||||
</ul>
|
||||
<h4>How to use:</h4>
|
||||
<ol>
|
||||
<li>Select orders from the list</li>
|
||||
<li>Preview labels in the preview pane</li>
|
||||
<li>Select your printer</li>
|
||||
<li>Click "Print Labels" to send to printer</li>
|
||||
</ol>
|
||||
'''
|
||||
},
|
||||
'print_lost_labels': {
|
||||
'title': 'Print Lost Labels Help',
|
||||
'content': '''
|
||||
@@ -198,3 +230,110 @@ def api_update_printed_status(order_id):
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating order status: {e}")
|
||||
return jsonify({'success': False, 'error': str(e)}), 500
|
||||
|
||||
|
||||
@labels_bp.route('/api/generate-pdf', methods=['POST'], endpoint='api_generate_pdf')
|
||||
def api_generate_pdf():
|
||||
"""Generate single label PDF for thermal printing via QZ Tray"""
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'error': 'Unauthorized'}), 401
|
||||
|
||||
try:
|
||||
from .pdf_generator import LabelPDFGenerator
|
||||
|
||||
# Get order data from request
|
||||
order_data = request.get_json()
|
||||
|
||||
if not order_data:
|
||||
return jsonify({'error': 'No order data provided'}), 400
|
||||
|
||||
# Extract piece number and total pieces for sequential numbering
|
||||
piece_number = order_data.get('piece_number', 1)
|
||||
total_pieces = order_data.get('total_pieces', 1)
|
||||
|
||||
logger.info(f"Generating single label PDF for piece {piece_number} of {total_pieces}")
|
||||
|
||||
# Initialize PDF generator in thermal printer optimized mode
|
||||
pdf_generator = LabelPDFGenerator(paper_saving_mode=True)
|
||||
|
||||
# Generate single label PDF with specific piece number for sequential CP numbering
|
||||
pdf_buffer = pdf_generator.generate_single_label_pdf(order_data, piece_number, total_pieces, printer_optimized=True)
|
||||
|
||||
# Create response with PDF data
|
||||
from flask import make_response
|
||||
response = make_response(pdf_buffer.getvalue())
|
||||
response.headers['Content-Type'] = 'application/pdf'
|
||||
response.headers['Content-Disposition'] = f'inline; filename="label_{piece_number:03d}.pdf"'
|
||||
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating PDF: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@labels_bp.route('/api/generate-pdf/<int:order_id>/true', methods=['POST'], endpoint='api_generate_batch_pdf')
|
||||
def api_generate_batch_pdf(order_id):
|
||||
"""Generate all label PDFs for an order and mark as printed"""
|
||||
if 'user_id' not in session:
|
||||
return jsonify({'error': 'Unauthorized'}), 401
|
||||
|
||||
try:
|
||||
from .pdf_generator import LabelPDFGenerator
|
||||
|
||||
# Get order data from database
|
||||
conn = get_db()
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT id, comanda_productie, cod_articol, descr_com_prod, cantitate,
|
||||
com_achiz_client, nr_linie_com_client, customer_name,
|
||||
customer_article_number, open_for_order, line_number,
|
||||
created_at, updated_at, printed_labels, data_livrare, dimensiune
|
||||
FROM order_for_labels
|
||||
WHERE id = %s
|
||||
""", (order_id,))
|
||||
|
||||
row = cursor.fetchone()
|
||||
|
||||
if not row:
|
||||
cursor.close()
|
||||
return jsonify({'error': 'Order not found'}), 404
|
||||
|
||||
# Create order data dictionary
|
||||
columns = [col[0] for col in cursor.description]
|
||||
order_data = {columns[i]: row[i] for i in range(len(columns))}
|
||||
|
||||
# Ensure date fields are strings
|
||||
if order_data.get('data_livrare'):
|
||||
order_data['data_livrare'] = str(order_data['data_livrare'])
|
||||
|
||||
cursor.close()
|
||||
|
||||
logger.info(f"Generating batch PDF for order {order_id} with {order_data.get('cantitate', 0)} labels")
|
||||
|
||||
# Initialize PDF generator
|
||||
pdf_generator = LabelPDFGenerator(paper_saving_mode=True)
|
||||
|
||||
# Get quantity from order data
|
||||
quantity = int(order_data.get('cantitate', 1))
|
||||
|
||||
# Generate PDF with all labels
|
||||
pdf_buffer = pdf_generator.generate_labels_pdf(order_data, quantity, printer_optimized=True)
|
||||
|
||||
# Mark order as printed
|
||||
success = update_order_printed_status(order_id, True)
|
||||
if not success:
|
||||
logger.warning(f"Failed to mark order {order_id} as printed, but PDF was generated")
|
||||
|
||||
# Create response with PDF data
|
||||
from flask import make_response
|
||||
response = make_response(pdf_buffer.getvalue())
|
||||
response.headers['Content-Type'] = 'application/pdf'
|
||||
response.headers['Content-Disposition'] = f'attachment; filename="labels_{order_data.get("comanda_productie", "unknown")}.pdf"'
|
||||
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating batch PDF: {e}")
|
||||
return jsonify({'error': str(e)}), 500
|
||||
Reference in New Issue
Block a user