feat: Major system improvements and production deployment
✨ New Features: - Added view_orders route with proper table display - Implemented CSV upload with preview workflow and date parsing - Added production WSGI server configuration with Gunicorn - Created comprehensive production management scripts 🔧 Bug Fixes: - Fixed upload_data route column mapping for actual CSV structure - Resolved print module database queries and template rendering - Fixed view orders navigation routing (was pointing to JSON API) - Corrected barcode display width constraints in print module - Added proper date format parsing for MySQL compatibility 🎨 UI/UX Improvements: - Updated view_orders template with theme-compliant styling - Hidden barcode text in print module preview for cleaner display - Enhanced CSV upload with two-step preview-then-save workflow - Improved error handling and debugging throughout upload process 🚀 Production Infrastructure: - Added Gunicorn WSGI server with proper configuration - Created systemd service for production deployment - Implemented production management scripts (start/stop/status) - Added comprehensive logging setup - Updated requirements.txt with production dependencies 📊 Database & Data: - Enhanced order_for_labels table compatibility - Fixed column mappings for real CSV data structure - Added proper date parsing and validation - Improved error handling with detailed debugging 🔧 Technical Debt: - Reorganized database setup documentation - Added proper error handling throughout upload workflow - Enhanced debugging capabilities for troubleshooting - Improved code organization and documentation
This commit is contained in:
59
logs/access.log
Normal file
59
logs/access.log
Normal file
@@ -0,0 +1,59 @@
|
||||
127.0.0.1 - - [11/Oct/2025:21:54:27 +0300] "GET / HTTP/1.1" 200 1189 "-" "Wget/1.25.0" 52198
|
||||
192.168.0.114 - - [11/Oct/2025:21:54:45 +0300] "GET /settings HTTP/1.1" 200 7526 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 52041
|
||||
192.168.0.114 - - [11/Oct/2025:21:54:45 +0300] "GET /static/css/base.css HTTP/1.1" 304 0 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 13844
|
||||
192.168.0.114 - - [11/Oct/2025:21:54:45 +0300] "GET /static/style.css HTTP/1.1" 304 0 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 28824
|
||||
192.168.0.114 - - [11/Oct/2025:21:54:45 +0300] "GET /static/scan_me.jpg HTTP/1.1" 304 0 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 28843
|
||||
192.168.0.114 - - [11/Oct/2025:21:54:45 +0300] "GET /static/script.js HTTP/1.1" 304 0 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 28905
|
||||
192.168.0.114 - - [11/Oct/2025:21:55:15 +0300] "POST /create_user HTTP/1.1" 302 205 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 14915
|
||||
192.168.0.114 - - [11/Oct/2025:21:55:15 +0300] "GET /settings HTTP/1.1" 200 8101 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 57417
|
||||
192.168.0.114 - - [11/Oct/2025:21:55:15 +0300] "GET /static/css/base.css HTTP/1.1" 304 0 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2648
|
||||
192.168.0.114 - - [11/Oct/2025:21:55:15 +0300] "GET /static/scan_me.jpg HTTP/1.1" 304 0 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 3406
|
||||
192.168.0.114 - - [11/Oct/2025:21:55:15 +0300] "GET /static/script.js HTTP/1.1" 304 0 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 29402
|
||||
192.168.0.114 - - [11/Oct/2025:21:55:15 +0300] "GET /static/style.css HTTP/1.1" 304 0 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 29573
|
||||
192.168.0.114 - - [11/Oct/2025:21:55:51 +0300] "POST /create_user HTTP/1.1" 302 205 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 15335
|
||||
192.168.0.114 - - [11/Oct/2025:21:55:51 +0300] "GET /settings HTTP/1.1" 200 8603 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 74762
|
||||
192.168.0.114 - - [11/Oct/2025:21:55:51 +0300] "GET /static/scan_me.jpg HTTP/1.1" 304 0 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2530
|
||||
192.168.0.114 - - [11/Oct/2025:21:55:51 +0300] "GET /static/script.js HTTP/1.1" 304 0 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2964
|
||||
192.168.0.114 - - [11/Oct/2025:21:55:51 +0300] "GET /static/css/base.css HTTP/1.1" 304 0 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 14047
|
||||
192.168.0.114 - - [11/Oct/2025:21:55:51 +0300] "GET /static/style.css HTTP/1.1" 304 0 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 28996
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:09 +0300] "POST /create_user HTTP/1.1" 302 205 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 15124
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:09 +0300] "GET /settings HTTP/1.1" 200 9105 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 57317
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:09 +0300] "GET /static/css/base.css HTTP/1.1" 304 0 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2372
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:09 +0300] "GET /static/script.js HTTP/1.1" 304 0 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2327
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:09 +0300] "GET /static/style.css HTTP/1.1" 304 0 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2496
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:09 +0300] "GET /static/scan_me.jpg HTTP/1.1" 304 0 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2446
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:31 +0300] "GET /dashboard HTTP/1.1" 200 2527 "http://192.168.0.205:8781/settings" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 40192
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:31 +0300] "GET /static/style.css HTTP/1.1" 304 0 "http://192.168.0.205:8781/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2419
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:31 +0300] "GET /static/css/base.css HTTP/1.1" 304 0 "http://192.168.0.205:8781/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2488
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:31 +0300] "GET /static/scan_me.jpg HTTP/1.1" 304 0 "http://192.168.0.205:8781/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2535
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:31 +0300] "GET /static/script.js HTTP/1.1" 304 0 "http://192.168.0.205:8781/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 28861
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:35 +0300] "GET /logout HTTP/1.1" 302 189 "http://192.168.0.205:8781/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 3949
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:35 +0300] "GET / HTTP/1.1" 200 1189 "http://192.168.0.205:8781/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 39010
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:35 +0300] "GET /static/css/base.css HTTP/1.1" 304 0 "http://192.168.0.205:8781/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2368
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:35 +0300] "GET /static/css/login.css HTTP/1.1" 304 0 "http://192.168.0.205:8781/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2249
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:35 +0300] "GET /static/style.css HTTP/1.1" 304 0 "http://192.168.0.205:8781/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2304
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:35 +0300] "GET /static/logo_login.jpg HTTP/1.1" 304 0 "http://192.168.0.205:8781/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2436
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:35 +0300] "GET /static/script.js HTTP/1.1" 304 0 "http://192.168.0.205:8781/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2435
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:50 +0300] "POST / HTTP/1.1" 302 207 "http://192.168.0.205:8781/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 10225
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:50 +0300] "GET /dashboard HTTP/1.1" 200 2527 "http://192.168.0.205:8781/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 9905
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:50 +0300] "GET /static/css/base.css HTTP/1.1" 304 0 "http://192.168.0.205:8781/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2470
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:50 +0300] "GET /static/style.css HTTP/1.1" 304 0 "http://192.168.0.205:8781/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2325
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:50 +0300] "GET /static/scan_me.jpg HTTP/1.1" 304 0 "http://192.168.0.205:8781/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 3243
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:50 +0300] "GET /static/script.js HTTP/1.1" 304 0 "http://192.168.0.205:8781/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2667
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:57 +0300] "GET /main_scan HTTP/1.1" 200 1981 "http://192.168.0.205:8781/dashboard" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 7045
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:57 +0300] "GET /static/css/base.css HTTP/1.1" 304 0 "http://192.168.0.205:8781/main_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2428
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:57 +0300] "GET /static/scan_me.jpg HTTP/1.1" 304 0 "http://192.168.0.205:8781/main_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2313
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:57 +0300] "GET /static/script.js HTTP/1.1" 304 0 "http://192.168.0.205:8781/main_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2307
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:57 +0300] "GET /static/style.css HTTP/1.1" 304 0 "http://192.168.0.205:8781/main_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 6148
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:59 +0300] "GET /fg_scan HTTP/1.1" 200 18932 "http://192.168.0.205:8781/main_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 26981
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:59 +0300] "GET /static/css/base.css HTTP/1.1" 304 0 "http://192.168.0.205:8781/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2432
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:59 +0300] "GET /static/css/scan.css HTTP/1.1" 200 0 "http://192.168.0.205:8781/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2911
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:59 +0300] "GET /static/scan_me.jpg HTTP/1.1" 304 0 "http://192.168.0.205:8781/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2645
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:59 +0300] "GET /static/script.js HTTP/1.1" 304 0 "http://192.168.0.205:8781/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2875
|
||||
192.168.0.114 - - [11/Oct/2025:21:56:59 +0300] "GET /static/style.css HTTP/1.1" 304 0 "http://192.168.0.205:8781/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 3597
|
||||
192.168.0.114 - - [11/Oct/2025:21:57:19 +0300] "POST /fg_scan HTTP/1.1" 200 18932 "http://192.168.0.205:8781/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 35270
|
||||
192.168.0.114 - - [11/Oct/2025:21:57:19 +0300] "GET /static/scan_me.jpg HTTP/1.1" 304 0 "http://192.168.0.205:8781/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2363
|
||||
192.168.0.114 - - [11/Oct/2025:21:57:19 +0300] "GET /static/css/scan.css HTTP/1.1" 304 0 "http://192.168.0.205:8781/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2354
|
||||
192.168.0.114 - - [11/Oct/2025:21:57:19 +0300] "GET /static/script.js HTTP/1.1" 304 0 "http://192.168.0.205:8781/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2353
|
||||
192.168.0.114 - - [11/Oct/2025:21:57:19 +0300] "GET /static/style.css HTTP/1.1" 304 0 "http://192.168.0.205:8781/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2227
|
||||
192.168.0.114 - - [11/Oct/2025:21:57:19 +0300] "GET /static/css/base.css HTTP/1.1" 304 0 "http://192.168.0.205:8781/fg_scan" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" 2364
|
||||
54
logs/error.log
Normal file
54
logs/error.log
Normal file
@@ -0,0 +1,54 @@
|
||||
[2025-10-11 21:52:09 +0300] [11218] [INFO] Starting gunicorn 23.0.0
|
||||
[2025-10-11 21:52:09 +0300] [11218] [ERROR] Connection in use: ('0.0.0.0', 8781)
|
||||
[2025-10-11 21:52:09 +0300] [11218] [ERROR] connection to ('0.0.0.0', 8781) failed: [Errno 98] Address already in use
|
||||
[2025-10-11 21:52:10 +0300] [11218] [ERROR] Connection in use: ('0.0.0.0', 8781)
|
||||
[2025-10-11 21:52:10 +0300] [11218] [ERROR] connection to ('0.0.0.0', 8781) failed: [Errno 98] Address already in use
|
||||
[2025-10-11 21:52:11 +0300] [11218] [ERROR] Connection in use: ('0.0.0.0', 8781)
|
||||
[2025-10-11 21:52:11 +0300] [11218] [ERROR] connection to ('0.0.0.0', 8781) failed: [Errno 98] Address already in use
|
||||
[2025-10-11 21:52:12 +0300] [11218] [ERROR] Connection in use: ('0.0.0.0', 8781)
|
||||
[2025-10-11 21:52:12 +0300] [11218] [ERROR] connection to ('0.0.0.0', 8781) failed: [Errno 98] Address already in use
|
||||
[2025-10-11 21:52:13 +0300] [11218] [ERROR] Connection in use: ('0.0.0.0', 8781)
|
||||
[2025-10-11 21:52:13 +0300] [11218] [ERROR] connection to ('0.0.0.0', 8781) failed: [Errno 98] Address already in use
|
||||
[2025-10-11 21:52:14 +0300] [11218] [ERROR] Can't connect to ('0.0.0.0', 8781)
|
||||
[2025-10-11 21:54:00 +0300] [11502] [INFO] Starting gunicorn 23.0.0
|
||||
[2025-10-11 21:54:00 +0300] [11502] [INFO] Listening at: http://0.0.0.0:8781 (11502)
|
||||
[2025-10-11 21:54:00 +0300] [11502] [INFO] Using worker: sync
|
||||
[2025-10-11 21:54:00 +0300] [11502] [INFO] Trasabilitate Application server is ready. Listening on: [('0.0.0.0', 8781)]
|
||||
[2025-10-11 21:54:00 +0300] [11502] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-11 21:54:00 +0300] [11519] [INFO] Booting worker with pid: 11519
|
||||
[2025-10-11 21:54:00 +0300] [11519] [INFO] Worker spawned (pid: 11519)
|
||||
[2025-10-11 21:54:01 +0300] [11502] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-11 21:54:01 +0300] [11526] [INFO] Booting worker with pid: 11526
|
||||
[2025-10-11 21:54:01 +0300] [11526] [INFO] Worker spawned (pid: 11526)
|
||||
[2025-10-11 21:54:01 +0300] [11502] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-11 21:54:01 +0300] [11527] [INFO] Booting worker with pid: 11527
|
||||
[2025-10-11 21:54:01 +0300] [11527] [INFO] Worker spawned (pid: 11527)
|
||||
[2025-10-11 21:54:01 +0300] [11502] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-11 21:54:01 +0300] [11528] [INFO] Booting worker with pid: 11528
|
||||
[2025-10-11 21:54:01 +0300] [11528] [INFO] Worker spawned (pid: 11528)
|
||||
[2025-10-11 21:54:01 +0300] [11502] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-11 21:54:01 +0300] [11529] [INFO] Booting worker with pid: 11529
|
||||
[2025-10-11 21:54:01 +0300] [11529] [INFO] Worker spawned (pid: 11529)
|
||||
[2025-10-11 21:54:01 +0300] [11502] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-11 21:54:01 +0300] [11530] [INFO] Booting worker with pid: 11530
|
||||
[2025-10-11 21:54:01 +0300] [11530] [INFO] Worker spawned (pid: 11530)
|
||||
[2025-10-11 21:54:01 +0300] [11502] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-11 21:54:01 +0300] [11531] [INFO] Booting worker with pid: 11531
|
||||
[2025-10-11 21:54:01 +0300] [11531] [INFO] Worker spawned (pid: 11531)
|
||||
[2025-10-11 21:54:01 +0300] [11502] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-11 21:54:01 +0300] [11532] [INFO] Booting worker with pid: 11532
|
||||
[2025-10-11 21:54:01 +0300] [11532] [INFO] Worker spawned (pid: 11532)
|
||||
[2025-10-11 21:54:01 +0300] [11502] [INFO] Worker spawned (pid: [booting])
|
||||
[2025-10-11 21:54:01 +0300] [11533] [INFO] Booting worker with pid: 11533
|
||||
[2025-10-11 21:54:01 +0300] [11533] [INFO] Worker spawned (pid: 11533)
|
||||
[2025-10-11 21:58:19 +0300] [11502] [INFO] Handling signal: term
|
||||
[2025-10-11 21:58:19 +0300] [11519] [INFO] Worker exiting (pid: 11519)
|
||||
[2025-10-11 21:58:19 +0300] [11526] [INFO] Worker exiting (pid: 11526)
|
||||
[2025-10-11 21:58:19 +0300] [11527] [INFO] Worker exiting (pid: 11527)
|
||||
[2025-10-11 21:58:19 +0300] [11533] [INFO] Worker exiting (pid: 11533)
|
||||
[2025-10-11 21:58:19 +0300] [11528] [INFO] Worker exiting (pid: 11528)
|
||||
[2025-10-11 21:58:19 +0300] [11529] [INFO] Worker exiting (pid: 11529)
|
||||
[2025-10-11 21:58:19 +0300] [11530] [INFO] Worker exiting (pid: 11530)
|
||||
[2025-10-11 21:58:19 +0300] [11531] [INFO] Worker exiting (pid: 11531)
|
||||
[2025-10-11 21:58:19 +0300] [11532] [INFO] Worker exiting (pid: 11532)
|
||||
[2025-10-11 21:58:20 +0300] [11502] [INFO] Shutting down: Master
|
||||
@@ -34,9 +34,9 @@ def get_unprinted_orders_data(limit=100):
|
||||
# Use printed_labels column
|
||||
cursor.execute("""
|
||||
SELECT id, comanda_productie, cod_articol, descr_com_prod, cantitate,
|
||||
data_livrare, dimensiune, com_achiz_client, nr_linie_com_client, customer_name,
|
||||
com_achiz_client, nr_linie_com_client, customer_name,
|
||||
customer_article_number, open_for_order, line_number,
|
||||
printed_labels, created_at, updated_at
|
||||
created_at, updated_at, printed_labels, data_livrare, dimensiune
|
||||
FROM order_for_labels
|
||||
WHERE printed_labels != 1
|
||||
ORDER BY created_at DESC
|
||||
@@ -46,7 +46,7 @@ def get_unprinted_orders_data(limit=100):
|
||||
# Fallback: get all orders if no printed_labels column
|
||||
cursor.execute("""
|
||||
SELECT id, comanda_productie, cod_articol, descr_com_prod, cantitate,
|
||||
data_livrare, dimensiune, com_achiz_client, nr_linie_com_client, customer_name,
|
||||
com_achiz_client, nr_linie_com_client, customer_name,
|
||||
customer_article_number, open_for_order, line_number,
|
||||
created_at, updated_at
|
||||
FROM order_for_labels
|
||||
@@ -63,17 +63,17 @@ def get_unprinted_orders_data(limit=100):
|
||||
'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],
|
||||
'com_achiz_client': row[5],
|
||||
'nr_linie_com_client': row[6],
|
||||
'customer_name': row[7],
|
||||
'customer_article_number': row[8],
|
||||
'open_for_order': row[9],
|
||||
'line_number': row[10],
|
||||
'created_at': row[11],
|
||||
'updated_at': row[12],
|
||||
'printed_labels': row[13],
|
||||
'created_at': row[14],
|
||||
'updated_at': row[15]
|
||||
'data_livrare': row[14] or '-',
|
||||
'dimensiune': row[15] or '-'
|
||||
})
|
||||
else:
|
||||
orders.append({
|
||||
@@ -82,17 +82,18 @@ def get_unprinted_orders_data(limit=100):
|
||||
'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': 0, # Default to not printed
|
||||
'created_at': row[13],
|
||||
'updated_at': row[14]
|
||||
'com_achiz_client': row[5],
|
||||
'nr_linie_com_client': row[6],
|
||||
'customer_name': row[7],
|
||||
'customer_article_number': row[8],
|
||||
'open_for_order': row[9],
|
||||
'line_number': row[10],
|
||||
'created_at': row[11],
|
||||
'updated_at': row[12],
|
||||
# Add default values for missing columns
|
||||
'data_livrare': '-',
|
||||
'dimensiune': '-',
|
||||
'printed_labels': 0
|
||||
})
|
||||
|
||||
conn.close()
|
||||
|
||||
@@ -242,27 +242,42 @@ def scan():
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Check if the CP_full_code already exists
|
||||
cursor.execute("SELECT Id FROM scan1_orders WHERE CP_full_code = ?", (cp_code,))
|
||||
existing_entry = cursor.fetchone()
|
||||
|
||||
if existing_entry:
|
||||
# Update the existing entry
|
||||
update_query = """
|
||||
UPDATE scan1_orders
|
||||
SET operator_code = ?, OC1_code = ?, OC2_code = ?, quality_code = ?, date = ?, time = ?
|
||||
WHERE CP_full_code = ?
|
||||
"""
|
||||
cursor.execute(update_query, (operator_code, oc1_code, oc2_code, defect_code, date, time, cp_code))
|
||||
flash('Existing entry updated successfully.')
|
||||
# Always insert a new entry - each scan is a separate record
|
||||
insert_query = """
|
||||
INSERT INTO scan1_orders (operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s)
|
||||
"""
|
||||
cursor.execute(insert_query, (operator_code, cp_code, oc1_code, oc2_code, defect_code, date, time))
|
||||
|
||||
# Get the CP_base_code (first 10 characters of CP_full_code)
|
||||
cp_base_code = cp_code[:10]
|
||||
|
||||
# Count approved quantities (quality_code = 0) for this CP_base_code
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) FROM scan1_orders
|
||||
WHERE CP_base_code = %s AND quality_code = 0
|
||||
""", (cp_base_code,))
|
||||
approved_count = cursor.fetchone()[0]
|
||||
|
||||
# Count rejected quantities (quality_code != 0) for this CP_base_code
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) FROM scan1_orders
|
||||
WHERE CP_base_code = %s AND quality_code != 0
|
||||
""", (cp_base_code,))
|
||||
rejected_count = cursor.fetchone()[0]
|
||||
|
||||
# Update all records with the same CP_base_code with new quantities
|
||||
cursor.execute("""
|
||||
UPDATE scan1_orders
|
||||
SET approved_quantity = %s, rejected_quantity = %s
|
||||
WHERE CP_base_code = %s
|
||||
""", (approved_count, rejected_count, cp_base_code))
|
||||
|
||||
# Flash appropriate message
|
||||
if int(defect_code) == 0:
|
||||
flash(f'✅ APPROVED scan recorded for {cp_code}. Total approved: {approved_count}')
|
||||
else:
|
||||
# Insert a new entry
|
||||
insert_query = """
|
||||
INSERT INTO scan1_orders (operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
"""
|
||||
cursor.execute(insert_query, (operator_code, cp_code, oc1_code, oc2_code, defect_code, date, time))
|
||||
flash('New entry inserted successfully.')
|
||||
flash(f'❌ REJECTED scan recorded for {cp_code} (defect: {defect_code}). Total rejected: {rejected_count}')
|
||||
|
||||
# Commit the transaction
|
||||
conn.commit()
|
||||
@@ -278,7 +293,7 @@ def scan():
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT Id, operator_code, CP_base_code, OC1_code, OC2_code, quality_code, date, time, approved_quantity, rejected_quantity
|
||||
SELECT Id, operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time, approved_quantity, rejected_quantity
|
||||
FROM scan1_orders
|
||||
ORDER BY Id DESC
|
||||
LIMIT 15
|
||||
@@ -321,27 +336,42 @@ def fg_scan():
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Check if the CP_full_code already exists in scanfg_orders
|
||||
cursor.execute("SELECT Id FROM scanfg_orders WHERE CP_full_code = ?", (cp_code,))
|
||||
existing_entry = cursor.fetchone()
|
||||
|
||||
if existing_entry:
|
||||
# Update the existing entry
|
||||
update_query = """
|
||||
UPDATE scanfg_orders
|
||||
SET operator_code = ?, OC1_code = ?, OC2_code = ?, quality_code = ?, date = ?, time = ?
|
||||
WHERE CP_full_code = ?
|
||||
"""
|
||||
cursor.execute(update_query, (operator_code, oc1_code, oc2_code, defect_code, date, time, cp_code))
|
||||
flash('Existing entry updated successfully.')
|
||||
# Always insert a new entry - each scan is a separate record
|
||||
insert_query = """
|
||||
INSERT INTO scanfg_orders (operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s)
|
||||
"""
|
||||
cursor.execute(insert_query, (operator_code, cp_code, oc1_code, oc2_code, defect_code, date, time))
|
||||
|
||||
# Get the CP_base_code (first 10 characters of CP_full_code)
|
||||
cp_base_code = cp_code[:10]
|
||||
|
||||
# Count approved quantities (quality_code = 0) for this CP_base_code
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) FROM scanfg_orders
|
||||
WHERE CP_base_code = %s AND quality_code = 0
|
||||
""", (cp_base_code,))
|
||||
approved_count = cursor.fetchone()[0]
|
||||
|
||||
# Count rejected quantities (quality_code != 0) for this CP_base_code
|
||||
cursor.execute("""
|
||||
SELECT COUNT(*) FROM scanfg_orders
|
||||
WHERE CP_base_code = %s AND quality_code != 0
|
||||
""", (cp_base_code,))
|
||||
rejected_count = cursor.fetchone()[0]
|
||||
|
||||
# Update all records with the same CP_base_code with new quantities
|
||||
cursor.execute("""
|
||||
UPDATE scanfg_orders
|
||||
SET approved_quantity = %s, rejected_quantity = %s
|
||||
WHERE CP_base_code = %s
|
||||
""", (approved_count, rejected_count, cp_base_code))
|
||||
|
||||
# Flash appropriate message
|
||||
if int(defect_code) == 0:
|
||||
flash(f'✅ APPROVED scan recorded for {cp_code}. Total approved: {approved_count}')
|
||||
else:
|
||||
# Insert a new entry
|
||||
insert_query = """
|
||||
INSERT INTO scanfg_orders (operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
"""
|
||||
cursor.execute(insert_query, (operator_code, cp_code, oc1_code, oc2_code, defect_code, date, time))
|
||||
flash('New entry inserted successfully.')
|
||||
flash(f'❌ REJECTED scan recorded for {cp_code} (defect: {defect_code}). Total rejected: {rejected_count}')
|
||||
|
||||
# Commit the transaction
|
||||
conn.commit()
|
||||
@@ -357,7 +387,7 @@ def fg_scan():
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT Id, operator_code, CP_base_code, OC1_code, OC2_code, quality_code, date, time, approved_quantity, rejected_quantity
|
||||
SELECT Id, operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time, approved_quantity, rejected_quantity
|
||||
FROM scanfg_orders
|
||||
ORDER BY Id DESC
|
||||
LIMIT 15
|
||||
@@ -1036,13 +1066,273 @@ def etichete():
|
||||
return redirect(url_for('main.dashboard'))
|
||||
return render_template('main_page_etichete.html')
|
||||
|
||||
@bp.route('/upload_data')
|
||||
@bp.route('/upload_data', methods=['GET', 'POST'])
|
||||
def upload_data():
|
||||
if request.method == 'POST':
|
||||
action = request.form.get('action', 'preview')
|
||||
|
||||
if action == 'preview':
|
||||
# Handle file upload and show preview
|
||||
if 'file' not in request.files:
|
||||
flash('No file selected', 'error')
|
||||
return redirect(request.url)
|
||||
|
||||
file = request.files['file']
|
||||
if file.filename == '':
|
||||
flash('No file selected', 'error')
|
||||
return redirect(request.url)
|
||||
|
||||
if file and file.filename.lower().endswith('.csv'):
|
||||
try:
|
||||
# Read CSV file
|
||||
import csv
|
||||
import io
|
||||
|
||||
# Read the file content
|
||||
stream = io.StringIO(file.stream.read().decode("UTF8"), newline=None)
|
||||
csv_input = csv.DictReader(stream)
|
||||
|
||||
# Convert to list for preview
|
||||
preview_data = []
|
||||
headers = []
|
||||
|
||||
for i, row in enumerate(csv_input):
|
||||
if i == 0:
|
||||
headers = list(row.keys())
|
||||
if i < 10: # Show only first 10 rows for preview
|
||||
preview_data.append(row)
|
||||
else:
|
||||
break
|
||||
|
||||
# Store the full file content in session for later processing
|
||||
file.stream.seek(0) # Reset file pointer
|
||||
session['csv_content'] = file.stream.read().decode("UTF8")
|
||||
session['csv_filename'] = file.filename
|
||||
|
||||
return render_template('upload_orders.html',
|
||||
preview_data=preview_data,
|
||||
headers=headers,
|
||||
show_preview=True,
|
||||
filename=file.filename)
|
||||
|
||||
except Exception as e:
|
||||
flash(f'Error reading CSV file: {str(e)}', 'error')
|
||||
return redirect(request.url)
|
||||
else:
|
||||
flash('Please upload a CSV file', 'error')
|
||||
return redirect(request.url)
|
||||
|
||||
elif action == 'save':
|
||||
# Save the data to database
|
||||
if 'csv_content' not in session:
|
||||
flash('No data to save. Please upload a file first.', 'error')
|
||||
return redirect(request.url)
|
||||
|
||||
try:
|
||||
import csv
|
||||
import io
|
||||
|
||||
print(f"DEBUG: Starting CSV upload processing...")
|
||||
|
||||
# Read the CSV content from session
|
||||
stream = io.StringIO(session['csv_content'], newline=None)
|
||||
csv_input = csv.DictReader(stream)
|
||||
|
||||
# Connect to database
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
inserted_count = 0
|
||||
error_count = 0
|
||||
errors = []
|
||||
|
||||
print(f"DEBUG: Connected to database, processing rows...")
|
||||
|
||||
# Process each row
|
||||
for index, row in enumerate(csv_input):
|
||||
try:
|
||||
print(f"DEBUG: Processing row {index + 1}: {row}")
|
||||
|
||||
# Extract data from CSV row with proper column mapping
|
||||
comanda_productie = str(row.get('comanda_productie', row.get('Comanda Productie', row.get('Order Number', '')))).strip()
|
||||
cod_articol = str(row.get('cod_articol', row.get('Cod Articol', row.get('Article Code', '')))).strip()
|
||||
descr_com_prod = str(row.get('descr_com_prod', row.get('Descr. Com. Prod', row.get('Descr Com Prod', row.get('Description', ''))))).strip()
|
||||
cantitate = int(float(row.get('cantitate', row.get('Cantitate', row.get('Quantity', 0)))))
|
||||
com_achiz_client = str(row.get('com_achiz_client', row.get('Com.Achiz.Client', row.get('Com Achiz Client', '')))).strip()
|
||||
nr_linie_com_client = row.get('nr_linie_com_client', row.get('Nr. Linie com. Client', row.get('Nr Linie Com Client', '')))
|
||||
customer_name = str(row.get('customer_name', row.get('Customer Name', ''))).strip()
|
||||
customer_article_number = str(row.get('customer_article_number', row.get('Customer Article Number', ''))).strip()
|
||||
open_for_order = str(row.get('open_for_order', row.get('Open for order', row.get('Open For Order', '')))).strip()
|
||||
line_number = row.get('line_number', row.get('Line ', row.get('Line Number', '')))
|
||||
data_livrare = str(row.get('data_livrare', row.get('DataLivrare', row.get('Data Livrare', '')))).strip()
|
||||
dimensiune = str(row.get('dimensiune', row.get('Dimensiune', ''))).strip()
|
||||
|
||||
print(f"DEBUG: Extracted data - comanda_productie: {comanda_productie}, descr_com_prod: {descr_com_prod}, cantitate: {cantitate}")
|
||||
|
||||
# Convert empty strings to None for integer fields
|
||||
nr_linie_com_client = int(nr_linie_com_client) if nr_linie_com_client and str(nr_linie_com_client).strip() else None
|
||||
line_number = int(line_number) if line_number and str(line_number).strip() else None
|
||||
|
||||
# Convert empty string to None for date field
|
||||
if data_livrare:
|
||||
try:
|
||||
# Parse date from various formats (9/23/2023, 23/9/2023, 2023-09-23, etc.)
|
||||
from datetime import datetime
|
||||
# Try different date formats
|
||||
date_formats = ['%m/%d/%Y', '%d/%m/%Y', '%Y-%m-%d', '%m-%d-%Y', '%d-%m-%Y']
|
||||
parsed_date = None
|
||||
for fmt in date_formats:
|
||||
try:
|
||||
parsed_date = datetime.strptime(data_livrare, fmt)
|
||||
break
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
if parsed_date:
|
||||
data_livrare = parsed_date.strftime('%Y-%m-%d') # MySQL date format
|
||||
print(f"DEBUG: Parsed date: {data_livrare}")
|
||||
else:
|
||||
print(f"DEBUG: Could not parse date: {data_livrare}, setting to None")
|
||||
data_livrare = None
|
||||
except Exception as date_error:
|
||||
print(f"DEBUG: Date parsing error: {date_error}")
|
||||
data_livrare = None
|
||||
else:
|
||||
data_livrare = None
|
||||
|
||||
dimensiune = dimensiune if dimensiune else None
|
||||
|
||||
print(f"DEBUG: Final data before insert - nr_linie: {nr_linie_com_client}, line_number: {line_number}, data_livrare: {data_livrare}")
|
||||
|
||||
if comanda_productie and descr_com_prod and cantitate > 0:
|
||||
# Insert into order_for_labels table with correct columns
|
||||
print(f"DEBUG: Inserting order: {comanda_productie}")
|
||||
try:
|
||||
cursor.execute("""
|
||||
INSERT INTO order_for_labels (
|
||||
comanda_productie, cod_articol, descr_com_prod, cantitate,
|
||||
com_achiz_client, nr_linie_com_client, customer_name,
|
||||
customer_article_number, open_for_order, line_number,
|
||||
data_livrare, dimensiune, printed_labels
|
||||
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, 0)
|
||||
""", (comanda_productie, cod_articol, descr_com_prod, cantitate,
|
||||
com_achiz_client, nr_linie_com_client, customer_name,
|
||||
customer_article_number, open_for_order, line_number,
|
||||
data_livrare, dimensiune))
|
||||
inserted_count += 1
|
||||
print(f"DEBUG: Successfully inserted order: {comanda_productie}")
|
||||
except Exception as insert_error:
|
||||
print(f"DEBUG: Database insert error for {comanda_productie}: {insert_error}")
|
||||
errors.append(f"Row {index + 1}: Database error - {str(insert_error)}")
|
||||
error_count += 1
|
||||
else:
|
||||
missing_fields = []
|
||||
if not comanda_productie:
|
||||
missing_fields.append("comanda_productie")
|
||||
if not descr_com_prod:
|
||||
missing_fields.append("descr_com_prod")
|
||||
if cantitate <= 0:
|
||||
missing_fields.append("cantitate (must be > 0)")
|
||||
errors.append(f"Row {index + 1}: Missing required fields: {', '.join(missing_fields)}")
|
||||
error_count += 1
|
||||
except ValueError as e:
|
||||
errors.append(f"Row {index + 1}: Invalid quantity value")
|
||||
error_count += 1
|
||||
except Exception as e:
|
||||
errors.append(f"Row {index + 1}: {str(e)}")
|
||||
error_count += 1
|
||||
continue
|
||||
|
||||
# Commit the transaction
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
print(f"DEBUG: Committed {inserted_count} records to database")
|
||||
|
||||
# Clear session data
|
||||
session.pop('csv_content', None)
|
||||
session.pop('csv_filename', None)
|
||||
|
||||
# Show results
|
||||
if error_count > 0:
|
||||
flash(f'Upload completed: {inserted_count} orders saved, {error_count} errors', 'warning')
|
||||
for error in errors[:5]: # Show only first 5 errors
|
||||
flash(error, 'error')
|
||||
if len(errors) > 5:
|
||||
flash(f'... and {len(errors) - 5} more errors', 'error')
|
||||
else:
|
||||
flash(f'Successfully uploaded {inserted_count} orders for labels', 'success')
|
||||
|
||||
except Exception as e:
|
||||
flash(f'Error processing data: {str(e)}', 'error')
|
||||
|
||||
return redirect(url_for('main.upload_data'))
|
||||
|
||||
# GET request - show the upload form
|
||||
return render_template('upload_orders.html')
|
||||
|
||||
@bp.route('/upload_orders')
|
||||
def upload_orders():
|
||||
"""Redirect to upload_data for compatibility"""
|
||||
return redirect(url_for('main.upload_data'))
|
||||
|
||||
@bp.route('/print_module')
|
||||
def print_module():
|
||||
return render_template('print_module.html')
|
||||
try:
|
||||
# Get unprinted orders data
|
||||
orders_data = get_unprinted_orders_data(limit=100)
|
||||
return render_template('print_module.html', orders=orders_data)
|
||||
except Exception as e:
|
||||
print(f"Error loading print module data: {e}")
|
||||
flash(f"Error loading orders: {e}", 'error')
|
||||
return render_template('print_module.html', orders=[])
|
||||
|
||||
@bp.route('/view_orders')
|
||||
def view_orders():
|
||||
"""View all orders in a table format"""
|
||||
try:
|
||||
# Get all orders data (not just unprinted)
|
||||
conn = get_db_connection()
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute("""
|
||||
SELECT id, comanda_productie, cod_articol, descr_com_prod, cantitate,
|
||||
com_achiz_client, nr_linie_com_client, customer_name,
|
||||
customer_article_number, open_for_order, line_number,
|
||||
created_at, updated_at, printed_labels, data_livrare, dimensiune
|
||||
FROM order_for_labels
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 500
|
||||
""")
|
||||
|
||||
orders_data = []
|
||||
for row in cursor.fetchall():
|
||||
orders_data.append({
|
||||
'id': row[0],
|
||||
'comanda_productie': row[1],
|
||||
'cod_articol': row[2],
|
||||
'descr_com_prod': row[3],
|
||||
'cantitate': row[4],
|
||||
'com_achiz_client': row[5],
|
||||
'nr_linie_com_client': row[6],
|
||||
'customer_name': row[7],
|
||||
'customer_article_number': row[8],
|
||||
'open_for_order': row[9],
|
||||
'line_number': row[10],
|
||||
'created_at': row[11],
|
||||
'updated_at': row[12],
|
||||
'printed_labels': row[13],
|
||||
'data_livrare': row[14] or '-',
|
||||
'dimensiune': row[15] or '-'
|
||||
})
|
||||
|
||||
conn.close()
|
||||
return render_template('view_orders.html', orders=orders_data)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error loading view orders data: {e}")
|
||||
flash(f"Error loading orders: {e}", 'error')
|
||||
return render_template('view_orders.html', orders=[])
|
||||
|
||||
|
||||
import secrets
|
||||
|
||||
@@ -23,6 +23,80 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const defectCodeInput = document.getElementById('defect_code');
|
||||
const form = document.getElementById('fg-scan-form');
|
||||
|
||||
// Restore saved operator code from localStorage (only Quality Operator Code)
|
||||
const savedOperatorCode = localStorage.getItem('fg_scan_operator_code');
|
||||
|
||||
if (savedOperatorCode) {
|
||||
operatorCodeInput.value = savedOperatorCode;
|
||||
}
|
||||
|
||||
// Check if we need to clear fields after a successful submission
|
||||
const shouldClearAfterSubmit = localStorage.getItem('fg_scan_clear_after_submit');
|
||||
if (shouldClearAfterSubmit === 'true') {
|
||||
// Clear the flag
|
||||
localStorage.removeItem('fg_scan_clear_after_submit');
|
||||
localStorage.removeItem('fg_scan_last_cp');
|
||||
localStorage.removeItem('fg_scan_last_defect');
|
||||
|
||||
// Clear CP code, OC1, OC2, and defect code for next scan
|
||||
cpCodeInput.value = '';
|
||||
oc1CodeInput.value = '';
|
||||
oc2CodeInput.value = '';
|
||||
defectCodeInput.value = '';
|
||||
|
||||
// Show success indicator
|
||||
setTimeout(function() {
|
||||
// Focus on CP code field for next scan
|
||||
cpCodeInput.focus();
|
||||
|
||||
// Add visual feedback
|
||||
const successIndicator = document.createElement('div');
|
||||
successIndicator.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
||||
font-weight: bold;
|
||||
`;
|
||||
successIndicator.textContent = '✅ Scan recorded! Ready for next scan';
|
||||
document.body.appendChild(successIndicator);
|
||||
|
||||
// Remove success indicator after 3 seconds
|
||||
setTimeout(function() {
|
||||
if (successIndicator.parentNode) {
|
||||
successIndicator.parentNode.removeChild(successIndicator);
|
||||
}
|
||||
}, 3000);
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Focus on the first empty required field (only if not clearing after submit)
|
||||
if (shouldClearAfterSubmit !== 'true') {
|
||||
if (!operatorCodeInput.value) {
|
||||
operatorCodeInput.focus();
|
||||
} else if (!oc1CodeInput.value) {
|
||||
oc1CodeInput.focus();
|
||||
} else if (!oc2CodeInput.value) {
|
||||
oc2CodeInput.focus();
|
||||
} else if (!cpCodeInput.value) {
|
||||
cpCodeInput.focus();
|
||||
} else {
|
||||
defectCodeInput.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// Save operator codes to localStorage when they change (only Quality Operator Code)
|
||||
operatorCodeInput.addEventListener('input', function() {
|
||||
if (this.value.startsWith('OP') && this.value.length >= 3) {
|
||||
localStorage.setItem('fg_scan_operator_code', this.value);
|
||||
}
|
||||
});
|
||||
|
||||
// Create error message element for operator code
|
||||
const operatorErrorMessage = document.createElement('div');
|
||||
operatorErrorMessage.className = 'error-message';
|
||||
@@ -338,6 +412,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||
timeInput.value = `${hours}:${minutes}:${seconds}`;
|
||||
|
||||
// Save current CP code and defect code to localStorage for clearing after reload
|
||||
localStorage.setItem('fg_scan_clear_after_submit', 'true');
|
||||
localStorage.setItem('fg_scan_last_cp', cpCodeInput.value);
|
||||
localStorage.setItem('fg_scan_last_defect', defectCodeInput.value);
|
||||
|
||||
// Submit the form
|
||||
form.submit();
|
||||
}
|
||||
@@ -399,6 +478,27 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add functionality for clear saved codes button
|
||||
const clearSavedBtn = document.getElementById('clear-saved-btn');
|
||||
clearSavedBtn.addEventListener('click', function() {
|
||||
if (confirm('Clear saved Quality Operator code? You will need to re-enter it.')) {
|
||||
// Clear localStorage (only Quality Operator Code)
|
||||
localStorage.removeItem('fg_scan_operator_code');
|
||||
localStorage.removeItem('fg_scan_clear_after_submit');
|
||||
localStorage.removeItem('fg_scan_last_cp');
|
||||
localStorage.removeItem('fg_scan_last_defect');
|
||||
|
||||
// Clear Quality Operator Code field only
|
||||
operatorCodeInput.value = '';
|
||||
|
||||
// Focus on operator code field
|
||||
operatorCodeInput.focus();
|
||||
|
||||
// Show confirmation
|
||||
alert('✅ Saved Quality Operator code cleared! Please re-enter your operator code.');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -430,6 +530,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
<input type="text" id="time" name="time" value="{{ now().strftime('%H:%M:%S') }}" readonly>
|
||||
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
<button type="button" class="btn" id="clear-saved-btn" style="background-color: #ff6b6b; margin-left: 10px;">Clear Quality Operator</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<p>Upload new orders or view existing orders and manage label data for printing.</p>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
<a href="{{ url_for('main.upload_data') }}" class="btn">Upload Orders</a>
|
||||
<a href="{{ url_for('main.get_unprinted_orders') }}" class="btn">View Orders</a>
|
||||
<a href="{{ url_for('main.view_orders') }}" class="btn">View Orders</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -10,8 +10,9 @@
|
||||
|
||||
/* Enhanced table styling */
|
||||
.card.scan-table-card table.print-module-table.scan-table thead th {
|
||||
border-bottom: 2px solid #dee2e6 !important;
|
||||
background-color: #f8f9fa !important;
|
||||
border-bottom: 2px solid var(--app-border-color, #dee2e6) !important;
|
||||
background-color: var(--app-table-header-bg, #2a3441) !important;
|
||||
color: var(--app-text-color, #ffffff) !important;
|
||||
padding: 0.25rem 0.4rem !important;
|
||||
text-align: left !important;
|
||||
font-weight: 600 !important;
|
||||
@@ -22,13 +23,21 @@
|
||||
.card.scan-table-card table.print-module-table.scan-table {
|
||||
width: 100% !important;
|
||||
border-collapse: collapse !important;
|
||||
background-color: var(--app-card-bg, #2a3441) !important;
|
||||
}
|
||||
|
||||
.card.scan-table-card table.print-module-table.scan-table tbody tr:hover td {
|
||||
background-color: #f8f9fa !important;
|
||||
background-color: var(--app-hover-bg, #3a4451) !important;
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
.card.scan-table-card table.print-module-table.scan-table tbody td {
|
||||
background-color: var(--app-card-bg, #2a3441) !important;
|
||||
color: var(--app-text-color, #ffffff) !important;
|
||||
border: 1px solid var(--app-border-color, #495057) !important;
|
||||
padding: 0.25rem 0.4rem !important;
|
||||
}
|
||||
|
||||
.card.scan-table-card table.print-module-table.scan-table tbody tr.selected td {
|
||||
background-color: #007bff !important;
|
||||
color: white !important;
|
||||
@@ -140,13 +149,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Barcode Frame - positioned 10px below rectangle, centered, 90% of label width -->
|
||||
<div id="barcode-frame" style="position: absolute; top: 395px; left: 50%; transform: translateX(-50%); width: 90%; max-width: 270px; height: 50px; background: white; display: flex; flex-direction: column; align-items: center; justify-content: center;">
|
||||
<!-- Barcode Frame - positioned 10px below rectangle, centered, constrained to label width -->
|
||||
<div id="barcode-frame" style="position: absolute; top: 395px; left: 50%; transform: translateX(-50%); width: 220px; max-width: 220px; height: 50px; background: white; display: flex; flex-direction: column; align-items: center; justify-content: center; overflow: hidden;">
|
||||
<!-- Code 128 Barcode representation -->
|
||||
<svg id="barcode-display" style="width: 100%; height: 40px;"></svg>
|
||||
<svg id="barcode-display" style="width: 100%; height: 40px; max-width: 220px;"></svg>
|
||||
|
||||
<!-- Barcode text below the bars -->
|
||||
<div id="barcode-text" style="font-size: 8px; font-family: 'Courier New', monospace; margin-top: 2px; text-align: center; font-weight: bold;">
|
||||
<!-- Barcode text below the bars (hidden in preview) -->
|
||||
<div id="barcode-text" style="font-size: 8px; font-family: 'Courier New', monospace; margin-top: 2px; text-align: center; font-weight: bold; display: none;">
|
||||
<!-- Barcode text will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
@@ -156,8 +165,8 @@
|
||||
<!-- Vertical Code 128 Barcode representation -->
|
||||
<svg id="vertical-barcode-display" style="width: 100%; height: 35px;"></svg>
|
||||
|
||||
<!-- Vertical barcode text -->
|
||||
<div id="vertical-barcode-text" style="position: absolute; bottom: -15px; font-size: 7px; font-family: 'Courier New', monospace; text-align: center; font-weight: bold; width: 100%;">
|
||||
<!-- Vertical barcode text (hidden in preview) -->
|
||||
<div id="vertical-barcode-text" style="position: absolute; bottom: -15px; font-size: 7px; font-family: 'Courier New', monospace; text-align: center; font-weight: bold; width: 100%; display: none;">
|
||||
<!-- Vertical barcode text will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
@@ -475,10 +484,12 @@ function updateLabelPreview(order) {
|
||||
|
||||
JsBarcode("#barcode-display", horizontalBarcodeData, {
|
||||
format: "CODE128",
|
||||
width: 2,
|
||||
width: 1.2,
|
||||
height: 40,
|
||||
displayValue: false,
|
||||
margin: 2
|
||||
margin: 0,
|
||||
fontSize: 0,
|
||||
textMargin: 0
|
||||
});
|
||||
console.log('✅ Horizontal barcode generated successfully');
|
||||
} catch (e) {
|
||||
|
||||
@@ -22,6 +22,74 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const defectCodeInput = document.getElementById('defect_code');
|
||||
const form = document.querySelector('.form-centered');
|
||||
|
||||
// Load saved operator codes from localStorage (only Quality Operator Code)
|
||||
function loadSavedCodes() {
|
||||
const savedOperatorCode = localStorage.getItem('scan_operator_code');
|
||||
|
||||
if (savedOperatorCode) {
|
||||
operatorCodeInput.value = savedOperatorCode;
|
||||
}
|
||||
}
|
||||
|
||||
// Save operator codes to localStorage (only Quality Operator Code)
|
||||
function saveCodes() {
|
||||
if (operatorCodeInput.value.startsWith('OP')) {
|
||||
localStorage.setItem('scan_operator_code', operatorCodeInput.value);
|
||||
}
|
||||
}
|
||||
|
||||
// Clear saved codes from localStorage (only Quality Operator Code)
|
||||
function clearSavedCodes() {
|
||||
localStorage.removeItem('scan_operator_code');
|
||||
operatorCodeInput.value = '';
|
||||
showSuccessMessage('Quality Operator code cleared!');
|
||||
operatorCodeInput.focus();
|
||||
}
|
||||
|
||||
// Show success message
|
||||
function showSuccessMessage(message) {
|
||||
const successDiv = document.createElement('div');
|
||||
successDiv.className = 'success-message';
|
||||
successDiv.textContent = message;
|
||||
successDiv.style.cssText = `
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: #4CAF50;
|
||||
color: white;
|
||||
padding: 15px 20px;
|
||||
border-radius: 4px;
|
||||
z-index: 1000;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||
`;
|
||||
document.body.appendChild(successDiv);
|
||||
|
||||
setTimeout(() => {
|
||||
if (document.body.contains(successDiv)) {
|
||||
document.body.removeChild(successDiv);
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// Load saved codes on page load
|
||||
loadSavedCodes();
|
||||
|
||||
// Focus on the first empty field
|
||||
setTimeout(() => {
|
||||
if (!operatorCodeInput.value) {
|
||||
operatorCodeInput.focus();
|
||||
} else if (!cpCodeInput.value) {
|
||||
cpCodeInput.focus();
|
||||
} else if (!oc1CodeInput.value) {
|
||||
oc1CodeInput.focus();
|
||||
} else if (!oc2CodeInput.value) {
|
||||
oc2CodeInput.focus();
|
||||
} else {
|
||||
defectCodeInput.focus();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// Create error message element for operator code
|
||||
const operatorErrorMessage = document.createElement('div');
|
||||
operatorErrorMessage.className = 'error-message';
|
||||
@@ -333,8 +401,21 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||
timeInput.value = `${hours}:${minutes}:${seconds}`;
|
||||
|
||||
// Save operator codes before submitting
|
||||
saveCodes();
|
||||
|
||||
// Submit the form
|
||||
form.submit();
|
||||
|
||||
// Clear CP, OC1, OC2, and defect code fields after successful submission
|
||||
setTimeout(() => {
|
||||
cpCodeInput.value = '';
|
||||
oc1CodeInput.value = '';
|
||||
oc2CodeInput.value = '';
|
||||
defectCodeInput.value = '';
|
||||
showSuccessMessage('Scan submitted successfully!');
|
||||
cpCodeInput.focus();
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -425,6 +506,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
<input type="text" id="time" name="time" value="{{ now().strftime('%H:%M:%S') }}" readonly>
|
||||
|
||||
<button type="submit" class="btn">Submit</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="clearSavedCodes()" style="margin-top: 10px; background-color: #6c757d;">Clear Quality Operator</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -82,15 +82,35 @@ table.view-orders-table.scan-table tbody tr:hover td {
|
||||
<h3>Upload Order Data for Labels</h3>
|
||||
{% endif %}
|
||||
<form method="POST" enctype="multipart/form-data" class="form-centered" id="csv-upload-form">
|
||||
<label for="csv_file">Choose CSV file:</label>
|
||||
{% if leftover_description %}
|
||||
<button type="submit" class="btn btn-danger" name="clear_table" value="1">Clear Table</button>
|
||||
{% elif not orders %}
|
||||
<input type="file" name="csv_file" accept=".csv" required><br>
|
||||
<button type="submit" class="btn">Upload & Review</button>
|
||||
{% if show_preview %}
|
||||
<!-- Show preview controls -->
|
||||
<input type="hidden" name="action" value="save">
|
||||
<label style="font-weight: bold;">Preview of: {{ filename }}</label><br>
|
||||
<p style="color: #666; font-size: 14px; margin: 10px 0;">
|
||||
Showing first 10 rows. Review the data below and click "Save to Database" to confirm.
|
||||
</p>
|
||||
<button type="submit" class="btn" style="background-color: #28a745;">Save to Database</button>
|
||||
<a href="{{ url_for('main.upload_data') }}" class="btn" style="background-color: #6c757d; margin-left: 10px;">Cancel</a>
|
||||
{% else %}
|
||||
<label style="font-weight: bold;">Selected file: {{ session['csv_filename'] if session['csv_filename'] else 'Unknown' }}</label><br>
|
||||
<button type="button" class="btn" onclick="showPopupAndSubmit()">Upload to Database</button>
|
||||
<!-- Show file upload -->
|
||||
<input type="hidden" name="action" value="preview">
|
||||
<label for="file">Choose CSV file:</label>
|
||||
<input type="file" name="file" accept=".csv" required><br>
|
||||
<button type="submit" class="btn">Upload & Preview</button>
|
||||
|
||||
<!-- CSV Format Information -->
|
||||
<div style="margin-top: 20px; padding: 15px; background-color: var(--app-card-bg, #2a3441); border-radius: 5px; border-left: 4px solid var(--app-accent-color, #007bff); color: var(--app-text-color, #ffffff);">
|
||||
<h5 style="margin-top: 0; color: var(--app-accent-color, #007bff);">Expected CSV Format</h5>
|
||||
<p style="margin-bottom: 10px; color: var(--app-text-color, #ffffff);">Your CSV file should contain columns such as:</p>
|
||||
<ul style="margin-bottom: 10px; color: var(--app-text-color, #ffffff);">
|
||||
<li><strong>order_number</strong> - The order/production number</li>
|
||||
<li><strong>quantity</strong> - Number of items</li>
|
||||
<li><strong>warehouse_location</strong> - Storage location</li>
|
||||
</ul>
|
||||
<p style="color: var(--app-secondary-text, #b8c5d1); font-size: 14px; margin-bottom: 0;">
|
||||
Column names are case-insensitive and can have variations like "Order Number", "Quantity", "Location", etc.
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
|
||||
@@ -124,108 +144,45 @@ table.view-orders-table.scan-table tbody tr:hover td {
|
||||
</div>
|
||||
|
||||
<!-- Preview Table Card (expandable height, scrollable) -->
|
||||
<div class="card scan-table-card{% if leftover_description %} leftover-table-card{% endif %}" style="margin-bottom: 24px; max-height: 480px; overflow-y: auto;">
|
||||
{% if leftover_description %}
|
||||
<h3>Left over orders</h3>
|
||||
<div class="card scan-table-card" style="margin-bottom: 24px; max-height: 480px; overflow-y: auto;">
|
||||
{% if show_preview %}
|
||||
<h3>CSV Data Preview - {{ filename }}</h3>
|
||||
<table class="scan-table">
|
||||
<thead>
|
||||
<tr>
|
||||
{% for header in headers %}
|
||||
<th>{{ header }}</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if preview_data %}
|
||||
{% for row in preview_data %}
|
||||
<tr>
|
||||
{% for header in headers %}
|
||||
<td>{{ row.get(header, '') }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr><td colspan="{{ headers|length }}" style="text-align:center;">No data to preview</td></tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<h3>Preview Table</h3>
|
||||
<h3>CSV Data Preview</h3>
|
||||
<table class="scan-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Upload a CSV file to see preview</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td style="text-align:center; padding: 40px;">No CSV file uploaded yet. Use the form above to upload and preview your data.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
<table class="scan-table view-orders-table{% if leftover_description %} leftover-table{% endif %}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Comanda<br>Productie</th>
|
||||
<th>Cod<br>Articol</th>
|
||||
<th>Descr. Com.<br>Prod</th>
|
||||
<th>Cantitate</th>
|
||||
<th>Data<br>Livrare</th>
|
||||
<th>Dimensiune</th>
|
||||
<th>Com.Achiz.<br>Client</th>
|
||||
<th>Nr.<br>Linie</th>
|
||||
<th>Customer<br>Name</th>
|
||||
<th>Customer<br>Art. Nr.</th>
|
||||
<th>Open<br>Order</th>
|
||||
<th>Line</th>
|
||||
<th>Printed</th>
|
||||
<th>Created</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if orders %}
|
||||
{% for order in orders %}
|
||||
{% if order and (order.get('comanda_productie', '') or order.get('descr_com_prod', '')) %}
|
||||
<tr>
|
||||
<td>{{ order.get('id', '') }}</td>
|
||||
<td><strong>{{ order.get('comanda_productie', '') }}</strong></td>
|
||||
<td>{{ order.get('cod_articol', '-') }}</td>
|
||||
<td>{{ order.get('descr_com_prod', '') }}</td>
|
||||
<td style="text-align: right; font-weight: 600;">{{ order.get('cantitate', '') }}</td>
|
||||
<td style="text-align: center;">{{ order.get('data_livrare', '') }}</td>
|
||||
<td style="text-align: center;">{{ order.get('dimensiune', '-') }}</td>
|
||||
<td>{{ order.get('com_achiz_client', '-') }}</td>
|
||||
<td style="text-align: right;">{{ order.get('nr_linie_com_client', '-') }}</td>
|
||||
<td>{{ order.get('customer_name', '-') }}</td>
|
||||
<td>{{ order.get('customer_article_number', '-') }}</td>
|
||||
<td>{{ order.get('open_for_order', '-') }}</td>
|
||||
<td style="text-align: right;">{{ order.get('line_number', '-') }}</td>
|
||||
<td style="text-align: center;">
|
||||
{% if order.get('printed_labels', 0) == 1 %}
|
||||
<span style="color: #28a745; font-weight: bold;">✓ Yes</span>
|
||||
{% else %}
|
||||
<span style="color: #dc3545;">✗ No</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="font-size: 11px; color: #6c757d;">{{ order.get('created_at', '-') }}</td>
|
||||
</tr>
|
||||
{% if order.error_message %}
|
||||
<tr>
|
||||
<td colspan="15" style="color: #dc3545; font-size: 12px; background: #fff3f3;">
|
||||
<strong>Error:</strong> {{ order.error_message }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr><td colspan="15" style="text-align:center;">No CSV file uploaded yet.</td></tr>
|
||||
{% endif %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if validation_errors or validation_warnings %}
|
||||
{% if not leftover_description %}
|
||||
<div class="card" style="margin-bottom: 24px;">
|
||||
<h4>Validation Results</h4>
|
||||
{% if validation_errors %}
|
||||
<div style="color: #dc3545; margin-bottom: 16px;">
|
||||
<strong>Errors found:</strong>
|
||||
<ul>
|
||||
{% for error in validation_errors %}
|
||||
<li>{{ error }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if validation_warnings %}
|
||||
<div style="color: #ffc107;">
|
||||
<strong>Warnings:</strong>
|
||||
<ul>
|
||||
{% for warning in validation_warnings %}
|
||||
<li>{{ warning }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if report %}
|
||||
<div class="card" style="margin-bottom: 24px;">
|
||||
<h4>Import Report</h4>
|
||||
<p>{{ report }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -25,13 +25,14 @@ table.view-orders-table.scan-table thead th {
|
||||
line-height: 1.3 !important;
|
||||
padding: 6px 3px !important;
|
||||
font-size: 11px !important;
|
||||
background-color: #e9ecef !important;
|
||||
background-color: var(--header-bg-color) !important;
|
||||
color: var(--header-text-color) !important;
|
||||
font-weight: bold !important;
|
||||
text-transform: none !important;
|
||||
letter-spacing: 0 !important;
|
||||
overflow: visible !important;
|
||||
box-sizing: border-box !important;
|
||||
border: 1px solid #ddd !important;
|
||||
border: 1px solid var(--border-color) !important;
|
||||
text-overflow: clip !important;
|
||||
position: relative !important;
|
||||
}
|
||||
@@ -41,7 +42,9 @@ table.view-orders-table.scan-table tbody td {
|
||||
padding: 4px 2px !important;
|
||||
font-size: 10px !important;
|
||||
text-align: center !important;
|
||||
border: 1px solid #ddd !important;
|
||||
border: 1px solid var(--border-color) !important;
|
||||
background-color: var(--card-bg-color) !important;
|
||||
color: var(--text-color) !important;
|
||||
white-space: nowrap !important;
|
||||
overflow: hidden !important;
|
||||
text-overflow: ellipsis !important;
|
||||
@@ -68,7 +71,7 @@ table.view-orders-table.scan-table tbody td {
|
||||
|
||||
/* HOVER EFFECTS */
|
||||
table.view-orders-table.scan-table tbody tr:hover td {
|
||||
background-color: #f8f9fa !important;
|
||||
background-color: var(--hover-color) !important;
|
||||
}
|
||||
|
||||
/* COLUMN WIDTH SPECIFICATIONS */
|
||||
|
||||
72
py_app/gunicorn.conf.py
Normal file
72
py_app/gunicorn.conf.py
Normal file
@@ -0,0 +1,72 @@
|
||||
# Gunicorn Configuration File for Trasabilitate Application
|
||||
# Production-ready WSGI server configuration
|
||||
|
||||
import multiprocessing
|
||||
import os
|
||||
|
||||
# Server socket
|
||||
bind = "0.0.0.0:8781"
|
||||
backlog = 2048
|
||||
|
||||
# Worker processes
|
||||
workers = multiprocessing.cpu_count() * 2 + 1
|
||||
worker_class = "sync"
|
||||
worker_connections = 1000
|
||||
timeout = 30
|
||||
keepalive = 2
|
||||
|
||||
# Restart workers after this many requests, to prevent memory leaks
|
||||
max_requests = 1000
|
||||
max_requests_jitter = 50
|
||||
|
||||
# Logging
|
||||
accesslog = "/srv/quality_recticel/logs/access.log"
|
||||
errorlog = "/srv/quality_recticel/logs/error.log"
|
||||
loglevel = "info"
|
||||
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'
|
||||
|
||||
# Process naming
|
||||
proc_name = 'trasabilitate_app'
|
||||
|
||||
# Daemon mode (set to True for production deployment)
|
||||
daemon = False
|
||||
|
||||
# User/group to run worker processes
|
||||
# user = "www-data"
|
||||
# group = "www-data"
|
||||
|
||||
# Preload application for better performance
|
||||
preload_app = True
|
||||
|
||||
# Enable automatic worker restarts
|
||||
max_requests = 1000
|
||||
max_requests_jitter = 100
|
||||
|
||||
# SSL Configuration (uncomment if using HTTPS)
|
||||
# keyfile = "/path/to/ssl/private.key"
|
||||
# certfile = "/path/to/ssl/certificate.crt"
|
||||
|
||||
# Security
|
||||
limit_request_line = 4094
|
||||
limit_request_fields = 100
|
||||
limit_request_field_size = 8190
|
||||
|
||||
def when_ready(server):
|
||||
"""Called just after the server is started."""
|
||||
server.log.info("Trasabilitate Application server is ready. Listening on: %s", server.address)
|
||||
|
||||
def worker_int(worker):
|
||||
"""Called just after a worker exited on SIGINT or SIGQUIT."""
|
||||
worker.log.info("Worker received INT or QUIT signal")
|
||||
|
||||
def pre_fork(server, worker):
|
||||
"""Called just before a worker is forked."""
|
||||
server.log.info("Worker spawned (pid: %s)", worker.pid)
|
||||
|
||||
def post_fork(server, worker):
|
||||
"""Called just after a worker has been forked."""
|
||||
server.log.info("Worker spawned (pid: %s)", worker.pid)
|
||||
|
||||
def worker_abort(worker):
|
||||
"""Called when a worker received the SIGABRT signal."""
|
||||
worker.log.info("Worker received SIGABRT signal")
|
||||
Binary file not shown.
114
py_app/manage_production.sh
Executable file
114
py_app/manage_production.sh
Executable file
@@ -0,0 +1,114 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Production Management Script for Trasabilitate Application
|
||||
# Usage: ./manage_production.sh {start|stop|restart|status|logs|install-service}
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}✅ $1${NC}"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}❌ $1${NC}"
|
||||
}
|
||||
|
||||
print_info() {
|
||||
echo -e "${BLUE}ℹ️ $1${NC}"
|
||||
}
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 {start|stop|restart|status|logs|install-service|help}"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " start Start the production server"
|
||||
echo " stop Stop the production server"
|
||||
echo " restart Restart the production server"
|
||||
echo " status Show server status"
|
||||
echo " logs Show recent logs"
|
||||
echo " install-service Install systemd service"
|
||||
echo " help Show this help message"
|
||||
echo ""
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
./start_production.sh
|
||||
;;
|
||||
stop)
|
||||
./stop_production.sh
|
||||
;;
|
||||
restart)
|
||||
echo -e "${BLUE}🔄 Restarting Trasabilitate Application${NC}"
|
||||
echo "=============================================="
|
||||
./stop_production.sh
|
||||
sleep 2
|
||||
./start_production.sh
|
||||
;;
|
||||
status)
|
||||
./status_production.sh
|
||||
;;
|
||||
logs)
|
||||
echo -e "${BLUE}📋 Recent Error Logs${NC}"
|
||||
echo "=============================================="
|
||||
if [[ -f "/srv/quality_recticel/logs/error.log" ]]; then
|
||||
tail -20 /srv/quality_recticel/logs/error.log
|
||||
else
|
||||
print_error "Error log file not found"
|
||||
fi
|
||||
echo ""
|
||||
echo -e "${BLUE}📋 Recent Access Logs${NC}"
|
||||
echo "=============================================="
|
||||
if [[ -f "/srv/quality_recticel/logs/access.log" ]]; then
|
||||
tail -10 /srv/quality_recticel/logs/access.log
|
||||
else
|
||||
print_error "Access log file not found"
|
||||
fi
|
||||
;;
|
||||
install-service)
|
||||
echo -e "${BLUE}📦 Installing Systemd Service${NC}"
|
||||
echo "=============================================="
|
||||
|
||||
if [[ ! -f "trasabilitate.service" ]]; then
|
||||
print_error "Service file not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_info "Installing service file..."
|
||||
sudo cp trasabilitate.service /etc/systemd/system/
|
||||
|
||||
print_info "Reloading systemd daemon..."
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
print_info "Enabling service..."
|
||||
sudo systemctl enable trasabilitate.service
|
||||
|
||||
print_success "Service installed successfully!"
|
||||
echo ""
|
||||
echo "Systemd commands:"
|
||||
echo " sudo systemctl start trasabilitate # Start service"
|
||||
echo " sudo systemctl stop trasabilitate # Stop service"
|
||||
echo " sudo systemctl restart trasabilitate # Restart service"
|
||||
echo " sudo systemctl status trasabilitate # Check status"
|
||||
echo " sudo systemctl enable trasabilitate # Enable auto-start"
|
||||
echo " sudo systemctl disable trasabilitate # Disable auto-start"
|
||||
;;
|
||||
help|--help|-h)
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
print_error "Invalid command: $1"
|
||||
echo ""
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -6,4 +6,6 @@ flask-sqlalchemy
|
||||
pyodbc
|
||||
mariadb
|
||||
reportlab
|
||||
requests
|
||||
requests
|
||||
pandas
|
||||
openpyxl
|
||||
163
py_app/start_production.sh
Executable file
163
py_app/start_production.sh
Executable file
@@ -0,0 +1,163 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Production Startup Script for Trasabilitate Application
|
||||
# This script starts the application using Gunicorn WSGI server
|
||||
|
||||
set -e # Exit on any error
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
print_step() {
|
||||
echo -e "\n${BLUE}📋 $1${NC}"
|
||||
echo "----------------------------------------"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}✅ $1${NC}"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}❌ $1${NC}"
|
||||
}
|
||||
|
||||
echo -e "${BLUE}🚀 Trasabilitate Application - Production Startup${NC}"
|
||||
echo "=============================================="
|
||||
|
||||
# Check if we're in the right directory
|
||||
if [[ ! -f "wsgi.py" ]]; then
|
||||
print_error "Please run this script from the py_app directory"
|
||||
print_error "Expected location: /srv/quality_recticel/py_app"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_step "Checking Prerequisites"
|
||||
|
||||
# Check if virtual environment exists
|
||||
if [[ ! -d "../recticel" ]]; then
|
||||
print_error "Virtual environment 'recticel' not found"
|
||||
print_error "Please create it first or run './quick_deploy.sh'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_success "Virtual environment found"
|
||||
|
||||
# Activate virtual environment
|
||||
print_step "Activating Virtual Environment"
|
||||
source ../recticel/bin/activate
|
||||
print_success "Virtual environment activated"
|
||||
|
||||
# Check if Gunicorn is installed
|
||||
if ! command -v gunicorn &> /dev/null; then
|
||||
print_error "Gunicorn not found. Installing..."
|
||||
pip install gunicorn
|
||||
fi
|
||||
|
||||
print_success "Gunicorn is available"
|
||||
|
||||
# Check database connection
|
||||
print_step "Testing Database Connection"
|
||||
if python3 -c "
|
||||
import mariadb
|
||||
try:
|
||||
conn = mariadb.connect(user='trasabilitate', password='Initial01!', host='localhost', database='trasabilitate')
|
||||
conn.close()
|
||||
print('Database connection successful')
|
||||
except Exception as e:
|
||||
print(f'Database connection failed: {e}')
|
||||
exit(1)
|
||||
" > /dev/null 2>&1; then
|
||||
print_success "Database connection verified"
|
||||
else
|
||||
print_error "Database connection failed. Please run database setup first:"
|
||||
print_error "python3 app/db_create_scripts/setup_complete_database.py"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create PID file directory
|
||||
print_step "Setting up Runtime Environment"
|
||||
mkdir -p ../run
|
||||
print_success "Runtime directory created"
|
||||
|
||||
# Check if already running
|
||||
PID_FILE="../run/trasabilitate.pid"
|
||||
if [[ -f "$PID_FILE" ]]; then
|
||||
PID=$(cat "$PID_FILE")
|
||||
if ps -p "$PID" > /dev/null 2>&1; then
|
||||
print_warning "Application is already running (PID: $PID)"
|
||||
echo "To stop the application, run:"
|
||||
echo "kill $PID"
|
||||
echo "rm $PID_FILE"
|
||||
exit 1
|
||||
else
|
||||
print_warning "Stale PID file found, removing..."
|
||||
rm -f "$PID_FILE"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Start Gunicorn
|
||||
print_step "Starting Production Server"
|
||||
|
||||
echo "Starting Gunicorn WSGI server..."
|
||||
echo "Configuration: gunicorn.conf.py"
|
||||
echo "Workers: $(python3 -c 'import multiprocessing; print(multiprocessing.cpu_count() * 2 + 1)')"
|
||||
echo "Binding to: 0.0.0.0:8781"
|
||||
echo ""
|
||||
|
||||
# Start Gunicorn with configuration file
|
||||
gunicorn --config gunicorn.conf.py \
|
||||
--pid "$PID_FILE" \
|
||||
--daemon \
|
||||
wsgi:application
|
||||
|
||||
# Wait a moment for startup
|
||||
sleep 2
|
||||
|
||||
# Check if the process started successfully
|
||||
if [[ -f "$PID_FILE" ]]; then
|
||||
PID=$(cat "$PID_FILE")
|
||||
if ps -p "$PID" > /dev/null 2>&1; then
|
||||
print_success "Application started successfully!"
|
||||
echo ""
|
||||
echo "=============================================="
|
||||
echo -e "${GREEN}🎉 PRODUCTION SERVER RUNNING${NC}"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
echo "📋 Server Information:"
|
||||
echo " • Process ID: $PID"
|
||||
echo " • Configuration: gunicorn.conf.py"
|
||||
echo " • Access Log: /srv/quality_recticel/logs/access.log"
|
||||
echo " • Error Log: /srv/quality_recticel/logs/error.log"
|
||||
echo ""
|
||||
echo "🌐 Application URLs:"
|
||||
echo " • Local: http://127.0.0.1:8781"
|
||||
echo " • Network: http://$(hostname -I | awk '{print $1}'):8781"
|
||||
echo ""
|
||||
echo "👤 Default Login:"
|
||||
echo " • Username: superadmin"
|
||||
echo " • Password: superadmin123"
|
||||
echo ""
|
||||
echo "🔧 Management Commands:"
|
||||
echo " • Stop server: kill $PID && rm $PID_FILE"
|
||||
echo " • View logs: tail -f /srv/quality_recticel/logs/error.log"
|
||||
echo " • Monitor access: tail -f /srv/quality_recticel/logs/access.log"
|
||||
echo " • Server status: ps -p $PID"
|
||||
echo ""
|
||||
print_warning "Server is running in daemon mode (background)"
|
||||
else
|
||||
print_error "Failed to start application. Check logs:"
|
||||
print_error "tail /srv/quality_recticel/logs/error.log"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
print_error "Failed to create PID file. Check permissions and logs."
|
||||
exit 1
|
||||
fi
|
||||
78
py_app/status_production.sh
Executable file
78
py_app/status_production.sh
Executable file
@@ -0,0 +1,78 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Production Status Script for Trasabilitate Application
|
||||
# This script shows the current status of the Gunicorn WSGI server
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}✅ $1${NC}"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}❌ $1${NC}"
|
||||
}
|
||||
|
||||
echo -e "${BLUE}📊 Trasabilitate Application - Status Check${NC}"
|
||||
echo "=============================================="
|
||||
|
||||
PID_FILE="../run/trasabilitate.pid"
|
||||
|
||||
if [[ ! -f "$PID_FILE" ]]; then
|
||||
print_error "Application is not running (no PID file found)"
|
||||
echo "To start the application, run: ./start_production.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PID=$(cat "$PID_FILE")
|
||||
|
||||
if ps -p "$PID" > /dev/null 2>&1; then
|
||||
print_success "Application is running (PID: $PID)"
|
||||
echo ""
|
||||
echo "📋 Process Information:"
|
||||
ps -p "$PID" -o pid,ppid,pcpu,pmem,etime,cmd --no-headers | while read line; do
|
||||
echo " $line"
|
||||
done
|
||||
echo ""
|
||||
echo "🌐 Server Information:"
|
||||
echo " • Listening on: 0.0.0.0:8781"
|
||||
echo " • Local URL: http://127.0.0.1:8781"
|
||||
echo " • Network URL: http://$(hostname -I | awk '{print $1}'):8781"
|
||||
echo ""
|
||||
echo "📁 Log Files:"
|
||||
echo " • Access Log: /srv/quality_recticel/logs/access.log"
|
||||
echo " • Error Log: /srv/quality_recticel/logs/error.log"
|
||||
echo ""
|
||||
echo "🔧 Quick Commands:"
|
||||
echo " • Stop server: ./stop_production.sh"
|
||||
echo " • Restart server: ./stop_production.sh && ./start_production.sh"
|
||||
echo " • View error log: tail -f /srv/quality_recticel/logs/error.log"
|
||||
echo " • View access log: tail -f /srv/quality_recticel/logs/access.log"
|
||||
echo ""
|
||||
|
||||
# Check if the web server is responding
|
||||
if command -v curl > /dev/null 2>&1; then
|
||||
echo "🌐 Connection Test:"
|
||||
if curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8781 | grep -q "200\|302\|401"; then
|
||||
print_success "Web server is responding"
|
||||
else
|
||||
print_warning "Web server may not be responding properly"
|
||||
fi
|
||||
fi
|
||||
|
||||
else
|
||||
print_error "Process $PID not found (stale PID file)"
|
||||
print_warning "Cleaning up stale PID file..."
|
||||
rm -f "$PID_FILE"
|
||||
echo "To start the application, run: ./start_production.sh"
|
||||
exit 1
|
||||
fi
|
||||
69
py_app/stop_production.sh
Executable file
69
py_app/stop_production.sh
Executable file
@@ -0,0 +1,69 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Production Stop Script for Trasabilitate Application
|
||||
# This script stops the Gunicorn WSGI server
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}✅ $1${NC}"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}❌ $1${NC}"
|
||||
}
|
||||
|
||||
echo -e "${BLUE}🛑 Trasabilitate Application - Production Stop${NC}"
|
||||
echo "=============================================="
|
||||
|
||||
PID_FILE="../run/trasabilitate.pid"
|
||||
|
||||
if [[ ! -f "$PID_FILE" ]]; then
|
||||
print_warning "No PID file found. Server may not be running."
|
||||
echo "PID file location: $PID_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PID=$(cat "$PID_FILE")
|
||||
|
||||
if ps -p "$PID" > /dev/null 2>&1; then
|
||||
echo "Stopping Trasabilitate application (PID: $PID)..."
|
||||
|
||||
# Send SIGTERM first (graceful shutdown)
|
||||
kill "$PID"
|
||||
|
||||
# Wait for graceful shutdown
|
||||
sleep 3
|
||||
|
||||
# Check if still running
|
||||
if ps -p "$PID" > /dev/null 2>&1; then
|
||||
print_warning "Process still running, sending SIGKILL..."
|
||||
kill -9 "$PID"
|
||||
sleep 1
|
||||
fi
|
||||
|
||||
# Check if process is finally stopped
|
||||
if ! ps -p "$PID" > /dev/null 2>&1; then
|
||||
print_success "Application stopped successfully"
|
||||
rm -f "$PID_FILE"
|
||||
else
|
||||
print_error "Failed to stop application (PID: $PID)"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
print_warning "Process $PID not found. Cleaning up PID file..."
|
||||
rm -f "$PID_FILE"
|
||||
print_success "PID file cleaned up"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
print_success "Trasabilitate application has been stopped"
|
||||
27
py_app/trasabilitate.service
Normal file
27
py_app/trasabilitate.service
Normal file
@@ -0,0 +1,27 @@
|
||||
[Unit]
|
||||
Description=Trasabilitate Quality Management Application
|
||||
After=network.target mariadb.service
|
||||
Wants=mariadb.service
|
||||
|
||||
[Service]
|
||||
Type=forking
|
||||
User=ske087
|
||||
Group=ske087
|
||||
WorkingDirectory=/srv/quality_recticel/py_app
|
||||
Environment="PATH=/srv/quality_recticel/recticel/bin"
|
||||
ExecStart=/srv/quality_recticel/recticel/bin/gunicorn --config gunicorn.conf.py --pid /srv/quality_recticel/run/trasabilitate.pid --daemon wsgi:application
|
||||
ExecReload=/bin/kill -s HUP $MAINPID
|
||||
ExecStop=/bin/kill -s TERM $MAINPID
|
||||
PIDFile=/srv/quality_recticel/run/trasabilitate.pid
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
# Security settings
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ReadWritePaths=/srv/quality_recticel
|
||||
ProtectHome=true
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
19
py_app/wsgi.py
Executable file
19
py_app/wsgi.py
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
WSGI Entry Point for Trasabilitate Application
|
||||
This file serves as the entry point for WSGI servers like Gunicorn.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from app import create_app
|
||||
|
||||
# Add the current directory to Python path
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
|
||||
# Create the Flask application instance
|
||||
application = create_app()
|
||||
|
||||
# For debugging purposes (will be ignored in production)
|
||||
if __name__ == "__main__":
|
||||
application.run(debug=False, host='0.0.0.0', port=8781)
|
||||
Reference in New Issue
Block a user