From 1f9060255044332fc7a539c245668086d3740701 Mon Sep 17 00:00:00 2001 From: Scheianu Ionut Date: Fri, 26 Sep 2025 22:17:37 +0300 Subject: [PATCH] saved --- py_app/app/__pycache__/routes.cpython-312.pyc | Bin 95061 -> 96453 bytes py_app/app/routes.py | 27 ++ py_app/app/templates/print_module.html | 365 +++++++++++++----- recticel_print_service/INSTALLATION_GUIDE.md | 146 +++++++ .../RecticelPrintService.sln | 24 ++ .../RecticelPrintService/App.config | 6 + .../RecticelPrintService/LabelPrinter.cs | 288 ++++++++++++++ .../RecticelPrintService/PrintData.cs | 49 +++ .../RecticelPrintService/Program.cs | 81 ++++ .../Properties/AssemblyInfo.cs | 19 + .../RecticelPrintService.csproj | 62 +++ .../RecticelPrintService/packages.config | 4 + recticel_print_service/install.bat | 50 +++ .../recticel-print-protocol.reg | 14 + 14 files changed, 1040 insertions(+), 95 deletions(-) create mode 100644 recticel_print_service/INSTALLATION_GUIDE.md create mode 100644 recticel_print_service/RecticelPrintService.sln create mode 100644 recticel_print_service/RecticelPrintService/App.config create mode 100644 recticel_print_service/RecticelPrintService/LabelPrinter.cs create mode 100644 recticel_print_service/RecticelPrintService/PrintData.cs create mode 100644 recticel_print_service/RecticelPrintService/Program.cs create mode 100644 recticel_print_service/RecticelPrintService/Properties/AssemblyInfo.cs create mode 100644 recticel_print_service/RecticelPrintService/RecticelPrintService.csproj create mode 100644 recticel_print_service/RecticelPrintService/packages.config create mode 100644 recticel_print_service/install.bat create mode 100644 recticel_print_service/recticel-print-protocol.reg diff --git a/py_app/app/__pycache__/routes.cpython-312.pyc b/py_app/app/__pycache__/routes.cpython-312.pyc index 9964761072496d0cc2e77ba8efedf7b8405fcde5..a6e3107d653fab3d35a2b9ac394172fe6a962fcb 100644 GIT binary patch delta 3427 zcma)`%JGk+B$8g8OJ!)?sS$I9gW6{jm>NZ@nfsbWU97fZ3dghMA{_1=PelP@JHVa zzdi5XbI(2Jp7YLojNMaRc&G~hI4n%dv8(IO)lOs6-f)8=M|UE_qJ`~}Lz@lp(qXMp z+5&MZw-DC>(sga6G_H+L=@;UJ_zuN;hNB8x&2fU~Rqkj|_a)HcysI504*%V(%|bXhPMcI9w_n8~zB&B=Y_ND3|mnufiJdUrxFKDy8~U ziy8NvS_-w^i>LmihPB?e&qt_|H?Va9jZ5rtv|DW=DMQ1n5M78W>HNr^#BQ`5hWFtJ zTtaM!8marzD>^HRvPXT=<4Y@Hmv`A8>vXVQ+B^1k=tefVj?_z8SM#9VYrE=a3# zwM{gmx4CB0<~?(LEJ`h)x+wkZetk*@ik%4AuQ!+!&(;Xng@C~bvg z#tbI%24cbCXU|v)dyp}>6Lkj}0ufGEtIOKf?6EqU9j&fTvInQ+2!~L53Gp(bAK^i4 zM+_i#AYNezL|g65lFi19_1c?>*edj}1D*2AX<(B1$wl8uf^gc~1ZrvIfdTe;KYMT@ zYR>CuY&SzG`5}t)5Os*jTr<(bOS5l9dq4j6cK{8)-w9g)*80W)JPFWFyNytpHeH&+ z9sRCO=2o9boXkD)BYN8i@z6yd8KGA#S6nykHbIfD3bza9gT8Nq%t1W;fL7ebDyI$O z3xwLNPM2UM!9-MGgmNO}9N@t}l`A<-R#cJGgy{^ZNUz&2Ol6DZDJ__%pckm2b`zV; zx|tkeH^!%m=*MXMN#@89-7yRHrv5((qB%)WpnDb92cIdrGYJ~tgfEZ;f;Q!+II)(2 zSCSg`6VS5wn#FA_P-igN+i5EgC5hjkj$Ddzd7i@X2tCU~>~r#BH4&X|n``=dpE8sBP_x)-G)F|O|`e-G*1rO+J`Cx`|`ffhF8F87d;_GWcyleZjoHZv8VbX^*2InY(B33-nMKujZQQ|;|z?)8Oc^j z3x5(v-Axb+<-RwY;00xTTDIFEw7SG*2eH~+qR@;z)9s|!Tfn4ab;~K)-xt3D`dtf@ z(5hOnr2udWZU6?jqs%N`I}`&7V#YQQyaX+sT?EE7ouD7!-rLrNk1pkhkBmU~Jy zLRL*z36WNqQV^or6}4On=h9DEOyx-W4y6mEZ{JNEbYULlOHc{X|GTVBQO^l6JCr+> zwUfVFVJo*ySx6Rf+ zX;h$t4Ujl@okzWv2qH0iW}16iNw+ANTb)zRlWcb2o<(&9HI=I>EwrZrvLKoEHbC;A z{ATf_PuedbgvDpIPw#N9*%P~pWuDkuZSsc#+fKw4m3(OM6^>aT9SHeLrVxXX%NI+K z4!Hnf4I(FEyvq`t^Ck+lIUR+ErIV z%IR;b!7>=3%7(z8%gm;SNQc6 zU*FNi{F76#y>4p4{EDvf^e$-2PDi?4|zrWupOyjTY-21kw=$_kU6UWW&CX6!=9=Qxh%>-+I!qY4~n2& z_@saGnqYOv&B%qj%I(dKQVH6c{ECv256996B1Eby*=UT$r)Y;;3UxKARMESgFk4m2 zUA;TyXKUBm+K766%6eTb&%i}rp27!V90>q(wQ2+n{ delta 2536 zcma);du)@}8OHP2QDc4w8yq>D7V1&onv@WqXPp*)UgC} zjKhvXEHZZ4U0I31oIrlJ%@}pGO&fqanYzzea~ST_lh5zVbgn=~hUiz0#7d)l_(e3N zJ|3Q)rLAT6TH~*;oIs=T+aph**{C}@ik4LVs|ldhcw%Ik;u|AB#HQ4@Bd0aArQUvX zMkekt&YvpAM&p}Pzna%eA31cLOWeceZo+FEdixP)52dwo!nos|HFz}D_s&))HXA3; z9mv|M1~-dshJW-<=&8e_QJ~A1IaY)oqjIbfTa5m(s^T5W$o+$)y)rtGoL1T4kBeRO z=_l?rj*aEdO}gp>fnd11G^UIGV5eT%6%6SsOGQWOpJV4{YMrzU81H|veL)vxH(|xK zPpNcvK#%I-fFAA)>M;>C)_=Ku&`K)Bd~V3BiG9sPFR{^bP1Qp5(VrIUNyLMpbY^EH zI#4a*Gzk+CB1A-qJ%k`)Rv#TCsQNKqagmbl0@29s8*6OTUs|L+K--@hkxQZ3WV1Bj&TY=?r%_>yReoOVGvpOT8L|-^2PSawGd}tM( z#s{)wHS%#mE?`B!qN9xY3$&Of_y~*7h;D)}o!cfj-Li<; zR7dILI!>-LmB|%l(we0LsZtgXX8bLC*8UBTZTKX+=)>%ygXWpLP!3!*ziz-q+dQt- z{rB@P{JZ@{t;+JI>;u`Q8rP1&qoW?MP~T zo!oPz*NfSnbWSwT(#WpTzJ?mxibUO-~PpIDXbhTmq{!l^} z?{VCEG;WABFFoBVgD)5mYv}HpW6NimtlWuq&AP*R>JHE1_0{BaJK;jJd3+~o?Phit zerF%N*2eKF|x#ZFSUExpPE-PO=H> zYk#u5^8I$?%fIx(QT4wHx~Gc', methods=['POST']) +def update_printed_status(order_id): + """Update printed status for direct printing (without PDF generation)""" + print(f"DEBUG: update_printed_status 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 update_order_printed_status + + # Update printed status in database + update_success = update_order_printed_status(order_id) + + if update_success: + print(f"DEBUG: Successfully updated printed status for order {order_id}") + return jsonify({'success': True, 'message': f'Order {order_id} marked as printed'}) + else: + print(f"WARNING: Could not update printed status for order {order_id}") + return jsonify({'error': 'Could not update printed status'}), 500 + + except Exception as e: + print(f"DEBUG: Error in update_printed_status: {e}") + return jsonify({'error': 'Internal server error'}), 500 + @bp.route('/get_order_data/', methods=['GET']) def get_order_data(order_id): """Get specific order data for preview""" diff --git a/py_app/app/templates/print_module.html b/py_app/app/templates/print_module.html index 03e092f..afc0054 100644 --- a/py_app/app/templates/print_module.html +++ b/py_app/app/templates/print_module.html @@ -40,8 +40,10 @@ {% block content %}
-
-
Label View
+
+
Label View
+ +
@@ -150,12 +152,60 @@
-
- -
- - + +
+ +
+ + +
+ + +
+ +
+ + +
+
+ + +
+ + + Choose your thermal label printer +
+ + +
+ +
+ + +
+
Creates sequential labels based on quantity
+ (e.g., CP00000711-001 to CP00000711-063) +
+
+

Data Preview (Unprinted Orders)

@@ -352,9 +402,13 @@ function addPDFGenerationHandler() { const printButton = document.getElementById('print-label-btn'); if (printButton) { - // Update button text and appearance for PDF generation - printButton.innerHTML = 'šŸ“„ Generate PDF Labels'; - printButton.title = 'Generate PDF with multiple labels based on quantity'; + // Update button text and appearance based on print method + updatePrintButtonText(); + + // Add event listeners for radio buttons + document.querySelectorAll('input[name="printMethod"]').forEach(radio => { + radio.addEventListener('change', updatePrintButtonText); + }); printButton.addEventListener('click', function(e) { e.preventDefault(); @@ -366,97 +420,218 @@ function addPDFGenerationHandler() { return; } - const orderId = selectedRow.dataset.orderId; - const quantityCell = selectedRow.querySelector('td:nth-child(5)'); // Cantitate column - const quantity = quantityCell ? parseInt(quantityCell.textContent) : 1; - const prodOrderCell = selectedRow.querySelector('td:nth-child(2)'); // Comanda Productie column - const prodOrder = prodOrderCell ? prodOrderCell.textContent.trim() : 'N/A'; + // Get print method + const printMethod = document.querySelector('input[name="printMethod"]:checked').value; - if (!orderId) { - alert('Could not determine order ID. Please refresh and try again.'); - return; + if (printMethod === 'direct') { + handleDirectPrint(selectedRow); + } else { + handlePDFGeneration(selectedRow); } - - // Show loading state - const originalText = this.textContent; - this.textContent = 'Generating PDF...'; - this.disabled = true; - - console.log(`Generating PDF for order ${orderId} with ${quantity} labels`); - - // Always use paper-saving mode (optimized for thermal label printers) - const paperSavingMode = 'true'; - - console.log(`Using paper-saving mode for optimal label printing`); - - // Generate PDF with paper-saving mode enabled - fetch(`/generate_labels_pdf/${orderId}/${paperSavingMode}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - } - }) - .then(response => { - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - return response.blob(); - }) - .then(blob => { - // Create blob URL for PDF - const url = window.URL.createObjectURL(blob); - - // Create download link for PDF - const a = document.createElement('a'); - a.href = url; - a.download = `labels_${prodOrder}_${quantity}pcs.pdf`; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - - // Also open PDF in new tab for printing - const printWindow = window.open(url, '_blank'); - if (printWindow) { - printWindow.focus(); - - // Wait for PDF to load, then show print dialog and cleanup - setTimeout(() => { - printWindow.print(); - - // Clean up blob URL after print dialog is shown - setTimeout(() => { - window.URL.revokeObjectURL(url); - }, 2000); - }, 1500); - } else { - // If popup was blocked, clean up immediately - setTimeout(() => { - window.URL.revokeObjectURL(url); - }, 1000); - } - - // Show success message - alert(`āœ… PDF generated successfully!\nšŸ“Š Order: ${prodOrder}\nšŸ“¦ Labels: ${quantity} pieces\n\nThe PDF has been downloaded and opened for printing.\n\nšŸ“‹ The order has been marked as printed and removed from the unprinted orders list.`); - - // Refresh the orders table to reflect printed status - // This will automatically hide the printed order from the unprinted orders list - setTimeout(() => { - refreshUnprintedOrdersTable(); - }, 1000); - }) - .catch(error => { - console.error('Error generating PDF:', error); - alert('āŒ Failed to generate PDF labels. Error: ' + error.message); - }) - .finally(() => { - // Reset button state - this.textContent = originalText; - this.disabled = false; - }); }); } } +function updatePrintButtonText() { + const printButton = document.getElementById('print-label-btn'); + const printMethod = document.querySelector('input[name="printMethod"]:checked').value; + const printerSelection = document.getElementById('printerSelection'); + + if (printMethod === 'direct') { + printButton.innerHTML = 'šŸ–Øļø Print Directly'; + printButton.title = 'Print directly to thermal label printer'; + printerSelection.style.display = 'block'; + } else { + printButton.innerHTML = 'šŸ“„ Generate PDF'; + printButton.title = 'Generate PDF with multiple labels based on quantity'; + printerSelection.style.display = 'none'; + } +} + +function handleDirectPrint(selectedRow) { + const orderId = selectedRow.dataset.orderId; + const quantityCell = selectedRow.querySelector('td:nth-child(5)'); + const quantity = quantityCell ? parseInt(quantityCell.textContent) : 1; + const prodOrderCell = selectedRow.querySelector('td:nth-child(2)'); + const prodOrder = prodOrderCell ? prodOrderCell.textContent.trim() : 'N/A'; + + // Get selected printer + const printerSelect = document.getElementById('printerSelect'); + let printerName = printerSelect.value; + + if (printerName === 'custom') { + printerName = prompt('Enter your printer name:'); + if (!printerName) return; + } else if (printerName === 'default') { + printerName = 'default'; + } + + console.log(`Direct printing to: ${printerName}`); + + // Extract order data from selected row + const cells = selectedRow.querySelectorAll('td'); + const orderData = { + order_id: parseInt(orderId), + comanda_productie: cells[1].textContent.trim(), + cod_articol: cells[2].textContent.trim(), + descr_com_prod: cells[3].textContent.trim(), + cantitate: parseInt(cells[4].textContent.trim()), + data_livrare: cells[5].textContent.trim(), + dimensiune: cells[6].textContent.trim(), + com_achiz_client: cells[7].textContent.trim(), + nr_linie_com_client: cells[8].textContent.trim(), + customer_name: cells[9].textContent.trim(), + customer_article_number: cells[10].textContent.trim() + }; + + // Print each label individually + for (let i = 1; i <= quantity; i++) { + const labelData = { + ...orderData, + sequential_number: `${orderData.comanda_productie}-${i.toString().padStart(3, '0')}`, + current_label: i, + total_labels: quantity + }; + + // Encode data as base64 JSON + const jsonString = JSON.stringify(labelData); + const base64Data = btoa(unescape(encodeURIComponent(jsonString))); + + // Create custom protocol URL + const printUrl = `recticel-print://${encodeURIComponent(printerName)}/${base64Data}`; + + console.log(`Printing label ${i}/${quantity}: ${printUrl.substring(0, 100)}...`); + + // Trigger direct print via custom protocol + try { + window.location.href = printUrl; + } catch (error) { + console.error(`Failed to print label ${i}:`, error); + alert(`āŒ Failed to print label ${i}/${quantity}. Please check if the print service is installed.`); + return; + } + + // Small delay between labels to prevent overwhelming the printer + if (i < quantity) { + setTimeout(() => {}, 100); + } + } + + // Show success message + setTimeout(() => { + alert(`āœ… Sent ${quantity} labels to printer: ${printerName}\nšŸ“Š Order: ${prodOrder}`); + + // Update database status and refresh table + updatePrintedStatus(orderId); + }, 500); +} + +function handlePDFGeneration(selectedRow) { + const orderId = selectedRow.dataset.orderId; + const quantityCell = selectedRow.querySelector('td:nth-child(5)'); + const quantity = quantityCell ? parseInt(quantityCell.textContent) : 1; + const prodOrderCell = selectedRow.querySelector('td:nth-child(2)'); + const prodOrder = prodOrderCell ? prodOrderCell.textContent.trim() : 'N/A'; + + const button = document.getElementById('print-label-btn'); + const originalText = button.textContent; + button.textContent = 'Generating PDF...'; + button.disabled = true; + + console.log(`Generating PDF for order ${orderId} with ${quantity} labels`); + + // Always use paper-saving mode (optimized for thermal label printers) + const paperSavingMode = 'true'; + + console.log(`Using paper-saving mode for optimal label printing`); + + // Generate PDF with paper-saving mode enabled + fetch(`/generate_labels_pdf/${orderId}/${paperSavingMode}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.blob(); + }) + .then(blob => { + // Create blob URL for PDF + const url = window.URL.createObjectURL(blob); + + // Create download link for PDF + const a = document.createElement('a'); + a.href = url; + a.download = `labels_${prodOrder}_${quantity}pcs.pdf`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + + // Also open PDF in new tab for printing + const printWindow = window.open(url, '_blank'); + if (printWindow) { + printWindow.focus(); + + // Wait for PDF to load, then show print dialog and cleanup + setTimeout(() => { + printWindow.print(); + + // Clean up blob URL after print dialog is shown + setTimeout(() => { + window.URL.revokeObjectURL(url); + }, 2000); + }, 1500); + } else { + // If popup was blocked, clean up immediately + setTimeout(() => { + window.URL.revokeObjectURL(url); + }, 1000); + } + + // Show success message + alert(`āœ… PDF generated successfully!\nšŸ“Š Order: ${prodOrder}\nšŸ“¦ Labels: ${quantity} pieces\n\nThe PDF has been downloaded and opened for printing.\n\nšŸ“‹ The order has been marked as printed and removed from the unprinted orders list.`); + + // Refresh the orders table to reflect printed status + // This will automatically hide the printed order from the unprinted orders list + setTimeout(() => { + refreshUnprintedOrdersTable(); + }, 1000); + }) + .catch(error => { + console.error('Error generating PDF:', error); + alert('āŒ Failed to generate PDF labels. Error: ' + error.message); + }) + .finally(() => { + // Reset button state + button.textContent = originalText; + button.disabled = false; + }); +} + +function updatePrintedStatus(orderId) { + // Call backend to update printed status for direct printing + fetch(`/update_printed_status/${orderId}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + } + }) + .then(response => response.json()) + .then(data => { + console.log('Updated printed status:', data); + // Refresh table + setTimeout(() => { + refreshUnprintedOrdersTable(); + }, 1000); + }) + .catch(error => { + console.error('Error updating printed status:', error); + }); +} + // Function to refresh the unprinted orders table function refreshUnprintedOrdersTable() { console.log('Refreshing unprinted orders table...'); diff --git a/recticel_print_service/INSTALLATION_GUIDE.md b/recticel_print_service/INSTALLATION_GUIDE.md new file mode 100644 index 0000000..1eaa4a2 --- /dev/null +++ b/recticel_print_service/INSTALLATION_GUIDE.md @@ -0,0 +1,146 @@ +# Recticel Direct Print Service - Installation Guide + +## šŸŽÆ Overview + +This service enables direct printing from web browsers to thermal label printers (Epson TM-T20, Citizen CTS-310, etc.) without requiring PDF generation or manual printer dialogs. + +## šŸ”§ Installation Steps + +### 1. **Build the C# Application** + +**Requirements:** +- Visual Studio 2019 or later +- .NET Framework 4.7.2 or later +- Windows 10/11 + +**Build Steps:** +1. Open `RecticelPrintService.sln` in Visual Studio +2. Restore NuGet packages (Newtonsoft.Json) +3. Build in Release mode +4. Copy the entire `bin\Release` folder to `C:\RecticelPrintService\` + +### 2. **Register the Custom Protocol** + +**Option A: Double-click the registry file** +1. Right-click `recticel-print-protocol.reg` +2. Select "Run as administrator" +3. Confirm the registry import + +**Option B: Manual registry import** +1. Open Command Prompt as Administrator +2. Run: `reg import "recticel-print-protocol.reg"` + +### 3. **Install Printer Drivers** + +**For Epson TM-T20:** +- Download and install Epson TM-T20 drivers from Epson website +- Configure printer as "Epson TM-T20" in Windows + +**For Citizen CTS-310:** +- Download and install Citizen CTS-310 drivers +- Configure printer as "Citizen CTS-310" in Windows + +### 4. **Configure Printer Settings** + +1. Open Windows "Printers & scanners" +2. Set paper size to **80mm x 110mm** (or closest available) +3. Set print quality to **300 DPI** for thermal printers +4. Disable margins (set to 0 if possible) + +### 5. **Test the Installation** + +1. Open your web browser +2. Navigate to the print module page +3. Select an order +4. Choose "Direct Print" option +5. Select your thermal printer +6. Click "Print Labels" + +## šŸ–Øļø Printer Configuration + +### Label Size Settings +- **Width**: 80mm +- **Height**: 110mm +- **Margins**: 0mm (all sides) +- **Print Quality**: 300 DPI + +### Thermal Printer Specific Settings + +**Epson TM-T20:** +- Paper Type: Continuous label +- Print Speed: Normal +- Print Density: Adjust based on label stock + +**Citizen CTS-310:** +- Media Type: Label +- Print Method: Thermal Transfer or Direct Thermal +- Print Speed: Medium + +## šŸ”„ How It Works + +1. **Web Interface**: User selects order and clicks "Direct Print" +2. **Data Encoding**: Order data is encoded as Base64 JSON +3. **Custom Protocol**: Browser calls `recticel-print://printer/data` +4. **Service Activation**: Windows launches RecticelPrintService.exe +5. **Data Processing**: Service decodes and formats label data +6. **Direct Printing**: Label is sent directly to the thermal printer +7. **Status Update**: Database is updated to mark order as printed + +## šŸš€ URL Format + +``` +recticel-print://[printer_name]/[base64_encoded_json] +``` + +**Example:** +``` +recticel-print://Epson%20TM-T20/eyJvcmRlcl9pZCI6MTIzLCJjb21hbmRhX3Byb2R1Y3RpZSI6IkNQMDAwMDA3MTEifQ== +``` + +## āš ļø Troubleshooting + +### Service Not Starting +1. Check if the service is installed in `C:\RecticelPrintService\` +2. Verify registry entries are correct +3. Run as Administrator if needed + +### Printer Not Found +1. Check printer name in Windows exactly matches selection +2. Ensure printer drivers are installed +3. Test print from Windows to verify printer works + +### Labels Not Printing +1. Verify paper size is set to 80x110mm +2. Check printer margins are set to 0 +3. Ensure thermal paper is loaded correctly + +### Browser Security +- Some browsers may block custom protocols +- Add site to trusted sites if needed +- Chrome may show a confirmation dialog + +## šŸ”§ Advanced Configuration + +### Custom Printer Names +Users can add custom printer names by selecting "Other Printer..." and entering the exact Windows printer name. + +### Multiple Label Quantities +The service automatically prints the quantity specified in the order, with sequential numbering (CP00000711-001, CP00000711-002, etc.). + +### Fallback to PDF +If direct printing fails, users can switch to "Generate PDF" mode for manual printing. + +## šŸ“ž Support + +For technical support: +1. Check Windows Event Viewer for service errors +2. Verify printer connectivity and driver installation +3. Test with PDF generation mode as fallback +4. Contact IT support with specific error messages + +## šŸ”’ Security Notes + +- The service only processes label data from trusted sources +- Registry changes require Administrator privileges +- Service runs with current user permissions +- No network connections are made by the service \ No newline at end of file diff --git a/recticel_print_service/RecticelPrintService.sln b/recticel_print_service/RecticelPrintService.sln new file mode 100644 index 0000000..8b086dd --- /dev/null +++ b/recticel_print_service/RecticelPrintService.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28010.2046 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RecticelPrintService", "RecticelPrintService\RecticelPrintService.csproj", "{41ED6235-3C76-4723-A5F6-02D2FA51E677}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {41ED6235-3C76-4723-A5F6-02D2FA51E677}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41ED6235-3C76-4723-A5F6-02D2FA51E677}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41ED6235-3C76-4723-A5F6-02D2FA51E677}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41ED6235-3C76-4723-A5F6-02D2FA51E677}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FC401D2A-E68E-47EE-B315-E1A2F0B292FA} + EndGlobalSection +EndGlobal \ No newline at end of file diff --git a/recticel_print_service/RecticelPrintService/App.config b/recticel_print_service/RecticelPrintService/App.config new file mode 100644 index 0000000..a021ad2 --- /dev/null +++ b/recticel_print_service/RecticelPrintService/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/recticel_print_service/RecticelPrintService/LabelPrinter.cs b/recticel_print_service/RecticelPrintService/LabelPrinter.cs new file mode 100644 index 0000000..d176451 --- /dev/null +++ b/recticel_print_service/RecticelPrintService/LabelPrinter.cs @@ -0,0 +1,288 @@ +using System; +using System.Drawing; +using System.Drawing.Printing; +using System.Text; +using Newtonsoft.Json; + +namespace RecticelPrintService +{ + public class LabelPrinter + { + private PrintData _labelData; + + // Label dimensions in millimeters (80x110mm) + private const float LABEL_WIDTH_MM = 80f; + private const float LABEL_HEIGHT_MM = 110f; + + // Convert mm to hundredths of an inch (used by .NET printing) + private float MmToHundredthsOfInch(float mm) + { + return mm * 3.937f; // 1mm = 0.03937 inches, * 100 for hundredths + } + + public bool PrintLabel(string printerName, string jsonData) + { + try + { + // Parse JSON data + _labelData = JsonConvert.DeserializeObject(jsonData); + + if (_labelData == null) + { + Console.WriteLine("Failed to parse JSON data"); + return false; + } + + // Set up print document + PrintDocument printDoc = new PrintDocument(); + printDoc.PrinterSettings.PrinterName = printerName; + + // Check if printer exists + if (!printDoc.PrinterSettings.IsValid) + { + Console.WriteLine($"Printer '{printerName}' not found or not available"); + Console.WriteLine("Available printers:"); + foreach (string printer in PrinterSettings.InstalledPrinters) + { + Console.WriteLine($" - {printer}"); + } + return false; + } + + // Set paper size for 80x110mm labels + PaperSize labelSize = new PaperSize("Label 80x110mm", + (int)MmToHundredthsOfInch(LABEL_WIDTH_MM), + (int)MmToHundredthsOfInch(LABEL_HEIGHT_MM)); + + printDoc.DefaultPageSettings.PaperSize = labelSize; + printDoc.DefaultPageSettings.Margins = new Margins(0, 0, 0, 0); + + // Set print event handler + printDoc.PrintPage += PrintDoc_PrintPage; + + // Print the document + printDoc.Print(); + + return true; + } + catch (Exception ex) + { + Console.WriteLine($"Error printing label: {ex.Message}"); + return false; + } + } + + private void PrintDoc_PrintPage(object sender, PrintPageEventArgs e) + { + try + { + Graphics g = e.Graphics; + + // Define fonts + Font companyFont = new Font("Arial", 10, FontStyle.Bold); + Font headerFont = new Font("Arial", 8, FontStyle.Bold); + Font dataFont = new Font("Arial", 9, FontStyle.Bold); + Font labelFont = new Font("Arial", 7); + Font barcodeFont = new Font("Courier New", 6, FontStyle.Bold); + + // Define brushes and pens + Brush textBrush = Brushes.Black; + Pen borderPen = new Pen(Color.Black, 2); + Pen dividerPen = new Pen(Color.Gray, 1); + + // Calculate positions (in pixels, 300 DPI assumed) + float dpiX = e.Graphics.DpiX; + float dpiY = e.Graphics.DpiY; + + // Convert mm to pixels + float labelWidth = (LABEL_WIDTH_MM / 25.4f) * dpiX; + float labelHeight = (LABEL_HEIGHT_MM / 25.4f) * dpiY; + + // Content area (similar to your HTML preview) + float contentX = (3f / 25.4f) * dpiX; // 3mm margin + float contentY = (15f / 25.4f) * dpiY; // 15mm from bottom + float contentWidth = (60f / 25.4f) * dpiX; // 60mm width + float contentHeight = (85f / 25.4f) * dpiY; // 85mm height + + // Draw main content border + g.DrawRectangle(borderPen, contentX, contentY, contentWidth, contentHeight); + + // Calculate row height + float rowHeight = contentHeight / 10f; // 9 rows + spacing + float currentY = contentY; + + // Row 1: Company Header + string companyText = "INNOFA RROMANIA SRL"; + SizeF companySize = g.MeasureString(companyText, companyFont); + float companyX = contentX + (contentWidth - companySize.Width) / 2; + g.DrawString(companyText, companyFont, textBrush, companyX, currentY + rowHeight/3); + currentY += rowHeight; + + // Horizontal divider after company + g.DrawLine(dividerPen, contentX, currentY, contentX + contentWidth, currentY); + + // Row 2: Customer Name + string customerName = _labelData.CustomerName ?? "N/A"; + SizeF customerSize = g.MeasureString(customerName, headerFont); + float customerX = contentX + (contentWidth - customerSize.Width) / 2; + g.DrawString(customerName, headerFont, textBrush, customerX, currentY + rowHeight/3); + currentY += rowHeight; + + // Horizontal divider after customer + g.DrawLine(dividerPen, contentX, currentY, contentX + contentWidth, currentY); + + // Vertical divider (40% from left) + float verticalDividerX = contentX + contentWidth * 0.4f; + g.DrawLine(dividerPen, verticalDividerX, currentY, verticalDividerX, contentY + contentHeight); + + // Row 3: Quantity + g.DrawString("Quantity ordered", labelFont, textBrush, contentX + 5, currentY + rowHeight/4); + string quantity = _labelData.Cantitate.ToString(); + SizeF qtySize = g.MeasureString(quantity, dataFont); + float qtyX = verticalDividerX + (contentWidth * 0.6f - qtySize.Width) / 2; + g.DrawString(quantity, dataFont, textBrush, qtyX, currentY + rowHeight/4); + currentY += rowHeight; + + // Horizontal divider + g.DrawLine(dividerPen, contentX, currentY, contentX + contentWidth, currentY); + + // Row 4: Customer Order + g.DrawString("Customer order", labelFont, textBrush, contentX + 5, currentY + rowHeight/4); + string customerOrder = $"{_labelData.ComAchizClient}-{_labelData.NrLinieComClient}"; + if (string.IsNullOrEmpty(_labelData.ComAchizClient)) customerOrder = "N/A"; + SizeF orderSize = g.MeasureString(customerOrder, dataFont); + float orderX = verticalDividerX + (contentWidth * 0.6f - orderSize.Width) / 2; + g.DrawString(customerOrder, dataFont, textBrush, orderX, currentY + rowHeight/4); + currentY += rowHeight; + + // Horizontal divider + g.DrawLine(dividerPen, contentX, currentY, contentX + contentWidth, currentY); + + // Row 5: Delivery Date + g.DrawString("Delivery date", labelFont, textBrush, contentX + 5, currentY + rowHeight/4); + string deliveryDate = _labelData.DataLivrare ?? "N/A"; + if (!string.IsNullOrEmpty(deliveryDate) && deliveryDate != "N/A") + { + try + { + DateTime dt = DateTime.Parse(deliveryDate); + deliveryDate = dt.ToString("dd/MM/yyyy"); + } + catch { } + } + SizeF dateSize = g.MeasureString(deliveryDate, dataFont); + float dateX = verticalDividerX + (contentWidth * 0.6f - dateSize.Width) / 2; + g.DrawString(deliveryDate, dataFont, textBrush, dateX, currentY + rowHeight/4); + currentY += rowHeight; + + // Horizontal divider + g.DrawLine(dividerPen, contentX, currentY, contentX + contentWidth, currentY); + + // Row 6: Description (double height) + g.DrawString("Description", labelFont, textBrush, contentX + 5, currentY + rowHeight/4); + string description = _labelData.DescrComProd ?? "N/A"; + // Word wrap description if needed + RectangleF descRect = new RectangleF(verticalDividerX + 5, currentY + 5, + contentWidth * 0.6f - 10, rowHeight * 2 - 10); + g.DrawString(description, dataFont, textBrush, descRect); + currentY += rowHeight * 2; + + // Horizontal divider + g.DrawLine(dividerPen, contentX, currentY, contentX + contentWidth, currentY); + + // Row 7: Size + g.DrawString("Size", labelFont, textBrush, contentX + 5, currentY + rowHeight/4); + string size = _labelData.Dimensiune ?? "N/A"; + SizeF sizeSize = g.MeasureString(size, dataFont); + float sizeX = verticalDividerX + (contentWidth * 0.6f - sizeSize.Width) / 2; + g.DrawString(size, dataFont, textBrush, sizeX, currentY + rowHeight/4); + currentY += rowHeight; + + // Horizontal divider + g.DrawLine(dividerPen, contentX, currentY, contentX + contentWidth, currentY); + + // Row 8: Article Code + g.DrawString("Article Code", labelFont, textBrush, contentX + 5, currentY + rowHeight/4); + string articleCode = _labelData.CustomerArticleNumber ?? "N/A"; + SizeF artSize = g.MeasureString(articleCode, dataFont); + float artX = verticalDividerX + (contentWidth * 0.6f - artSize.Width) / 2; + g.DrawString(articleCode, dataFont, textBrush, artX, currentY + rowHeight/4); + currentY += rowHeight; + + // Horizontal divider + g.DrawLine(dividerPen, contentX, currentY, contentX + contentWidth, currentY); + + // Row 9: Prod Order + g.DrawString("Prod Order", labelFont, textBrush, contentX + 5, currentY + rowHeight/4); + string prodOrder = _labelData.SequentialNumber ?? $"{_labelData.ComandaProductie}-{_labelData.Cantitate}"; + SizeF prodSize = g.MeasureString(prodOrder, dataFont); + float prodX = verticalDividerX + (contentWidth * 0.6f - prodSize.Width) / 2; + g.DrawString(prodOrder, dataFont, textBrush, prodX, currentY + rowHeight/4); + + // Bottom barcode area + float barcodeY = contentY + contentHeight + (5f / 25.4f) * dpiY; // 5mm below content + float barcodeWidth = contentWidth; + float barcodeHeight = (10f / 25.4f) * dpiY; // 10mm height + + // Draw barcode representation (simple lines) + DrawSimpleBarcode(g, contentX, barcodeY, barcodeWidth, barcodeHeight, prodOrder); + + // Barcode text + SizeF barcodeTextSize = g.MeasureString(prodOrder, barcodeFont); + float barcodeTextX = contentX + (contentWidth - barcodeTextSize.Width) / 2; + g.DrawString(prodOrder, barcodeFont, textBrush, barcodeTextX, barcodeY + barcodeHeight + 5); + + // Vertical barcode on the right side + float verticalBarcodeX = contentX + contentWidth + (5f / 25.4f) * dpiX; // 5mm to the right + float verticalBarcodeWidth = (8f / 25.4f) * dpiX; // 8mm width + DrawSimpleVerticalBarcode(g, verticalBarcodeX, contentY + rowHeight * 2, + verticalBarcodeWidth, contentHeight - rowHeight * 2, customerOrder); + + // Dispose resources + companyFont.Dispose(); + headerFont.Dispose(); + dataFont.Dispose(); + labelFont.Dispose(); + barcodeFont.Dispose(); + borderPen.Dispose(); + dividerPen.Dispose(); + } + catch (Exception ex) + { + Console.WriteLine($"Error in PrintPage event: {ex.Message}"); + } + } + + private void DrawSimpleBarcode(Graphics g, float x, float y, float width, float height, string data) + { + // Simple barcode representation using alternating lines + float barWidth = 2f; + bool drawBar = true; + + for (float currentX = x; currentX < x + width; currentX += barWidth) + { + if (drawBar) + { + g.FillRectangle(Brushes.Black, currentX, y, barWidth, height * 0.8f); + } + drawBar = !drawBar; + } + } + + private void DrawSimpleVerticalBarcode(Graphics g, float x, float y, float width, float height, string data) + { + // Simple vertical barcode representation + float barHeight = 2f; + bool drawBar = true; + + for (float currentY = y; currentY < y + height; currentY += barHeight) + { + if (drawBar) + { + g.FillRectangle(Brushes.Black, x, currentY, width * 0.8f, barHeight); + } + drawBar = !drawBar; + } + } + } +} \ No newline at end of file diff --git a/recticel_print_service/RecticelPrintService/PrintData.cs b/recticel_print_service/RecticelPrintService/PrintData.cs new file mode 100644 index 0000000..8036369 --- /dev/null +++ b/recticel_print_service/RecticelPrintService/PrintData.cs @@ -0,0 +1,49 @@ +using Newtonsoft.Json; + +namespace RecticelPrintService +{ + public class PrintData + { + [JsonProperty("order_id")] + public int OrderId { get; set; } + + [JsonProperty("comanda_productie")] + public string ComandaProductie { get; set; } + + [JsonProperty("customer_name")] + public string CustomerName { get; set; } + + [JsonProperty("cantitate")] + public int Cantitate { get; set; } + + [JsonProperty("com_achiz_client")] + public string ComAchizClient { get; set; } + + [JsonProperty("nr_linie_com_client")] + public string NrLinieComClient { get; set; } + + [JsonProperty("data_livrare")] + public string DataLivrare { get; set; } + + [JsonProperty("dimensiune")] + public string Dimensiune { get; set; } + + [JsonProperty("descr_com_prod")] + public string DescrComProd { get; set; } + + [JsonProperty("customer_article_number")] + public string CustomerArticleNumber { get; set; } + + [JsonProperty("cod_articol")] + public string CodArticol { get; set; } + + [JsonProperty("sequential_number")] + public string SequentialNumber { get; set; } + + [JsonProperty("current_label")] + public int CurrentLabel { get; set; } + + [JsonProperty("total_labels")] + public int TotalLabels { get; set; } + } +} \ No newline at end of file diff --git a/recticel_print_service/RecticelPrintService/Program.cs b/recticel_print_service/RecticelPrintService/Program.cs new file mode 100644 index 0000000..a60f278 --- /dev/null +++ b/recticel_print_service/RecticelPrintService/Program.cs @@ -0,0 +1,81 @@ +using System; +using System.Web; + +namespace RecticelPrintService +{ + class Program + { + static void Main(string[] args) + { + try + { + if (args.Length == 0) + { + Console.WriteLine("Recticel Print Service - No arguments provided"); + Console.WriteLine("Usage: RecticelPrintService.exe \"recticel-print://printer/data\""); + Console.ReadKey(); + return; + } + + string url = args[0]; + Console.WriteLine($"Received print request: {url}"); + + // Parse the custom protocol URL + // Format: recticel-print://printer_name/base64_encoded_json_data + if (!url.StartsWith("recticel-print://")) + { + Console.WriteLine("Invalid protocol. Expected: recticel-print://"); + Console.ReadKey(); + return; + } + + // Remove protocol prefix + string data = url.Substring("recticel-print://".Length); + + // Split printer name and data + string[] parts = data.Split('/'); + if (parts.Length < 2) + { + Console.WriteLine("Invalid URL format. Expected: recticel-print://printer_name/data"); + Console.ReadKey(); + return; + } + + string printerName = HttpUtility.UrlDecode(parts[0]); + string encodedData = HttpUtility.UrlDecode(parts[1]); + + Console.WriteLine($"Target Printer: {printerName}"); + Console.WriteLine($"Data Length: {encodedData.Length}"); + + // Decode base64 JSON data + byte[] dataBytes = Convert.FromBase64String(encodedData); + string jsonData = System.Text.Encoding.UTF8.GetString(dataBytes); + + Console.WriteLine($"Decoded JSON: {jsonData}"); + + // Parse and print the label + var printer = new LabelPrinter(); + bool success = printer.PrintLabel(printerName, jsonData); + + if (success) + { + Console.WriteLine("āœ… Label printed successfully!"); + } + else + { + Console.WriteLine("āŒ Failed to print label!"); + } + + // Keep console open for debugging (remove in production) + Console.WriteLine("Press any key to exit..."); + Console.ReadKey(); + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + Console.WriteLine(ex.StackTrace); + Console.ReadKey(); + } + } + } +} \ No newline at end of file diff --git a/recticel_print_service/RecticelPrintService/Properties/AssemblyInfo.cs b/recticel_print_service/RecticelPrintService/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..e8a8bb7 --- /dev/null +++ b/recticel_print_service/RecticelPrintService/Properties/AssemblyInfo.cs @@ -0,0 +1,19 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("RecticelPrintService")] +[assembly: AssemblyDescription("Direct label printing service for Recticel Quality System")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Recticel")] +[assembly: AssemblyProduct("Recticel Print Service")] +[assembly: AssemblyCopyright("Copyright Ā© Recticel 2025")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("41ed6235-3c76-4723-a5f6-02d2fa51e677")] + +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] \ No newline at end of file diff --git a/recticel_print_service/RecticelPrintService/RecticelPrintService.csproj b/recticel_print_service/RecticelPrintService/RecticelPrintService.csproj new file mode 100644 index 0000000..e82abac --- /dev/null +++ b/recticel_print_service/RecticelPrintService/RecticelPrintService.csproj @@ -0,0 +1,62 @@ + + + + + Debug + AnyCPU + {41ED6235-3C76-4723-A5F6-02D2FA51E677} + Exe + RecticelPrintService + RecticelPrintService + v4.7.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + ..\packages\Newtonsoft.Json.13.0.1\lib\net45\Newtonsoft.Json.dll + + + + + + + + + + + + + + \ No newline at end of file diff --git a/recticel_print_service/RecticelPrintService/packages.config b/recticel_print_service/RecticelPrintService/packages.config new file mode 100644 index 0000000..a70468f --- /dev/null +++ b/recticel_print_service/RecticelPrintService/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/recticel_print_service/install.bat b/recticel_print_service/install.bat new file mode 100644 index 0000000..f55faa0 --- /dev/null +++ b/recticel_print_service/install.bat @@ -0,0 +1,50 @@ +@echo off +echo Installing Recticel Print Service... +echo. + +REM Check if running as Administrator +net session >nul 2>&1 +if %errorlevel% neq 0 ( + echo ERROR: This script must be run as Administrator. + echo Right-click and select "Run as administrator" + pause + exit /b 1 +) + +REM Create installation directory +echo Creating installation directory... +if not exist "C:\RecticelPrintService" ( + mkdir "C:\RecticelPrintService" +) + +REM Copy files (assuming this script is in the same directory as the built application) +echo Copying application files... +copy "RecticelPrintService\bin\Release\*.*" "C:\RecticelPrintService\" /Y + +REM Register custom protocol +echo Registering custom protocol... +reg import "recticel-print-protocol.reg" + +if %errorlevel% equ 0 ( + echo. + echo āœ… Installation completed successfully! + echo. + echo Service installed to: C:\RecticelPrintService\ + echo Protocol registered: recticel-print:// + echo. + echo Next steps: + echo 1. Configure your thermal label printer in Windows + echo 2. Set label size to 80mm x 110mm + echo 3. Test printing from the web interface + echo. + echo Press any key to exit... + pause >nul +) else ( + echo. + echo āŒ Installation failed! + echo Please check if you're running as Administrator. + echo. + pause +) + +exit /b 0 \ No newline at end of file diff --git a/recticel_print_service/recticel-print-protocol.reg b/recticel_print_service/recticel-print-protocol.reg new file mode 100644 index 0000000..ea2a8ff --- /dev/null +++ b/recticel_print_service/recticel-print-protocol.reg @@ -0,0 +1,14 @@ +Windows Registry Editor Version 5.00 + +[HKEY_CLASSES_ROOT\recticel-print] +@="URL:Recticel Print Protocol" +"URL Protocol"="" +"EditFlags"=dword:00000002 + +[HKEY_CLASSES_ROOT\recticel-print\shell] +@="open" + +[HKEY_CLASSES_ROOT\recticel-print\shell\open] + +[HKEY_CLASSES_ROOT\recticel-print\shell\open\command] +@="\"C:\\RecticelPrintService\\RecticelPrintService.exe\" \"%1\"" \ No newline at end of file