updated : solutions
This commit is contained in:
@@ -1,74 +0,0 @@
|
||||
# Windows Print Service Network Configuration Guide
|
||||
|
||||
## Current Issue
|
||||
Your Flask server (Linux) cannot connect to Windows Print Service because they're on different machines.
|
||||
|
||||
## Solution Options
|
||||
|
||||
### Option 1: Same Machine Setup
|
||||
If both Flask and Windows service are on the same machine:
|
||||
```python
|
||||
WINDOWS_PRINT_SERVICE_URL = "http://localhost:8765"
|
||||
```
|
||||
|
||||
### Option 2: Different Machines (Recommended)
|
||||
If Flask server (Linux) and Windows service are on different machines:
|
||||
|
||||
1. **Find Windows Machine IP Address:**
|
||||
```cmd
|
||||
# On Windows machine, run:
|
||||
ipconfig
|
||||
# Look for IPv4 Address, e.g., 192.168.1.100
|
||||
```
|
||||
|
||||
2. **Update Configuration:**
|
||||
```python
|
||||
# In /app/print_config.py, change:
|
||||
WINDOWS_PRINT_SERVICE_URL = "http://192.168.1.100:8765"
|
||||
```
|
||||
|
||||
3. **Test Connection:**
|
||||
```bash
|
||||
# From Linux machine, test:
|
||||
curl http://192.168.1.100:8765/health
|
||||
```
|
||||
|
||||
### Option 3: Windows Firewall Configuration
|
||||
Ensure Windows allows incoming connections on port 8765:
|
||||
|
||||
1. **Windows Firewall Settings:**
|
||||
- Control Panel → System and Security → Windows Defender Firewall
|
||||
- Advanced Settings → Inbound Rules → New Rule
|
||||
- Port → TCP → 8765 → Allow the connection
|
||||
|
||||
2. **Test from Linux:**
|
||||
```bash
|
||||
telnet [WINDOWS_IP] 8765
|
||||
```
|
||||
|
||||
### Option 4: Port Forwarding/Tunneling
|
||||
If direct connection isn't possible, set up SSH tunnel or port forwarding.
|
||||
|
||||
## Quick Fix for Testing
|
||||
|
||||
1. **Get Windows IP Address**
|
||||
2. **Update print_config.py with the IP**
|
||||
3. **Restart Flask server**
|
||||
4. **Test printing**
|
||||
|
||||
## Current Status
|
||||
- ✅ Windows Print Service: Running (localhost:8765/health works)
|
||||
- ❌ Network Connection: Flask can't reach Windows service
|
||||
- 🔧 Fix Needed: Update WINDOWS_PRINT_SERVICE_URL with correct IP
|
||||
|
||||
## Example Commands
|
||||
|
||||
```bash
|
||||
# 1. Find Windows IP (run on Windows):
|
||||
# ipconfig | findstr IPv4
|
||||
|
||||
# 2. Test connection (run on Linux):
|
||||
# curl http://[WINDOWS_IP]:8765/health
|
||||
|
||||
# 3. Update config and restart Flask
|
||||
```
|
||||
@@ -1,125 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
PDF Label Dimension Analysis Tool
|
||||
Analyzes the generated PDF to verify exact dimensions and content positioning
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), 'app'))
|
||||
|
||||
from app.pdf_generator import LabelPDFGenerator, mm_to_points
|
||||
from reportlab.lib.units import mm
|
||||
|
||||
def analyze_pdf_dimensions():
|
||||
"""Analyze the PDF dimensions in detail"""
|
||||
print("=== PDF Label Dimension Analysis ===\n")
|
||||
|
||||
generator = LabelPDFGenerator()
|
||||
|
||||
# Label dimensions
|
||||
print("1. LABEL PHYSICAL DIMENSIONS:")
|
||||
print(f" Width: {generator.label_width/mm:.1f}mm ({generator.label_width:.2f} points)")
|
||||
print(f" Height: {generator.label_height/mm:.1f}mm ({generator.label_height:.2f} points)")
|
||||
print(f" ✓ Should be exactly 80.0mm × 110.0mm")
|
||||
|
||||
# Content area
|
||||
print(f"\n2. CONTENT AREA DIMENSIONS:")
|
||||
print(f" Content Width: {generator.content_width/mm:.1f}mm")
|
||||
print(f" Content Height: {generator.content_height/mm:.1f}mm")
|
||||
print(f" Left margin: {generator.content_x/mm:.1f}mm")
|
||||
print(f" Bottom margin: {generator.content_y/mm:.1f}mm")
|
||||
print(f" Right margin: {(generator.label_width - generator.content_x - generator.content_width)/mm:.1f}mm")
|
||||
print(f" Top margin: {(generator.label_height - generator.content_y - generator.content_height)/mm:.1f}mm")
|
||||
|
||||
# Row analysis
|
||||
print(f"\n3. ROW LAYOUT:")
|
||||
total_rows = 9
|
||||
standard_rows = 8 # 7 standard + 1 double
|
||||
double_rows = 1
|
||||
|
||||
standard_row_height_mm = generator.row_height/mm
|
||||
double_row_height_mm = generator.double_row_height/mm
|
||||
|
||||
total_content_used = (standard_rows * standard_row_height_mm + double_rows * double_row_height_mm)
|
||||
expected_content_height = generator.content_height/mm
|
||||
|
||||
print(f" Standard row height: {standard_row_height_mm:.1f}mm")
|
||||
print(f" Double row height: {double_row_height_mm:.1f}mm")
|
||||
print(f" Total rows: {total_rows} (8 standard + 1 double)")
|
||||
print(f" Content height used: {total_content_used:.1f}mm")
|
||||
print(f" Content area available: {expected_content_height:.1f}mm")
|
||||
print(f" Remaining space: {expected_content_height - total_content_used:.1f}mm")
|
||||
|
||||
# Barcode area
|
||||
print(f"\n4. BARCODE AREA:")
|
||||
barcode_bottom_margin = generator.content_y/mm
|
||||
print(f" Bottom barcode space: {barcode_bottom_margin:.1f}mm (should be ~12-15mm)")
|
||||
|
||||
top_margin = (generator.label_height - generator.content_y - generator.content_height)/mm
|
||||
print(f" Top margin space: {top_margin:.1f}mm")
|
||||
|
||||
# Check for potential issues
|
||||
print(f"\n5. POTENTIAL ISSUES ANALYSIS:")
|
||||
|
||||
# Check if content is too high
|
||||
if total_content_used > expected_content_height:
|
||||
print(f" ⚠️ ISSUE: Content ({total_content_used:.1f}mm) exceeds available space ({expected_content_height:.1f}mm)")
|
||||
else:
|
||||
print(f" ✓ Content fits within available space")
|
||||
|
||||
# Check margins
|
||||
if barcode_bottom_margin < 10:
|
||||
print(f" ⚠️ WARNING: Bottom margin ({barcode_bottom_margin:.1f}mm) may be too small for barcode")
|
||||
else:
|
||||
print(f" ✓ Bottom margin adequate for barcode")
|
||||
|
||||
if top_margin < 3:
|
||||
print(f" ⚠️ WARNING: Top margin ({top_margin:.1f}mm) very small")
|
||||
else:
|
||||
print(f" ✓ Top margin adequate")
|
||||
|
||||
# Aspect ratio check
|
||||
aspect_ratio = generator.label_height / generator.label_width
|
||||
expected_ratio = 110.0 / 80.0 # 1.375
|
||||
|
||||
print(f"\n6. ASPECT RATIO CHECK:")
|
||||
print(f" Current ratio: {aspect_ratio:.3f}")
|
||||
print(f" Expected ratio: {expected_ratio:.3f} (110/80)")
|
||||
|
||||
if abs(aspect_ratio - expected_ratio) < 0.001:
|
||||
print(f" ✓ Aspect ratio is correct")
|
||||
else:
|
||||
print(f" ⚠️ Aspect ratio mismatch!")
|
||||
|
||||
# Browser display notes
|
||||
print(f"\n7. BROWSER DISPLAY CONSIDERATIONS:")
|
||||
print(f" • Browser zoom affects apparent size")
|
||||
print(f" • PDF viewer scaling can change appearance")
|
||||
print(f" • Monitor DPI affects screen dimensions")
|
||||
print(f" • Print preview scaling may alter appearance")
|
||||
print(f"\n 🔧 SOLUTION: Always verify with physical printout at 100% scale")
|
||||
|
||||
return {
|
||||
'width_mm': generator.label_width/mm,
|
||||
'height_mm': generator.label_height/mm,
|
||||
'content_height_mm': expected_content_height,
|
||||
'content_used_mm': total_content_used,
|
||||
'aspect_ratio': aspect_ratio,
|
||||
'expected_ratio': expected_ratio
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
results = analyze_pdf_dimensions()
|
||||
|
||||
print(f"\n=== SUMMARY ===")
|
||||
if (abs(results['width_mm'] - 80.0) < 0.1 and
|
||||
abs(results['height_mm'] - 110.0) < 0.1 and
|
||||
abs(results['aspect_ratio'] - results['expected_ratio']) < 0.001):
|
||||
print("✅ PDF dimensions are CORRECT: 80mm × 110mm")
|
||||
print("If labels appear too large, check:")
|
||||
print(" • Browser zoom level (should be 100%)")
|
||||
print(" • PDF viewer scaling settings")
|
||||
print(" • Print scaling (should be 'Actual size' or 100%)")
|
||||
else:
|
||||
print("❌ PDF dimensions need adjustment")
|
||||
Binary file not shown.
@@ -2065,6 +2065,47 @@ def update_printed_status(order_id):
|
||||
print(f"DEBUG: Error in update_printed_status: {e}")
|
||||
return jsonify({'error': 'Internal server error'}), 500
|
||||
|
||||
|
||||
@bp.route('/download_print_service')
|
||||
def download_print_service():
|
||||
"""Download the direct print service package"""
|
||||
try:
|
||||
from flask import send_from_directory, current_app
|
||||
import os
|
||||
|
||||
# Check if complete package is requested
|
||||
package_type = request.args.get('package', 'basic')
|
||||
|
||||
if package_type == 'complete':
|
||||
# Serve the complete package
|
||||
filename = 'QualityPrintService_Complete.zip'
|
||||
else:
|
||||
# Serve the basic package (legacy)
|
||||
filename = 'RecticelPrintService.zip'
|
||||
|
||||
# Path to the print service files
|
||||
service_path = os.path.join(current_app.root_path, 'static', 'downloads')
|
||||
|
||||
# Create the directory if it doesn't exist
|
||||
os.makedirs(service_path, exist_ok=True)
|
||||
|
||||
# Check if the zip file exists
|
||||
full_path = os.path.join(service_path, filename)
|
||||
|
||||
if not os.path.exists(full_path):
|
||||
# If the zip doesn't exist, return information about creating it
|
||||
return jsonify({
|
||||
'error': f'Print service package ({filename}) not found',
|
||||
'message': f'The {package_type} print service package is not available',
|
||||
'suggestion': 'Please contact the administrator or use the basic package'
|
||||
}), 404
|
||||
|
||||
return send_from_directory(service_path, filename, as_attachment=True)
|
||||
|
||||
except Exception as e:
|
||||
print(f"DEBUG: Error downloading print service: {e}")
|
||||
return jsonify({'error': 'Failed to download print service'}), 500
|
||||
|
||||
@bp.route('/get_order_data/<int:order_id>', methods=['GET'])
|
||||
def get_order_data(order_id):
|
||||
"""Get specific order data for preview"""
|
||||
|
||||
@@ -204,6 +204,25 @@
|
||||
<div style="margin-bottom: 5px;">Creates sequential labels based on quantity</div>
|
||||
<small>(e.g., CP00000711-001 to CP00000711-063)</small>
|
||||
</div>
|
||||
|
||||
<!-- Direct Print Service Download -->
|
||||
<div style="width: 100%; text-align: center; margin-top: 20px; padding-top: 15px; border-top: 1px solid #e9ecef;">
|
||||
<div style="background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 6px; padding: 12px; margin-bottom: 10px;">
|
||||
<div style="font-size: 11px; color: #856404; margin-bottom: 8px;">
|
||||
<strong>📦 Quality Print Service</strong>
|
||||
</div>
|
||||
<div style="font-size: 10px; color: #6c757d; margin-bottom: 10px; line-height: 1.3;">
|
||||
Professional Windows service for instant direct printing to thermal label printers without PDF dialogs
|
||||
</div>
|
||||
<a href="{{ url_for('main.download_print_service') }}?package=complete" class="btn btn-outline-warning btn-sm" download style="font-size: 10px; padding: 4px 12px; text-decoration: none;">
|
||||
📥 Download Complete Package
|
||||
</a>
|
||||
</div>
|
||||
<div style="font-size: 9px; color: #6c757d; margin-top: 5px; line-height: 1.2;">
|
||||
<strong>Easy Install:</strong> Extract → Run install.bat as Admin → Done!<br>
|
||||
<small>Supports all thermal printers • Auto-starts with Windows • One-click installation</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- Data Preview Card -->
|
||||
<div class="card scan-table-card" style="min-height: 700px; width: calc(100% - 350px); margin: 0;">
|
||||
@@ -330,6 +349,9 @@ document.getElementById('check-db-btn').addEventListener('click', function() {
|
||||
// Initialize PDF generation functionality
|
||||
addPDFGenerationHandler();
|
||||
|
||||
// Initialize print service
|
||||
initializePrintService();
|
||||
|
||||
// Auto-select first row
|
||||
setTimeout(() => {
|
||||
const firstRow = document.querySelector('.print-module-table tbody tr');
|
||||
@@ -396,6 +418,96 @@ function updateLabelPreview(order) {
|
||||
document.getElementById('barcode-text').textContent = prodOrder;
|
||||
}
|
||||
|
||||
// Initialize print service connection and load available printers
|
||||
function initializePrintService() {
|
||||
// Check service status and load printers
|
||||
checkPrintServiceStatus()
|
||||
.then(serviceStatus => {
|
||||
if (serviceStatus) {
|
||||
console.log('✅ Print service is available');
|
||||
loadAvailablePrinters();
|
||||
updateServiceStatusIndicator(true);
|
||||
} else {
|
||||
console.log('⚠️ Print service not available');
|
||||
updateServiceStatusIndicator(false);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('❌ Print service connection failed:', error);
|
||||
updateServiceStatusIndicator(false);
|
||||
});
|
||||
}
|
||||
|
||||
// Load available printers from print service
|
||||
function loadAvailablePrinters() {
|
||||
fetch('http://localhost:8899/printers', {
|
||||
method: 'GET',
|
||||
mode: 'cors'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const printerSelect = document.getElementById('printerSelect');
|
||||
|
||||
// Clear existing options except default ones
|
||||
const defaultOptions = ['default', 'Epson TM-T20', 'Citizen CTS-310', 'custom'];
|
||||
Array.from(printerSelect.options).forEach(option => {
|
||||
if (!defaultOptions.includes(option.value)) {
|
||||
option.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Add available printers
|
||||
if (data.printers && data.printers.length > 0) {
|
||||
data.printers.forEach(printer => {
|
||||
// Don't add if already exists in default options
|
||||
if (!defaultOptions.includes(printer.name)) {
|
||||
const option = document.createElement('option');
|
||||
option.value = printer.name;
|
||||
option.textContent = `${printer.name}${printer.default ? ' (Default)' : ''}${printer.status !== 'ready' ? ' - Offline' : ''}`;
|
||||
|
||||
// Insert before "Other Printer..." option
|
||||
const customOption = printerSelect.querySelector('option[value="custom"]');
|
||||
printerSelect.insertBefore(option, customOption);
|
||||
}
|
||||
});
|
||||
|
||||
// Set default printer if available
|
||||
const defaultPrinter = data.printers.find(p => p.default);
|
||||
if (defaultPrinter) {
|
||||
printerSelect.value = defaultPrinter.name;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Loaded ${data.printers.length} printers from service`);
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('Could not load printers from service:', error);
|
||||
});
|
||||
}
|
||||
|
||||
// Update service status indicator
|
||||
function updateServiceStatusIndicator(isAvailable) {
|
||||
// Find or create status indicator
|
||||
let statusIndicator = document.getElementById('service-status-indicator');
|
||||
if (!statusIndicator) {
|
||||
statusIndicator = document.createElement('div');
|
||||
statusIndicator.id = 'service-status-indicator';
|
||||
statusIndicator.style.cssText = 'font-size: 9px; color: #6c757d; margin-top: 5px; text-align: center;';
|
||||
|
||||
// Insert after printer selection
|
||||
const printerSelection = document.getElementById('printerSelection');
|
||||
printerSelection.appendChild(statusIndicator);
|
||||
}
|
||||
|
||||
if (isAvailable) {
|
||||
statusIndicator.innerHTML = '🟢 <strong>Print service connected</strong><br><small>Direct printing available</small>';
|
||||
statusIndicator.style.color = '#28a745';
|
||||
} else {
|
||||
statusIndicator.innerHTML = '🔴 <strong>Print service offline</strong><br><small>Install and start the service for direct printing</small>';
|
||||
statusIndicator.style.color = '#dc3545';
|
||||
}
|
||||
}
|
||||
|
||||
// PDF Generation System - No printer setup needed
|
||||
// Labels are generated as PDF files for universal compatibility
|
||||
function addPDFGenerationHandler() {
|
||||
@@ -463,67 +575,114 @@ function handleDirectPrint(selectedRow) {
|
||||
printerName = prompt('Enter your printer name:');
|
||||
if (!printerName) return;
|
||||
} else if (printerName === 'default') {
|
||||
printerName = 'default';
|
||||
printerName = ''; // Empty for default printer
|
||||
}
|
||||
|
||||
console.log(`Direct printing to: ${printerName}`);
|
||||
console.log(`Direct printing to: ${printerName || 'default printer'}`);
|
||||
|
||||
// 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;
|
||||
// Check if print service is available
|
||||
checkPrintServiceStatus()
|
||||
.then(serviceStatus => {
|
||||
if (!serviceStatus) {
|
||||
throw new Error('Print service is not available');
|
||||
}
|
||||
|
||||
// Generate PDF first for printing
|
||||
return generatePrintablePDF(orderId, true); // true for paper-saving mode
|
||||
})
|
||||
.then(pdfUrl => {
|
||||
// Send PDF to print service
|
||||
const printData = {
|
||||
pdf_url: pdfUrl,
|
||||
printer: printerName,
|
||||
copies: 1,
|
||||
order_id: parseInt(orderId),
|
||||
paper_saving: true
|
||||
};
|
||||
|
||||
return fetch('http://localhost:8899/print-pdf', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(printData)
|
||||
});
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Print service error: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
alert(`✅ Successfully sent ${quantity} labels to printer!\n📊 Order: ${prodOrder}\n🖨️ Printer: ${printerName || 'Default'}\n📄 Job ID: ${result.job_id || 'N/A'}`);
|
||||
|
||||
// Update database status and refresh table
|
||||
updatePrintedStatus(orderId);
|
||||
} else {
|
||||
throw new Error(result.error || 'Unknown print error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Direct print error:', error);
|
||||
|
||||
let errorMessage = '❌ Direct printing failed: ' + error.message;
|
||||
|
||||
if (error.message.includes('service is not available')) {
|
||||
errorMessage += '\n\n💡 Solutions:\n' +
|
||||
'1. Install the print service using the download link below\n' +
|
||||
'2. Ensure the service is running (check Windows Services)\n' +
|
||||
'3. Try restarting the print service\n' +
|
||||
'4. Use PDF generation as alternative';
|
||||
} else if (error.message.includes('fetch')) {
|
||||
errorMessage += '\n\n💡 The print service may not be running.\n' +
|
||||
'Please start the Quality Print Service from Windows Services.';
|
||||
}
|
||||
|
||||
alert(errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to check print service status
|
||||
function checkPrintServiceStatus() {
|
||||
return fetch('http://localhost:8899/status', {
|
||||
method: 'GET',
|
||||
mode: 'cors'
|
||||
})
|
||||
.then(response => {
|
||||
if (response.ok) {
|
||||
return response.json().then(data => {
|
||||
console.log('Print service status:', data);
|
||||
return data.status === 'running';
|
||||
});
|
||||
}
|
||||
|
||||
// Small delay between labels to prevent overwhelming the printer
|
||||
if (i < quantity) {
|
||||
setTimeout(() => {}, 100);
|
||||
return false;
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('Print service not available:', error.message);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to generate PDF for printing
|
||||
function generatePrintablePDF(orderId, paperSaving = true) {
|
||||
return fetch(`/generate_labels_pdf/${orderId}/${paperSaving ? 'true' : 'false'}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
|
||||
// Show success message
|
||||
setTimeout(() => {
|
||||
alert(`✅ Sent ${quantity} labels to printer: ${printerName}\n📊 Order: ${prodOrder}`);
|
||||
|
||||
// Update database status and refresh table
|
||||
updatePrintedStatus(orderId);
|
||||
}, 500);
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to generate PDF: ${response.status}`);
|
||||
}
|
||||
return response.blob();
|
||||
})
|
||||
.then(blob => {
|
||||
// Create a temporary URL for the PDF
|
||||
return URL.createObjectURL(blob);
|
||||
});
|
||||
}
|
||||
|
||||
function handlePDFGeneration(selectedRow) {
|
||||
|
||||
@@ -1,422 +0,0 @@
|
||||
# Quality Control Management System - Documentation
|
||||
|
||||
## Table of Contents
|
||||
1. [Login System](#login-system)
|
||||
2. [Dashboard System](#dashboard-system)
|
||||
3. [User Authentication](#user-authentication)
|
||||
4. [Role-Based Access Control](#role-based-access-control)
|
||||
|
||||
---
|
||||
|
||||
## Login System
|
||||
|
||||
### Overview
|
||||
The Quality Control Management System features a dual-database authentication system that provides flexible user management and robust access control. The login system supports both internal SQLite database users and external MariaDB database users.
|
||||
|
||||
### Authentication Flow
|
||||
|
||||
#### 1. Login Page Access
|
||||
- **URL**: `/login`
|
||||
- **Template**: `login.html`
|
||||
- **Methods**: `GET`, `POST`
|
||||
|
||||
#### 2. User Interface
|
||||
The login page features:
|
||||
- **Company Logo**: Displayed prominently on the left side
|
||||
- **Login Form**: Clean, centered form on the right side
|
||||
- **Required Fields**:
|
||||
- Username (text input)
|
||||
- Password (password input)
|
||||
- **Responsive Design**: Adapts to different screen sizes
|
||||
|
||||
#### 3. Authentication Methods
|
||||
|
||||
##### Internal Database Authentication
|
||||
Users can access the system using the internal SQLite database by prefixing their username with `#`:
|
||||
|
||||
**Format**: `#username`
|
||||
**Example**: `#admin` for internal admin user
|
||||
|
||||
**Database Details**:
|
||||
- **Location**: `py_app/instance/users.db`
|
||||
- **Table**: `users`
|
||||
- **Schema**: `username, password, role`
|
||||
- **Use Case**: System administrators, fallback authentication
|
||||
|
||||
##### External Database Authentication
|
||||
Standard authentication uses the external MariaDB database:
|
||||
|
||||
**Format**: `username` (no prefix)
|
||||
**Example**: `john.doe` for external user
|
||||
|
||||
**Database Details**:
|
||||
- **Type**: MariaDB
|
||||
- **Configuration**: Loaded from `external_database_settings`
|
||||
- **Table**: `users`
|
||||
- **Schema**: `username, password, role`
|
||||
- **Use Case**: Regular operational users
|
||||
|
||||
#### 4. Authentication Logic
|
||||
|
||||
```python
|
||||
# Authentication Process Flow
|
||||
if username.startswith('#'):
|
||||
# Internal SQLite Database Authentication
|
||||
username_clean = username[1:].strip()
|
||||
# Query: py_app/instance/users.db
|
||||
|
||||
else:
|
||||
# External MariaDB Database Authentication
|
||||
# Primary: External database query
|
||||
# Fallback: Internal database if external fails
|
||||
```
|
||||
|
||||
#### 5. Security Features
|
||||
|
||||
##### Input Validation
|
||||
- **Required Fields**: Both username and password must be provided
|
||||
- **Sanitization**: Automatic trimming of whitespace
|
||||
- **Error Handling**: Clear error messages for invalid inputs
|
||||
|
||||
##### Database Connection Security
|
||||
- **Dual Fallback**: External database with internal fallback
|
||||
- **Error Isolation**: Database errors don't expose system details
|
||||
- **Connection Management**: Proper connection opening/closing
|
||||
|
||||
##### Session Management
|
||||
- **Secure Sessions**: User credentials stored in Flask session
|
||||
- **Role Tracking**: User role preserved for authorization
|
||||
- **Session Data**:
|
||||
- `session['user']`: Username
|
||||
- `session['role']`: User role
|
||||
|
||||
#### 6. User Roles
|
||||
|
||||
The system supports multiple user roles with different access levels:
|
||||
|
||||
- **superadmin**: Full system access, all modules and administrative functions
|
||||
- **admin**: Administrative access with some limitations
|
||||
- **quality**: Quality control module access
|
||||
- **warehouse**: Warehouse management module access
|
||||
- **scan**: Scanning operations access
|
||||
- **etichete**: Label management access
|
||||
- **management**: Management reporting and oversight
|
||||
|
||||
#### 7. Login Process
|
||||
|
||||
1. **User Navigation**: User accesses `/login` URL
|
||||
2. **Form Display**: Login form rendered with company branding
|
||||
3. **Credential Submission**: User enters username/password and submits
|
||||
4. **Authentication Check**:
|
||||
- Internal users: Check SQLite database
|
||||
- External users: Check MariaDB database with SQLite fallback
|
||||
5. **Session Creation**: Valid credentials create user session
|
||||
6. **Redirect**: Successful login redirects to `/dashboard`
|
||||
7. **Error Handling**: Invalid credentials display error message
|
||||
|
||||
#### 8. Error Messages
|
||||
|
||||
- **Missing Credentials**: "Please enter both username and password."
|
||||
- **Invalid Credentials**: "Invalid credentials. Please try again."
|
||||
- **Database Errors**: Handled gracefully with fallback mechanisms
|
||||
|
||||
#### 9. Post-Login Behavior
|
||||
|
||||
After successful authentication:
|
||||
- **Session Establishment**: User session created with username and role
|
||||
- **Dashboard Redirect**: User redirected to main dashboard
|
||||
- **Access Control**: Role-based permissions applied throughout system
|
||||
- **Navigation**: Header displays logged-in user information
|
||||
|
||||
#### 10. Security Considerations
|
||||
|
||||
##### Password Security
|
||||
- **Storage**: Passwords stored in plaintext (consider encryption upgrade)
|
||||
- **Transmission**: Form-based submission over HTTPS recommended
|
||||
- **Session**: Password not stored in session, only username/role
|
||||
|
||||
##### Database Security
|
||||
- **Connection Strings**: External database settings in separate config
|
||||
- **Error Handling**: Database errors logged but not exposed to users
|
||||
- **Fallback System**: Ensures availability even if external database fails
|
||||
|
||||
### Technical Implementation
|
||||
|
||||
#### Frontend Components
|
||||
- **Template**: `templates/login.html`
|
||||
- **Styling**: Login-specific CSS in `static/style.css`
|
||||
- **Assets**: Company logo (`static/logo_login.jpg`)
|
||||
|
||||
#### Backend Components
|
||||
- **Route Handler**: `@bp.route('/login', methods=['GET', 'POST'])`
|
||||
- **Database Connections**: SQLite and MariaDB integration
|
||||
- **Session Management**: Flask session handling
|
||||
- **Error Handling**: Comprehensive exception management
|
||||
|
||||
#### Configuration Files
|
||||
- **External Database**: Configuration loaded from `external_database_settings`
|
||||
- **Internal Database**: SQLite database in `instance/users.db`
|
||||
|
||||
### Usage Examples
|
||||
|
||||
#### Standard User Login
|
||||
```
|
||||
Username: john.doe
|
||||
Password: userpassword
|
||||
Result: Queries external MariaDB database
|
||||
```
|
||||
|
||||
#### Internal Admin Login
|
||||
```
|
||||
Username: #admin
|
||||
Password: adminpassword
|
||||
Result: Queries internal SQLite database
|
||||
```
|
||||
|
||||
#### System Administrator Login
|
||||
```
|
||||
Username: #superadmin
|
||||
Password: superpass
|
||||
Result: Internal database, full system access
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dashboard System
|
||||
|
||||
### Overview
|
||||
The dashboard serves as the central hub of the Quality Control Management System, providing authenticated users with access to various system modules based on their assigned roles. It features a clean, card-based interface that displays available modules and ensures proper access control.
|
||||
|
||||
### Dashboard Access
|
||||
|
||||
#### 1. Dashboard Page Access
|
||||
- **URL**: `/dashboard`
|
||||
- **Template**: `dashboard.html`
|
||||
- **Methods**: `GET`
|
||||
- **Authentication Required**: Yes (redirects to login if not authenticated)
|
||||
|
||||
#### 2. User Interface Design
|
||||
|
||||
The dashboard features a modern, responsive card-based layout:
|
||||
- **Container**: Full-width responsive grid layout
|
||||
- **Module Cards**: Individual cards for each system module
|
||||
- **Visual Hierarchy**: Clear headings, descriptions, and call-to-action buttons
|
||||
- **Responsive Design**: Adapts to different screen sizes and devices
|
||||
|
||||
#### 3. Available Modules
|
||||
|
||||
##### Scanning Module
|
||||
- **Card Title**: "Access Scanning Module"
|
||||
- **Description**: "Final scanning module for production orders"
|
||||
- **Button**: "Launch Scanning Module"
|
||||
- **Route**: `/scan`
|
||||
- **Required Roles**: `superadmin`, `scan`
|
||||
- **Purpose**: Quality control scanning operations for production orders
|
||||
|
||||
##### Reports Module (Quality)
|
||||
- **Card Title**: "Access Reports Module"
|
||||
- **Description**: "Module for verification and quality settings configuration"
|
||||
- **Button**: "Launch Reports Module"
|
||||
- **Route**: `/quality`
|
||||
- **Required Roles**: `superadmin`, `quality`
|
||||
- **Purpose**: Quality reporting, defects analysis, and quality control reports
|
||||
|
||||
##### Warehouse Module
|
||||
- **Card Title**: "Access Warehouse Module"
|
||||
- **Description**: "Access warehouse module functionalities"
|
||||
- **Button**: "Open Warehouse"
|
||||
- **Route**: `/warehouse`
|
||||
- **Required Roles**: `superadmin`, `warehouse`
|
||||
- **Purpose**: Warehouse management operations and inventory control
|
||||
|
||||
##### Labels Module
|
||||
- **Card Title**: "Access Labels Module"
|
||||
- **Description**: "Module for label management"
|
||||
- **Button**: "Launch Labels Module"
|
||||
- **Route**: `/etichete`
|
||||
- **Required Roles**: `superadmin`, `etichete`
|
||||
- **Purpose**: Label creation, template management, and printing operations
|
||||
|
||||
##### Settings Module
|
||||
- **Card Title**: "Manage Settings"
|
||||
- **Description**: "Access and manage application settings"
|
||||
- **Button**: "Access Settings Page"
|
||||
- **Route**: `/settings`
|
||||
- **Required Roles**: `superadmin` only
|
||||
- **Purpose**: System configuration, user management, and administrative settings
|
||||
|
||||
#### 4. Access Control Logic
|
||||
|
||||
The dashboard implements role-based access control at both the display and route levels:
|
||||
|
||||
##### Frontend Display Control
|
||||
All module cards are displayed to all authenticated users, but access is controlled at the route level.
|
||||
|
||||
##### Backend Route Protection
|
||||
Each module route implements permission checking:
|
||||
|
||||
```python
|
||||
# Quality Module Access Control
|
||||
@bp.route('/quality')
|
||||
def quality():
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'quality']:
|
||||
flash('Access denied: Quality users only.')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
# Warehouse Module Access Control
|
||||
@bp.route('/warehouse')
|
||||
def warehouse():
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse']:
|
||||
flash('Access denied: Warehouse users only.')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
# Scanning Module Access Control
|
||||
@bp.route('/scan')
|
||||
def scan():
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'scan']:
|
||||
flash('Access denied: Scan users only.')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
# Labels Module Access Control
|
||||
@bp.route('/etichete')
|
||||
def etichete():
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'etichete']:
|
||||
flash('Access denied: Etichete users only.')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
# Settings Module Access Control (Superadmin Only)
|
||||
def settings_handler():
|
||||
if 'role' not in session or session['role'] != 'superadmin':
|
||||
flash('Access denied: Superadmin only.')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
```
|
||||
|
||||
#### 5. User Experience Flow
|
||||
|
||||
1. **Authentication Check**: User must be logged in to access dashboard
|
||||
2. **Dashboard Display**: All module cards shown regardless of role
|
||||
3. **Module Selection**: User clicks on desired module button
|
||||
4. **Permission Validation**: System checks user role against module requirements
|
||||
5. **Access Grant/Deny**:
|
||||
- **Authorized**: User redirected to module interface
|
||||
- **Unauthorized**: Error message displayed, user remains on dashboard
|
||||
|
||||
#### 6. Session Management
|
||||
|
||||
The dashboard relies on Flask session management:
|
||||
- **Session Check**: `if 'user' not in session` validates authentication
|
||||
- **Role Access**: `session['role']` determines module permissions
|
||||
- **Debug Logging**: Session information logged for troubleshooting
|
||||
|
||||
#### 7. Error Handling
|
||||
|
||||
##### Authentication Errors
|
||||
- **Unauthenticated Users**: Automatic redirect to login page
|
||||
- **Session Timeout**: Redirect to login with appropriate messaging
|
||||
|
||||
##### Authorization Errors
|
||||
- **Insufficient Permissions**: Flash message with specific role requirements
|
||||
- **Access Denied**: User returned to dashboard with error notification
|
||||
- **Clear Messaging**: Specific error messages indicate required permissions
|
||||
|
||||
#### 8. Security Features
|
||||
|
||||
##### Session Security
|
||||
- **Authentication Required**: All dashboard access requires valid session
|
||||
- **Role Validation**: Each module validates user role before access
|
||||
- **Automatic Redirect**: Unauthorized access redirected safely
|
||||
|
||||
##### Access Control
|
||||
- **Principle of Least Privilege**: Users only access modules for their role
|
||||
- **Superadmin Override**: Superadmin role has access to all modules
|
||||
- **Route-Level Protection**: Backend validation prevents unauthorized access
|
||||
|
||||
#### 9. Module Descriptions
|
||||
|
||||
##### Quality Reports Module
|
||||
- **Primary Function**: Generate quality control reports and analytics
|
||||
- **Key Features**:
|
||||
- Daily, weekly, and custom date range reports
|
||||
- Quality defects analysis and tracking
|
||||
- Export capabilities (CSV format)
|
||||
- Database testing tools (superadmin only)
|
||||
|
||||
##### Scanning Module
|
||||
- **Primary Function**: Production order scanning and quality validation
|
||||
- **Key Features**:
|
||||
- Barcode/QR code scanning interface
|
||||
- Real-time quality validation
|
||||
- Production order processing
|
||||
|
||||
##### Warehouse Module
|
||||
- **Primary Function**: Warehouse operations and inventory management
|
||||
- **Key Features**:
|
||||
- Inventory tracking and management
|
||||
- Location management
|
||||
- Warehouse reporting
|
||||
|
||||
##### Labels Module
|
||||
- **Primary Function**: Label design, generation, and printing
|
||||
- **Key Features**:
|
||||
- Label template creation and management
|
||||
- Dynamic label generation
|
||||
- Print management system
|
||||
|
||||
##### Settings Module
|
||||
- **Primary Function**: System administration and configuration
|
||||
- **Key Features**:
|
||||
- User account management
|
||||
- Role and permission configuration
|
||||
- Database settings management
|
||||
- System configuration options
|
||||
|
||||
#### 10. Technical Implementation
|
||||
|
||||
##### Frontend Components
|
||||
- **Template**: `templates/dashboard.html`
|
||||
- **Styling**: Dashboard-specific CSS classes in `static/style.css`
|
||||
- **Layout**: CSS Grid/Flexbox responsive card layout
|
||||
- **Navigation**: Base template integration with header/footer
|
||||
|
||||
##### Backend Components
|
||||
- **Route Handler**: `@bp.route('/dashboard')`
|
||||
- **Session Management**: Flask session integration
|
||||
- **Authentication Check**: User session validation
|
||||
- **Logging**: Debug output for troubleshooting
|
||||
|
||||
##### Styling Classes
|
||||
- `.dashboard-container`: Main container with responsive grid
|
||||
- `.dashboard-card`: Individual module cards
|
||||
- `.btn`: Standardized button styling
|
||||
- Responsive breakpoints for mobile/tablet adaptation
|
||||
|
||||
### Usage Examples
|
||||
|
||||
#### Superadmin User Dashboard Access
|
||||
```
|
||||
Role: superadmin
|
||||
Available Modules: All (Scanning, Quality, Warehouse, Labels, Settings)
|
||||
Special Access: Settings module exclusive access
|
||||
```
|
||||
|
||||
#### Quality Control User Dashboard Access
|
||||
```
|
||||
Role: quality
|
||||
Available Modules: Quality Reports module only
|
||||
Restricted Access: Cannot access other modules
|
||||
```
|
||||
|
||||
#### Multi-Role Access Example
|
||||
```
|
||||
Role: warehouse
|
||||
Available Modules: Warehouse module only
|
||||
Access Pattern: Click → Permission Check → Module Access
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*This documentation covers the dashboard system implementation. For specific module details, see their respective documentation sections.*
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Windows Print Service Connection Tester
|
||||
Tests connectivity between Flask server and Windows Print Service
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), 'app'))
|
||||
|
||||
import requests
|
||||
import time
|
||||
from app.print_config import WINDOWS_PRINT_SERVICE_URL, PRINT_SERVICE_TIMEOUT
|
||||
|
||||
def test_print_service_connection():
|
||||
"""Test connection to Windows Print Service"""
|
||||
print("=== Windows Print Service Connection Test ===\n")
|
||||
|
||||
print(f"🔍 Testing connection to: {WINDOWS_PRINT_SERVICE_URL}")
|
||||
print(f"⏱️ Timeout: {PRINT_SERVICE_TIMEOUT} seconds")
|
||||
print(f"🌐 From: Flask server (Linux)")
|
||||
print(f"🎯 To: Windows Print Service")
|
||||
print()
|
||||
|
||||
# Test 1: Health check
|
||||
print("1️⃣ Testing /health endpoint...")
|
||||
try:
|
||||
start_time = time.time()
|
||||
response = requests.get(
|
||||
f"{WINDOWS_PRINT_SERVICE_URL}/health",
|
||||
timeout=PRINT_SERVICE_TIMEOUT
|
||||
)
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
print(f" ✅ SUCCESS (Status: {response.status_code}, Time: {elapsed:.2f}s)")
|
||||
print(f" 📋 Service: {data.get('service', 'Unknown')}")
|
||||
print(f" 🖥️ Platform: {data.get('platform', 'Unknown')}")
|
||||
print(f" 📅 Timestamp: {data.get('timestamp', 'Unknown')}")
|
||||
health_ok = True
|
||||
else:
|
||||
print(f" ❌ FAILED (Status: {response.status_code})")
|
||||
print(f" 📝 Response: {response.text}")
|
||||
health_ok = False
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
print(f" ❌ CONNECTION REFUSED")
|
||||
print(f" 🔧 Error: {str(e)}")
|
||||
print(f" 💡 This usually means:")
|
||||
print(f" • Windows service is not running")
|
||||
print(f" • Wrong IP address in configuration")
|
||||
print(f" • Firewall blocking connection")
|
||||
print(f" • Network routing issue")
|
||||
health_ok = False
|
||||
except Exception as e:
|
||||
print(f" ❌ ERROR: {str(e)}")
|
||||
health_ok = False
|
||||
|
||||
print()
|
||||
|
||||
# Test 2: Printers endpoint (only if health check passed)
|
||||
if health_ok:
|
||||
print("2️⃣ Testing /printers endpoint...")
|
||||
try:
|
||||
response = requests.get(
|
||||
f"{WINDOWS_PRINT_SERVICE_URL}/printers",
|
||||
timeout=PRINT_SERVICE_TIMEOUT
|
||||
)
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
printers = data.get('printers', [])
|
||||
print(f" ✅ SUCCESS - Found {len(printers)} printer(s)")
|
||||
for i, printer in enumerate(printers[:3]): # Show first 3
|
||||
name = printer.get('name', 'Unknown') if isinstance(printer, dict) else printer
|
||||
print(f" 🖨️ {i+1}. {name}")
|
||||
if len(printers) > 3:
|
||||
print(f" ... and {len(printers)-3} more")
|
||||
else:
|
||||
print(f" ❌ FAILED (Status: {response.status_code})")
|
||||
except Exception as e:
|
||||
print(f" ❌ ERROR: {str(e)}")
|
||||
|
||||
print()
|
||||
|
||||
# Test 3: Network diagnostics
|
||||
print("3️⃣ Network Diagnostics...")
|
||||
|
||||
# Parse URL to get host and port
|
||||
try:
|
||||
from urllib.parse import urlparse
|
||||
parsed = urlparse(WINDOWS_PRINT_SERVICE_URL)
|
||||
host = parsed.hostname
|
||||
port = parsed.port or 8765
|
||||
|
||||
print(f" 🌐 Target Host: {host}")
|
||||
print(f" 🔌 Target Port: {port}")
|
||||
|
||||
# Test DNS resolution
|
||||
try:
|
||||
import socket
|
||||
ip = socket.gethostbyname(host)
|
||||
print(f" 🔍 DNS Resolution: {host} → {ip}")
|
||||
dns_ok = True
|
||||
except Exception as e:
|
||||
print(f" ❌ DNS Resolution failed: {e}")
|
||||
dns_ok = False
|
||||
|
||||
# Test raw TCP connection
|
||||
if dns_ok:
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(5)
|
||||
result = sock.connect_ex((host, port))
|
||||
sock.close()
|
||||
|
||||
if result == 0:
|
||||
print(f" ✅ TCP Connection: Port {port} is open")
|
||||
else:
|
||||
print(f" ❌ TCP Connection: Port {port} is closed or filtered")
|
||||
print(f" 💡 Check Windows Firewall and service status")
|
||||
except Exception as e:
|
||||
print(f" ❌ TCP Test error: {e}")
|
||||
except Exception as e:
|
||||
print(f" ❌ URL parsing error: {e}")
|
||||
|
||||
print()
|
||||
|
||||
# Final recommendations
|
||||
print("🔧 TROUBLESHOOTING RECOMMENDATIONS:")
|
||||
print()
|
||||
|
||||
if not health_ok:
|
||||
print("❌ CONNECTION FAILED - Try these steps:")
|
||||
print("1. Verify Windows Print Service is running:")
|
||||
print(" • Windows: sc query QualityLabelPrinting")
|
||||
print(" • Or: Get-Service QualityLabelPrinting")
|
||||
print()
|
||||
print("2. Check Windows machine IP address:")
|
||||
print(" • Windows: ipconfig | findstr IPv4")
|
||||
print(" • Update WINDOWS_PRINT_SERVICE_URL in print_config.py")
|
||||
print()
|
||||
print("3. Test firewall:")
|
||||
print(" • Windows: Allow port 8765 in Windows Firewall")
|
||||
print(" • Test: telnet [WINDOWS_IP] 8765")
|
||||
print()
|
||||
print("4. If same machine, verify service binding:")
|
||||
print(" • Service should bind to 0.0.0.0:8765 (not 127.0.0.1)")
|
||||
else:
|
||||
print("✅ CONNECTION SUCCESS - Print service should work!")
|
||||
print("• Service is reachable and responding")
|
||||
print("• Ready for label printing")
|
||||
print("• Try printing labels from the web interface")
|
||||
|
||||
print(f"\n📝 Current Configuration:")
|
||||
print(f" Service URL: {WINDOWS_PRINT_SERVICE_URL}")
|
||||
print(f" Timeout: {PRINT_SERVICE_TIMEOUT}s")
|
||||
|
||||
return health_ok
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_print_service_connection()
|
||||
Reference in New Issue
Block a user