Major updates: Setup environment, database, permissions and admin access
- Created virtual environment and installed requirements - Set up MariaDB database with user 'sa' and database 'recticel' - Created order_for_labels table for printing functionality - Implemented role-based permissions system with admin role - Added admin access to print label modules and etichete functionality - Fixed template routing issues in main_page_etichete.html - Updated app to run on port 8781 with network access (0.0.0.0) - Added systemd service file for background deployment - Created installation documentation
This commit is contained in:
25
app.log
Normal file
25
app.log
Normal file
@@ -0,0 +1,25 @@
|
||||
nohup: ignoring input
|
||||
* Serving Flask app 'app'
|
||||
* Debug mode: on
|
||||
[31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
|
||||
* Running on all addresses (0.0.0.0)
|
||||
* Running on http://127.0.0.1:8781
|
||||
* Running on http://192.168.0.132:8781
|
||||
[33mPress CTRL+C to quit[0m
|
||||
* Restarting with stat
|
||||
* Debugger is active!
|
||||
* Debugger PIN: 869-443-054
|
||||
172.18.0.2 - - [07/Oct/2025 22:03:14] "GET /quality HTTP/1.1" 200 -
|
||||
172.18.0.2 - - [07/Oct/2025 22:03:19] "[32mGET /logout HTTP/1.1[0m" 302 -
|
||||
172.18.0.2 - - [07/Oct/2025 22:03:19] "GET / HTTP/1.1" 200 -
|
||||
172.18.0.2 - - [07/Oct/2025 22:03:19] "[36mGET /static/logo_login.jpg HTTP/1.1[0m" 304 -
|
||||
172.18.0.2 - - [07/Oct/2025 22:03:24] "[32mPOST / HTTP/1.1[0m" 302 -
|
||||
172.18.0.2 - - [07/Oct/2025 22:03:24] "GET /dashboard HTTP/1.1" 200 -
|
||||
172.18.0.2 - - [07/Oct/2025 22:03:30] "[32mGET /logout HTTP/1.1[0m" 302 -
|
||||
172.18.0.2 - - [07/Oct/2025 22:03:30] "GET / HTTP/1.1" 200 -
|
||||
172.18.0.2 - - [07/Oct/2025 22:03:34] "[32mPOST / HTTP/1.1[0m" 302 -
|
||||
172.18.0.2 - - [07/Oct/2025 22:03:34] "GET /dashboard HTTP/1.1" 200 -
|
||||
172.18.0.2 - - [07/Oct/2025 22:03:38] "[32mGET /logout HTTP/1.1[0m" 302 -
|
||||
172.18.0.2 - - [07/Oct/2025 22:03:38] "GET / HTTP/1.1" 200 -
|
||||
172.18.0.2 - - [07/Oct/2025 22:03:56] "[32mPOST / HTTP/1.1[0m" 302 -
|
||||
172.18.0.2 - - [07/Oct/2025 22:03:56] "GET /dashboard HTTP/1.1" 200 -
|
||||
BIN
py_app/app/__pycache__/__init__.cpython-311.pyc
Executable file → Normal file
BIN
py_app/app/__pycache__/__init__.cpython-311.pyc
Executable file → Normal file
Binary file not shown.
BIN
py_app/app/__pycache__/models.cpython-311.pyc
Executable file → Normal file
BIN
py_app/app/__pycache__/models.cpython-311.pyc
Executable file → Normal file
Binary file not shown.
BIN
py_app/app/__pycache__/routes.cpython-311.pyc
Executable file → Normal file
BIN
py_app/app/__pycache__/routes.cpython-311.pyc
Executable file → Normal file
Binary file not shown.
@@ -2,10 +2,10 @@ import mariadb
|
||||
|
||||
# Database connection credentials
|
||||
DB_CONFIG = {
|
||||
"user": "trasabilitate",
|
||||
"password": "Initial01!",
|
||||
"user": "sa",
|
||||
"password": "12345678",
|
||||
"host": "localhost",
|
||||
"database": "trasabilitate_database"
|
||||
"database": "recticel"
|
||||
}
|
||||
|
||||
def recreate_order_for_labels_table():
|
||||
|
||||
@@ -27,84 +27,8 @@ from .print_module import get_unprinted_orders_data
|
||||
bp = Blueprint('main', __name__)
|
||||
warehouse_bp = Blueprint('warehouse', __name__)
|
||||
|
||||
def format_cell_data(cell):
|
||||
"""Helper function to format cell data, especially dates and times"""
|
||||
# Import date and datetime at the top of the function
|
||||
from datetime import datetime, date
|
||||
|
||||
if isinstance(cell, datetime):
|
||||
# Format datetime as dd/mm/yyyy
|
||||
return cell.strftime('%d/%m/%Y')
|
||||
elif isinstance(cell, date):
|
||||
# Format date as dd/mm/yyyy
|
||||
return cell.strftime('%d/%m/%Y')
|
||||
elif isinstance(cell, timedelta):
|
||||
# Convert timedelta to HH:MM:SS format
|
||||
total_seconds = int(cell.total_seconds())
|
||||
hours, remainder = divmod(total_seconds, 3600)
|
||||
minutes, seconds = divmod(remainder, 60)
|
||||
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
||||
elif isinstance(cell, str):
|
||||
# Handle string dates in yyyy-mm-dd or yyyy-mm-dd HH:MM:SS
|
||||
import re
|
||||
match = re.match(r'^(\d{4})-(\d{2})-(\d{2})(.*)$', cell)
|
||||
if match:
|
||||
year, month, day, rest = match.groups()
|
||||
formatted = f"{day}/{month}/{year}"
|
||||
if rest.strip():
|
||||
# If there is a time part, keep it after the date
|
||||
formatted += rest
|
||||
return formatted
|
||||
return cell
|
||||
else:
|
||||
return cell
|
||||
|
||||
@bp.route('/store_articles')
|
||||
def store_articles():
|
||||
return render_template('store_articles.html')
|
||||
|
||||
@bp.route('/get_pairing_keys')
|
||||
def get_pairing_keys():
|
||||
"""Return all pairing keys as JSON for client selection."""
|
||||
keys_path = os.path.join(current_app.instance_path, 'pairing_keys.json')
|
||||
try:
|
||||
if os.path.exists(keys_path):
|
||||
with open(keys_path, 'r') as f:
|
||||
keys = json.load(f)
|
||||
else:
|
||||
keys = []
|
||||
except Exception as e:
|
||||
print(f"Error loading pairing keys: {e}")
|
||||
return jsonify([]), 200
|
||||
return jsonify(keys)
|
||||
|
||||
@bp.route('/warehouse_reports')
|
||||
def warehouse_reports():
|
||||
return render_template('warehouse_reports.html')
|
||||
|
||||
def get_db_connection():
|
||||
"""Reads the external_server.conf file and returns a MariaDB database connection."""
|
||||
settings_file = os.path.join(current_app.instance_path, 'external_server.conf')
|
||||
if not os.path.exists(settings_file):
|
||||
raise FileNotFoundError("The external_server.conf file is missing in the instance folder.")
|
||||
|
||||
# Read settings from the configuration file
|
||||
settings = {}
|
||||
with open(settings_file, 'r') as f:
|
||||
for line in f:
|
||||
key, value = line.strip().split('=', 1)
|
||||
settings[key] = value
|
||||
|
||||
# Create a database connection
|
||||
return mariadb.connect(
|
||||
user=settings['username'],
|
||||
password=settings['password'],
|
||||
host=settings['server_domain'],
|
||||
port=int(settings['port']),
|
||||
database=settings['database_name']
|
||||
)
|
||||
|
||||
@bp.route('/login', methods=['GET', 'POST'])
|
||||
@bp.route('/', methods=['GET', 'POST'])
|
||||
def login():
|
||||
import sqlite3
|
||||
if request.method == 'POST':
|
||||
@@ -190,6 +114,83 @@ def login():
|
||||
flash('Invalid credentials. Please try again.')
|
||||
return render_template('login.html')
|
||||
|
||||
def format_cell_data(cell):
|
||||
"""Helper function to format cell data, especially dates and times"""
|
||||
# Import date and datetime at the top of the function
|
||||
from datetime import datetime, date
|
||||
|
||||
if isinstance(cell, datetime):
|
||||
# Format datetime as dd/mm/yyyy
|
||||
return cell.strftime('%d/%m/%Y')
|
||||
elif isinstance(cell, date):
|
||||
# Format date as dd/mm/yyyy
|
||||
return cell.strftime('%d/%m/%Y')
|
||||
elif isinstance(cell, timedelta):
|
||||
# Convert timedelta to HH:MM:SS format
|
||||
total_seconds = int(cell.total_seconds())
|
||||
hours, remainder = divmod(total_seconds, 3600)
|
||||
minutes, seconds = divmod(remainder, 60)
|
||||
return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
|
||||
elif isinstance(cell, str):
|
||||
# Handle string dates in yyyy-mm-dd or yyyy-mm-dd HH:MM:SS
|
||||
import re
|
||||
match = re.match(r'^(\d{4})-(\d{2})-(\d{2})(.*)$', cell)
|
||||
if match:
|
||||
year, month, day, rest = match.groups()
|
||||
formatted = f"{day}/{month}/{year}"
|
||||
if rest.strip():
|
||||
# If there is a time part, keep it after the date
|
||||
formatted += rest
|
||||
return formatted
|
||||
return cell
|
||||
else:
|
||||
return cell
|
||||
|
||||
@bp.route('/store_articles')
|
||||
def store_articles():
|
||||
return render_template('store_articles.html')
|
||||
|
||||
@bp.route('/get_pairing_keys')
|
||||
def get_pairing_keys():
|
||||
"""Return all pairing keys as JSON for client selection."""
|
||||
keys_path = os.path.join(current_app.instance_path, 'pairing_keys.json')
|
||||
try:
|
||||
if os.path.exists(keys_path):
|
||||
with open(keys_path, 'r') as f:
|
||||
keys = json.load(f)
|
||||
else:
|
||||
keys = []
|
||||
except Exception as e:
|
||||
print(f"Error loading pairing keys: {e}")
|
||||
return jsonify([]), 200
|
||||
return jsonify(keys)
|
||||
|
||||
@bp.route('/warehouse_reports')
|
||||
def warehouse_reports():
|
||||
return render_template('warehouse_reports.html')
|
||||
|
||||
def get_db_connection():
|
||||
"""Reads the external_server.conf file and returns a MariaDB database connection."""
|
||||
settings_file = os.path.join(current_app.instance_path, 'external_server.conf')
|
||||
if not os.path.exists(settings_file):
|
||||
raise FileNotFoundError("The external_server.conf file is missing in the instance folder.")
|
||||
|
||||
# Read settings from the configuration file
|
||||
settings = {}
|
||||
with open(settings_file, 'r') as f:
|
||||
for line in f:
|
||||
key, value = line.strip().split('=', 1)
|
||||
settings[key] = value
|
||||
|
||||
# Create a database connection
|
||||
return mariadb.connect(
|
||||
user=settings['username'],
|
||||
password=settings['password'],
|
||||
host=settings['server_domain'],
|
||||
port=int(settings['port']),
|
||||
database=settings['database_name']
|
||||
)
|
||||
|
||||
@bp.route('/dashboard')
|
||||
def dashboard():
|
||||
print("Session user:", session.get('user'), session.get('role'))
|
||||
@@ -203,21 +204,21 @@ def settings():
|
||||
|
||||
@bp.route('/quality')
|
||||
def quality():
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'quality']:
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'administrator', 'quality']:
|
||||
flash('Access denied: Quality users only.')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
return render_template('quality.html')
|
||||
|
||||
@bp.route('/warehouse')
|
||||
def warehouse():
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse']:
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'administrator', 'admin', 'warehouse']:
|
||||
flash('Access denied: Warehouse users only.')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
return render_template('main_page_warehouse.html')
|
||||
|
||||
@bp.route('/scan', methods=['GET', 'POST'])
|
||||
def scan():
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'scan']:
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'administrator', 'admin', 'scan']:
|
||||
flash('Access denied: Scan users only.')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
@@ -952,14 +953,14 @@ def test_database():
|
||||
|
||||
@bp.route('/etichete')
|
||||
def etichete():
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'etichete']:
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'admin', 'administrator', 'etichete']:
|
||||
flash('Access denied: Etichete users only.')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
return render_template('main_page_etichete.html')
|
||||
|
||||
@bp.route('/upload_data')
|
||||
def upload_data():
|
||||
return render_template('upload_data.html')
|
||||
return render_template('upload_orders.html')
|
||||
|
||||
@bp.route('/print_module')
|
||||
def print_module():
|
||||
@@ -1817,7 +1818,7 @@ def create_template():
|
||||
@bp.route('/get_database_tables', methods=['GET'])
|
||||
def get_database_tables():
|
||||
"""Get list of database tables for template creation"""
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse_manager', 'etichete']:
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'admin', 'administrator', 'warehouse_manager', 'etichete']:
|
||||
return jsonify({'error': 'Access denied'}), 403
|
||||
|
||||
try:
|
||||
@@ -1867,7 +1868,7 @@ def get_database_tables():
|
||||
@bp.route('/get_table_columns/<table_name>', methods=['GET'])
|
||||
def get_table_columns(table_name):
|
||||
"""Get column names and descriptions for a specific table"""
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse_manager', 'etichete']:
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'admin', 'administrator', 'warehouse_manager', 'etichete']:
|
||||
return jsonify({'error': 'Access denied'}), 403
|
||||
|
||||
try:
|
||||
@@ -2002,41 +2003,7 @@ def generate_pdf():
|
||||
return jsonify({'message': 'PDF generated successfully!', 'pdf_path': f'/static/label_templates/label_template.pdf'})
|
||||
|
||||
# Order Labels Upload Module Routes
|
||||
@bp.route('/upload_orders', methods=['GET', 'POST'])
|
||||
def upload_orders():
|
||||
"""Route for uploading orders CSV files for label generation"""
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse', 'warehouse_manager']:
|
||||
flash('Access denied: Warehouse management permissions required.')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
from app.order_labels import upload_orders_handler
|
||||
return upload_orders_handler()
|
||||
|
||||
@bp.route('/view_orders')
|
||||
def view_orders():
|
||||
"""Route for viewing uploaded orders"""
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse', 'warehouse_manager', 'warehouse_worker']:
|
||||
flash('Access denied: Warehouse access required.')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
from app.order_labels import get_orders_from_database
|
||||
orders = get_orders_from_database(200) # Get last 200 orders
|
||||
return render_template('view_orders.html', orders=orders)
|
||||
|
||||
@bp.route('/print_lost_labels')
|
||||
def print_lost_labels():
|
||||
"""Route for printing lost labels - allows searching and reprinting specific orders"""
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse', 'warehouse_manager', 'warehouse_worker']:
|
||||
flash('Access denied: Warehouse access required.')
|
||||
return redirect(url_for('main.dashboard'))
|
||||
|
||||
from app.order_labels import get_orders_from_database
|
||||
orders = get_orders_from_database(500) # Get more orders for searching
|
||||
return render_template('print_lost_labels.html', orders=orders)
|
||||
|
||||
@bp.route('/db_test')
|
||||
def db_test():
|
||||
"""Simple database test page"""
|
||||
# ...existing code...
|
||||
return render_template('db_test.html')
|
||||
|
||||
@bp.route('/get_unprinted_orders', methods=['GET'])
|
||||
@@ -2067,9 +2034,9 @@ def generate_labels_pdf(order_id, paper_saving_mode='true'):
|
||||
"""Generate PDF labels for a specific order"""
|
||||
print(f"DEBUG: generate_labels_pdf called for order_id: {order_id}")
|
||||
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse_manager', 'etichete']:
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'admin', 'administrator', 'warehouse_manager', 'etichete']:
|
||||
print(f"DEBUG: Access denied for role: {session.get('role')}")
|
||||
return jsonify({'error': 'Access denied. Required roles: superadmin, warehouse_manager, etichete'}), 403
|
||||
return jsonify({'error': 'Access denied. Required roles: superadmin, admin, administrator, warehouse_manager, etichete'}), 403
|
||||
|
||||
try:
|
||||
from .pdf_generator import generate_order_labels_pdf, update_order_printed_status
|
||||
@@ -2148,8 +2115,8 @@ def generate_labels_pdf(order_id, paper_saving_mode='true'):
|
||||
def generate_label_pdf():
|
||||
"""Generate a single label PDF for thermal printing via QZ Tray"""
|
||||
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse_manager', 'etichete']:
|
||||
return jsonify({'error': 'Access denied. Required roles: superadmin, warehouse_manager, etichete'}), 403
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'admin', 'administrator', 'warehouse_manager', 'etichete']:
|
||||
return jsonify({'error': 'Access denied. Required roles: superadmin, admin, administrator, warehouse_manager, etichete'}), 403
|
||||
|
||||
try:
|
||||
from .pdf_generator import LabelPDFGenerator
|
||||
@@ -2197,9 +2164,9 @@ def update_printed_status(order_id):
|
||||
"""Update printed status for direct printing (without PDF generation)"""
|
||||
print(f"DEBUG: update_printed_status called for order_id: {order_id}")
|
||||
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse_manager', 'etichete']:
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'admin', 'administrator', 'warehouse_manager', 'etichete']:
|
||||
print(f"DEBUG: Access denied for role: {session.get('role')}")
|
||||
return jsonify({'error': 'Access denied. Required roles: superadmin, warehouse_manager, etichete'}), 403
|
||||
return jsonify({'error': 'Access denied. Required roles: superadmin, admin, administrator, warehouse_manager, etichete'}), 403
|
||||
|
||||
try:
|
||||
from .pdf_generator import update_order_printed_status
|
||||
@@ -2312,7 +2279,7 @@ def get_order_data(order_id):
|
||||
"""Get specific order data for preview"""
|
||||
print(f"DEBUG: get_order_data called for order_id: {order_id}")
|
||||
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'warehouse_manager', 'etichete']:
|
||||
if 'role' not in session or session['role'] not in ['superadmin', 'admin', 'administrator', 'warehouse_manager', 'etichete']:
|
||||
return jsonify({'error': 'Access denied'}), 403
|
||||
|
||||
try:
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
<h3>View Orders</h3>
|
||||
<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_orders') }}" class="btn">Upload Orders</a>
|
||||
<a href="{{ url_for('main.view_orders') }}" class="btn">View Orders</a>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<p>Access the print module to print labels.</p>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
<a href="{{ url_for('main.print_module') }}" class="btn">Launch Printing Module</a>
|
||||
<a href="{{ url_for('main.print_lost_labels') }}" class="btn">Launch lost labels printing module</a>
|
||||
<a href="{{ url_for('main.print_module') }}" class="btn">Launch lost labels printing module</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
server_domain=localhost
|
||||
port=3602
|
||||
database_name=trasabilitate_database
|
||||
username=trasabilitate
|
||||
password=Initial01!
|
||||
database_name=recticel
|
||||
username=sa
|
||||
password=12345678
|
||||
|
||||
@@ -3,4 +3,4 @@ from app import create_app
|
||||
app = create_app()
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True)
|
||||
app.run(debug=True, port=8781, host='0.0.0.0')
|
||||
|
||||
15
recticel-app.service
Normal file
15
recticel-app.service
Normal file
@@ -0,0 +1,15 @@
|
||||
[Unit]
|
||||
Description=Recticel Quality App
|
||||
After=network.target mariadb.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=ske087
|
||||
WorkingDirectory=/home/ske087/quality_recticel
|
||||
Environment=PATH=/home/ske087/quality_recticel/recticel/bin
|
||||
ExecStart=/home/ske087/quality_recticel/recticel/bin/python py_app/run.py
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
27
to_do_install.txt
Normal file
27
to_do_install.txt
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
# Steps to Prepare Environment for Installing Python Requirements
|
||||
|
||||
1. Change ownership of the project directory (if needed):
|
||||
sudo chown -R $USER:$USER /home/ske087/quality_recticel
|
||||
|
||||
2. Install Python venv module:
|
||||
sudo apt install -y python3-venv
|
||||
|
||||
3. Create and activate the virtual environment:
|
||||
python3 -m venv recticel
|
||||
source recticel/bin/activate
|
||||
|
||||
4. Install MariaDB server and development libraries:
|
||||
sudo apt install -y mariadb-server libmariadb-dev
|
||||
|
||||
5. Create MariaDB database and user:
|
||||
sudo mysql -e "CREATE DATABASE recticel; CREATE USER 'sa'@'localhost' IDENTIFIED BY '12345678'; GRANT ALL PRIVILEGES ON recticel.* TO 'sa'@'localhost'; FLUSH PRIVILEGES;"
|
||||
|
||||
6. Install build tools (for compiling Python packages):
|
||||
sudo apt install -y build-essential
|
||||
|
||||
7. Install Python development headers:
|
||||
sudo apt install -y python3-dev
|
||||
|
||||
8. Install Python requirements:
|
||||
pip install -r py_app/requirements.txt
|
||||
Reference in New Issue
Block a user