From 2ceab8363d16787c0584bbee18f4184b78d6e173 Mon Sep 17 00:00:00 2001 From: Scheianu Ionut Date: Sat, 20 Sep 2025 17:31:20 +0300 Subject: [PATCH] uploadte to corect preview and print the labels for 1 row --- py_app/app/__pycache__/routes.cpython-312.pyc | Bin 65588 -> 71792 bytes py_app/app/pdf_generator.py | 357 ++++++++++++++++++ py_app/app/routes.py | 145 ++++++- .../static/quality_recticel_print_helper.zip | Bin 8067 -> 9269 bytes py_app/app/templates/print_module.html | 292 ++++++++++---- py_app/chrome_extension/background.js | 140 +++++-- py_app/chrome_extension/content.js | 51 ++- 7 files changed, 865 insertions(+), 120 deletions(-) create mode 100644 py_app/app/pdf_generator.py diff --git a/py_app/app/__pycache__/routes.cpython-312.pyc b/py_app/app/__pycache__/routes.cpython-312.pyc index 875aedaf4a2190fd34fe984bc0a04b4fe6c793ad..a6d5be39f56e35ff9245989aed398d204e851d07 100644 GIT binary patch delta 6300 zcmb_gYj7Lab>3aP7K;~2@BzM;q)ieMMaU8*S`wvNqDVSUEybi1QE3tw!Y(Puc#%iaN=x-)nvT0H*_uJ5siY=~=~^iiNq(r0 z^qk!V2-5OQ$I1RU``vrbJ@?*o?>*<+edV-1H?6n-&}QQ(crKiO=jfXsJ!f|@BizgF zl36-VSJnrGs!{sSUFR9ppeP~q+thg#QeXM96s4;SVWD!Amh7BM%9*Pgqe3P0V2XMi zFX}OjIxXZ=I}S_N>52E6K>opd_4M4#`S*6y^u?JQ%hwsGTm0ZH`hs-!#-Qa5 znxe>CC|;62x$#YUX(sjaA27xrV@+1tcC&%LJTrWAnXbBoHCIrWV7YEa#n+_aTlL(( zLv^WvqGFFqzMECjAK!X{xkO7-Z4W;50`($u9$N9CDS?S=ZlkCZbj-3+5}0bx5OgPK zvN5jdLs>6fX={`^&zhxu%hef!U<}ZCYl3Nn8KTTokv5xeqp7%A84^T(bJE3{=C~!_ zo}%)-UZ4c-MP0rGHgKWJqw4HJJVdonajRNQQTI_~Z<;Qef+a<-S+Z5I1x;xt#Ym?| z%cV0PSZoEef+6mdp1)n+WFKMt%wbBfrRh`jkoG=FpQN7D9i~pwZin1v5X>$;wWg60E%|(qa*KQ?TLI-Ob3XU z4&7#n~&`FG#=>uTM#~w@3yvq` zP*iqAWj31Aa|RFgvTt8xcU;=PE>!p9XQ@h^`dUsvb2}w)M8^F7h(Hb$rjJ37>13Fi{7B`h!~7{qr!-%ClHQrS9Zi35O(=# z>7n<_4={R)dcOGf&?#!oA^(-`^`rN^*8(e;gZhvmO^1wW24V**a6wauP3r(#1Z&V7 z;?jDWVyKj^aK{ZPgEaRJT(e5E&X_VvO_$5d1Y3$4q4V*Yrl9%^sWYZcDZ{aP>E(@e znUpE!Sh?(iL9iEv83%@$040>9Oo9c2ltc@qfLRTf0d{J*9B_q(D*;z&*af&+!!>|w zHCzX{zB;2CZGb|f*1!X9(r`2277gDA_M0n20e-eBMaf#d?pfsh!E z1rlM!O33T;9}OhE{$M~1$2<5i0Zm9_inV;DKM{*ZLL%{oeIeE6mO_;>iJ<5WCqhRs zTScN`*b6C8^+5*@)(VX{n@rv-s@q_)M1+~bYG8ELeTcagNAP_A;QmL|O;s#wo4!ir z(8GO$ec&v9Cx3se&~n$YACn&+j>KWF6Jde8kO4<$nShWj{ghX;mLjhbFYHs+zS_0R znA@`Ef`7>+EL)2mOe{D6x8h||i4`TdJLu|6-q*``53m} zaqOtrPM}3jRmQ`b<#2vS5+frb$!6?Y)QTk(9-C$JQK^X3;fW}|k~~i~p;2cc7_Njt zVuu*_0P?;pfCBI9nH`>eMCJw-^$)LNdzsHq_x8{R&%d#UFSDC3vu!txR7ulGe9Y4q--_go5zn&Y|WXl%u=@casOn~#7NGJWsb7B#+~DjO;qMAShiBO+VS2= zIy2FqvtfmuvQ>|_jQb{9a}F$*P`1WN-Q@0b##||u%P3p(l;m zr4wJyRbaW2vb9derZ$~BnXAIGOPxUT#AvP>%Qfl*$wYUq7RziBj`~a1Qn`HKZFVjxmklmvtV7UV-ZWYB zZd2EMQ`bV%)&*zx7`wE-XN-N9b=@ndDin2{DM%=oqb@DeRv7>-?5hNH#|9gY>t_GV6H{B_b;*o z_h9{lD(i2B6aTt%cV~Ymb&=^^53e^n%>%o1Z+7<#Y}389nFjpUHUr3H1F-+N#sl!i z-IMrd$|0Zp8i>Cm4wp@R1ifG=;?_p>6J7u|9n^*NXFEtB+t(L9u(?c14}89OOs;55i^k`zDZOCRh_M~8Ny84nB^oXTT&7_s z;BpOD09KeXvB#bHwdL#1V-Cj$7;)jXbMC0|B~cp@O4Os+4m?Be78Sp*x_pz`bMlIxB=gS3MD2j;39 zD1MIO1r+~@0+}+&A-ahI2^skXqFX3#qd@*e?jW*&Nb2@^c9RdWh@^{rgyLfqpC|~G z?;=7nMgEHhy00)N^50ng9~A$K;+H7kyO$y~h<$`Xs6(MgVE~c5Pg}S8I|VL#99e?O zg9=U}M$}h$f~|-rD1TLe2}s}i%i10I6JJ-QsZ|jy6me1 z4JiSIQ&l;>l|H9hrjC5C?Q9Ds)``)x9Wc*ADuuSk z);1RMYDv|ZBVk7m z3%vfm+uU!`y+QT#GrBi=XuxkWhJKS||M0ZQiASo8k^bmI-cSMfRq1a&l&s}g>q1e* zNQXbVueloVYk08m_@1VIs^9)2OWT6QE$iK+3#!~-nfcB~({%4N9_Uw4yo%yA6muvp zqF6$483q2+Cisg`;fu<-B{)2(W~8xC>bPdW&`QOAIP({u@W!u}(Vkm6is{NRfO72= zqyJsk(=M6w%(l+DPP-Q9JvW(0XjQ3~&M7!I%oOz2O&^>dK)s%u%obH`XAV&=OiP!4 HdFcNDwt~%Q delta 765 zcmZ9KT}V@57{}k|oSnH3cUEX_=EtTD+VP@cSdxge>mW!mvo0*0S+J#?&8%oLVi$!M zeZ0iEiW0k9pmG``Z~P$YA`oGaXrpqj%~{H9v`Q#?AC-t+4!`$*o{#_gyoZw;*E|nt z#5X3Bfw0Ag=Pr4d?~7LMw|>G&_28uE`F%BBPnpNk%RRG3IIJSX;|r6>jwEMK_c&=z zZ?*qd()UkE$90hU)dvRgEnHSdKIQ>N>BHm`7*mg~wDO?RJL$VHLCZ6Ch|;D^6eejd zlVnBl)f(F<5aP#!?RNPv+m?*IJ%O(7KJS3%H5;wQ zm#j~Ig&3t5Hg3U!n%anQ+9ixE(vi(-NT}1BS+Kmr$Ol9bvzNFXy-cU`Rgk3b^TRMl zZLvCfE?x@)J@u{R2)}!Ga+5?@AFeiXfCRKVnh*z(Yl9H@Tj${edTJ_E)o3qr-CQT} z6a`@j+IIIr7$n#DPBI9Rj-DtqD!=rwzrLM0pOgf-n3>8V!iMNaI9ZMjx`z5H!}j-~ zKfhNSQ!G;{u!xV)<16*mg5XM+opE#BZB4>lb}uj-oYz(UNEu7N5)fR<9xYTjc&4`e z70l;p=`r#`?{@GroW*V9XUYM!O>jGuya_@@8RjmhLAfDUbKvA5@=4RQXgnA@~ diff --git a/py_app/app/pdf_generator.py b/py_app/app/pdf_generator.py new file mode 100644 index 0000000..43d7e9f --- /dev/null +++ b/py_app/app/pdf_generator.py @@ -0,0 +1,357 @@ +""" +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): + # Label dimensions: 80mm x 110mm + self.label_width = mm_to_points(80) + self.label_height = mm_to_points(110) + + # 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(85) # ~321px scaled to 110mm + + # Position content in label, leaving space for barcodes + self.content_x = mm_to_points(3) # 3mm from left edge + self.content_y = mm_to_points(15) # 15mm from bottom (space for bottom barcode) + + # 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): + """ + Generate PDF with multiple labels based on quantity + Creates sequential labels: CP00000711-001 to CP00000711-XXX + """ + buffer = io.BytesIO() + + # Create canvas with label dimensions + c = canvas.Canvas(buffer, pagesize=(self.label_width, self.label_height)) + + # 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() + + # 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 _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 + 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) + customer_order = str(order_data.get('com_achiz_client', 'N/A'))[:15] + 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 + 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) + article_code = str(order_data.get('cod_articol', '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 + + # Row 9: Prod Order (but we'll show sequential number instead) + 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) + # Show original production order + prod_order = str(order_data.get('comanda_productie', 'N/A')) + 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 within label bounds + barcode_area_height = mm_to_points(12) # Reserve space for barcode + barcode_y = mm_to_points(5) # 5mm from bottom of label + 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 + vertical_code = f"{current_num:03d}-{total_qty:02d}" + + # 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): + """ + Main function to generate PDF for an order with multiple labels + """ + try: + generator = LabelPDFGenerator() + + # Get quantity from order data + quantity = int(order_data.get('cantitate', 1)) + + # Generate PDF + pdf_buffer = generator.generate_labels_pdf(order_data, quantity) + + 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 \ No newline at end of file diff --git a/py_app/app/routes.py b/py_app/app/routes.py index bb9340f..f13794a 100644 --- a/py_app/app/routes.py +++ b/py_app/app/routes.py @@ -1308,14 +1308,155 @@ def view_orders(): @bp.route('/get_unprinted_orders', methods=['GET']) def get_unprinted_orders(): """Get all rows from order_for_labels where printed != 1""" + print(f"DEBUG: get_unprinted_orders called. Session role: {session.get('role')}") + + if 'role' not in session or session['role'] not in ['superadmin', 'warehouse_manager', 'etichete']: + print(f"DEBUG: Access denied for role: {session.get('role')}") + return jsonify({'error': 'Access denied. Required roles: superadmin, warehouse_manager, etichete'}), 403 + + try: + print("DEBUG: Calling get_unprinted_orders_data()") + data = get_unprinted_orders_data() + print(f"DEBUG: Retrieved {len(data)} orders") + return jsonify(data) + + except Exception as e: + print(f"DEBUG: Error in get_unprinted_orders: {e}") + import traceback + traceback.print_exc() + return jsonify({'error': str(e)}), 500 + +@bp.route('/generate_labels_pdf/', methods=['POST']) +def generate_labels_pdf(order_id): + """Generate PDF labels for a specific order""" + print(f"DEBUG: generate_labels_pdf called for order_id: {order_id}") + + if 'role' not in session or session['role'] not in ['superadmin', 'warehouse_manager', 'etichete']: + print(f"DEBUG: Access denied for role: {session.get('role')}") + return jsonify({'error': 'Access denied. Required roles: superadmin, warehouse_manager, etichete'}), 403 + + try: + from .pdf_generator import generate_order_labels_pdf, update_order_printed_status + from .print_module import get_db_connection + from flask import make_response + + # Get order data from database + conn = get_db_connection() + cursor = conn.cursor() + + cursor.execute(""" + SELECT id, comanda_productie, cod_articol, descr_com_prod, cantitate, + data_livrare, dimensiune, com_achiz_client, nr_linie_com_client, customer_name, + customer_article_number, open_for_order, line_number, + printed_labels, created_at, updated_at + FROM order_for_labels + WHERE id = %s + """, (order_id,)) + + row = cursor.fetchone() + conn.close() + + if not row: + return jsonify({'error': 'Order not found'}), 404 + + # Create order data dictionary + order_data = { + 'id': row[0], + 'comanda_productie': row[1], + 'cod_articol': row[2], + 'descr_com_prod': row[3], + 'cantitate': row[4], + 'data_livrare': row[5], + 'dimensiune': row[6], + 'com_achiz_client': row[7], + 'nr_linie_com_client': row[8], + 'customer_name': row[9], + 'customer_article_number': row[10], + 'open_for_order': row[11], + 'line_number': row[12], + 'printed_labels': row[13] if row[13] is not None else 0, + 'created_at': row[14], + 'updated_at': row[15] + } + + print(f"DEBUG: Generating PDF for order {order_id} with quantity {order_data['cantitate']}") + + # Generate PDF + pdf_buffer = generate_order_labels_pdf(order_id, order_data) + + # Update printed status in database + update_success = update_order_printed_status(order_id) + if not update_success: + print(f"Warning: Could not update printed status for order {order_id}") + + # Create response with PDF + response = make_response(pdf_buffer.getvalue()) + response.headers['Content-Type'] = 'application/pdf' + response.headers['Content-Disposition'] = f'inline; filename="labels_order_{order_id}_{order_data["comanda_productie"]}.pdf"' + + print(f"DEBUG: PDF generated successfully for order {order_id}") + return response + + except Exception as e: + print(f"DEBUG: Error generating PDF for order {order_id}: {e}") + import traceback + traceback.print_exc() + return jsonify({'error': str(e)}), 500 + +@bp.route('/get_order_data/', methods=['GET']) +def get_order_data(order_id): + """Get specific order data for preview""" + print(f"DEBUG: get_order_data called for order_id: {order_id}") + if 'role' not in session or session['role'] not in ['superadmin', 'warehouse_manager', 'etichete']: return jsonify({'error': 'Access denied'}), 403 try: - data = get_unprinted_orders_data() - return jsonify(data) + from .print_module import get_db_connection + + conn = get_db_connection() + cursor = conn.cursor() + + cursor.execute(""" + SELECT id, comanda_productie, cod_articol, descr_com_prod, cantitate, + data_livrare, dimensiune, com_achiz_client, nr_linie_com_client, customer_name, + customer_article_number, open_for_order, line_number, + printed_labels, created_at, updated_at + FROM order_for_labels + WHERE id = %s + """, (order_id,)) + + row = cursor.fetchone() + conn.close() + + if not row: + return jsonify({'error': 'Order not found'}), 404 + + order_data = { + 'id': row[0], + 'comanda_productie': row[1], + 'cod_articol': row[2], + 'descr_com_prod': row[3], + 'cantitate': row[4], + 'data_livrare': str(row[5]) if row[5] else 'N/A', + 'dimensiune': row[6], + 'com_achiz_client': row[7], + 'nr_linie_com_client': row[8], + 'customer_name': row[9], + 'customer_article_number': row[10], + 'open_for_order': row[11], + 'line_number': row[12], + 'printed_labels': row[13] if row[13] is not None else 0, + 'created_at': str(row[14]) if row[14] else 'N/A', + 'updated_at': str(row[15]) if row[15] else 'N/A' + } + + return jsonify(order_data) except Exception as e: + print(f"DEBUG: Error getting order data for {order_id}: {e}") + import traceback + traceback.print_exc() return jsonify({'error': str(e)}), 500 @warehouse_bp.route('/create_locations', methods=['GET', 'POST']) diff --git a/py_app/app/static/quality_recticel_print_helper.zip b/py_app/app/static/quality_recticel_print_helper.zip index 920f87b8f2f221aa28bcd69a5f9ced99edf06523..d98901e42e41ecd86f9551c391f337334f0a6421 100644 GIT binary patch delta 5827 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 PhXP -
- - + + +
+
+ 📄 PDF Label Generation
+ Labels will be generated as PDF files for universal printing compatibility.
+ Works with any browser and printer - no extensions needed! +
+
+ + +
+ + +
+
+ Creates sequential labels based on quantity (e.g., CP00000711-001 to CP00000711-063)
- - 🔗 Install Direct Print Extension - + + � PDF labels can be printed directly from your browser or saved for later use +
@@ -281,9 +295,24 @@ table tbody tr.selected td { {% endblock %} \ No newline at end of file diff --git a/py_app/chrome_extension/background.js b/py_app/chrome_extension/background.js index a8f7f23..7b124e9 100644 --- a/py_app/chrome_extension/background.js +++ b/py_app/chrome_extension/background.js @@ -33,10 +33,10 @@ async function handlePrintLabel(printData, sendResponse) { // Check if printing API is available if (!chrome.printing || !chrome.printing.getPrinters) { - console.error('Chrome printing API not available'); + console.error('Chrome printing API not available - browser may be Brave, Edge, or older Chrome'); sendResponse({ success: false, - error: 'Chrome printing API not available. Please ensure you are using Chrome 85+ and the extension has proper permissions.' + error: 'Direct printing API not available in this browser. The extension works best in Google Chrome 85+. Using fallback method instead.' }); return; } @@ -50,13 +50,21 @@ async function handlePrintLabel(printData, sendResponse) { return; } - // Find default printer or use first available - const defaultPrinter = printers.find(p => p.isDefault) || printers[0]; - console.log('Using printer:', defaultPrinter); + // Find selected printer or use default/first available + let selectedPrinter; + if (printData.printerId && printData.printerId !== 'default') { + selectedPrinter = printers.find(p => p.id === printData.printerId); + } + + if (!selectedPrinter) { + selectedPrinter = printers.find(p => p.isDefault) || printers[0]; + } + + console.log('Using printer:', selectedPrinter); // Create print job const printJob = { - printerId: defaultPrinter.id, + printerId: selectedPrinter.id, ticket: { version: '1.0', print: { @@ -100,7 +108,7 @@ async function handlePrintLabel(printData, sendResponse) { lastPrint: { timestamp: Date.now(), jobId: result.jobId, - printer: defaultPrinter.displayName + printer: selectedPrinter.displayName } }); } else { @@ -120,7 +128,7 @@ async function getPrinters(sendResponse) { if (!chrome.printing || !chrome.printing.getPrinters) { sendResponse({ success: false, - error: 'Chrome printing API not available' + error: 'Direct printing API not available in this browser (works in Chrome only)' }); return; } @@ -144,38 +152,108 @@ chrome.runtime.onInstalled.addListener((details) => { }); }); -// Fallback print method using tabs API +// Fallback print method using tabs API (works in Brave, Edge, Chrome) async function handleFallbackPrint(printData, sendResponse) { try { - console.log('Using fallback print method'); + console.log('Using fallback print method for Brave/Edge/Chrome'); - // Create a new tab with the print content + // Create enhanced HTML with better print styles + const enhancedHTML = ` + + + + Quality Recticel Label + + + + + + + + + `; + + // Create a new tab with the enhanced print content const tab = await chrome.tabs.create({ - url: 'data:text/html,' + encodeURIComponent(printData.html), - active: false + url: 'data:text/html;charset=utf-8,' + encodeURIComponent(enhancedHTML), + active: true // Make active so user can see the print dialog }); - // Wait for tab to load, then print - setTimeout(async () => { - try { - await chrome.tabs.executeScript(tab.id, { - code: 'window.print();' - }); - - // Close the tab after printing - setTimeout(() => { - chrome.tabs.remove(tab.id); - }, 1000); - - sendResponse({success: true, method: 'fallback'}); - } catch (error) { - sendResponse({success: false, error: 'Fallback print failed: ' + error.message}); - } - }, 1000); + console.log('Created print tab:', tab.id); + sendResponse({success: true, method: 'fallback_tab', tabId: tab.id}); } catch (error) { console.error('Fallback print error:', error); - sendResponse({success: false, error: error.message}); + sendResponse({success: false, error: 'Browser print fallback failed: ' + error.message}); } } diff --git a/py_app/chrome_extension/content.js b/py_app/chrome_extension/content.js index 71ff214..e106012 100644 --- a/py_app/chrome_extension/content.js +++ b/py_app/chrome_extension/content.js @@ -30,6 +30,9 @@ function initializePrintExtension() { } function addExtensionStatusIndicator() { + // Detect browser type + const browserType = getBrowserType(); + // Create status indicator element const statusIndicator = document.createElement('div'); statusIndicator.id = 'extension-status'; @@ -37,24 +40,39 @@ function addExtensionStatusIndicator() { position: fixed; top: 10px; right: 10px; - background: #28a745; - color: white; - padding: 5px 10px; + background: ${browserType === 'chrome' ? '#28a745' : '#ffc107'}; + color: ${browserType === 'chrome' ? 'white' : 'black'}; + padding: 8px 12px; border-radius: 4px; font-size: 12px; z-index: 9999; box-shadow: 0 2px 4px rgba(0,0,0,0.2); + max-width: 250px; `; - statusIndicator.textContent = '🖨️ Print Extension Active'; + + if (browserType === 'chrome') { + statusIndicator.textContent = '🖨️ Print Extension Active (Direct Print Available)'; + } else { + statusIndicator.innerHTML = `🖨️ Print Extension Active
${browserType.toUpperCase()} - Browser Print Mode`; + } document.body.appendChild(statusIndicator); - // Hide after 3 seconds + // Hide after 4 seconds (longer for non-Chrome browsers) setTimeout(() => { statusIndicator.style.opacity = '0'; statusIndicator.style.transition = 'opacity 0.5s'; setTimeout(() => statusIndicator.remove(), 500); - }, 3000); + }, browserType === 'chrome' ? 3000 : 5000); +} + +// Detect browser type +function getBrowserType() { + const userAgent = navigator.userAgent.toLowerCase(); + if (userAgent.includes('edg/')) return 'edge'; + if (userAgent.includes('brave')) return 'brave'; + if (userAgent.includes('chrome') && !userAgent.includes('edg/')) return 'chrome'; + return 'chromium'; } function overridePrintButton() { @@ -71,9 +89,15 @@ function overridePrintButton() { // Add new event listener for extension printing newPrintButton.addEventListener('click', handleExtensionPrint); - // Update button text to indicate direct printing - newPrintButton.innerHTML = '🖨️ Print Direct'; - newPrintButton.title = 'Print directly to default printer (Chrome Extension)'; + // Update button text based on browser capabilities + const browserType = getBrowserType(); + if (browserType === 'chrome') { + newPrintButton.innerHTML = '🖨️ Print Direct'; + newPrintButton.title = 'Print directly to default printer (Chrome Extension)'; + } else { + newPrintButton.innerHTML = '🖨️ Print Enhanced'; + newPrintButton.title = `Enhanced printing for ${browserType.toUpperCase()} browser - Opens optimized print dialog`; + } console.log('Print button override complete'); } else { @@ -103,12 +127,16 @@ function handleExtensionPrint(event) { // Create complete HTML for printing const printHTML = createPrintableHTML(labelPreview); + // Get selected printer from dropdown + const selectedPrinterId = window.getSelectedPrinter ? window.getSelectedPrinter() : 'default'; + // Try direct printing first, then fallback chrome.runtime.sendMessage({ action: 'print_label', data: { html: printHTML, - timestamp: Date.now() + timestamp: Date.now(), + printerId: selectedPrinterId } }, (response) => { if (response && response.success) { @@ -122,7 +150,8 @@ function handleExtensionPrint(event) { action: 'fallback_print', data: { html: printHTML, - timestamp: Date.now() + timestamp: Date.now(), + printerId: selectedPrinterId } }, handlePrintResponse); }