updated control access

This commit is contained in:
Quality System Admin
2025-10-16 02:36:32 +03:00
parent 50c791e242
commit c96039542d
266 changed files with 32656 additions and 9 deletions

View File

@@ -1,152 +0,0 @@
# CSS Modular Structure Guide
## Overview
This guide explains how to migrate from a monolithic CSS file to a modular CSS structure for better maintainability and organization.
## New CSS Structure
```
app/static/css/
├── base.css # Global styles, header, buttons, theme
├── login.css # Login page specific styles
├── dashboard.css # Dashboard and module cards
├── warehouse.css # Warehouse module styles
├── etichete.css # Labels/etiquette module styles (to be created)
├── quality.css # Quality module styles (to be created)
└── scan.css # Scan module styles (to be created)
```
## Implementation Strategy
### Phase 1: Setup Modular Structure ✅
- [x] Created `css/` directory
- [x] Created `base.css` with global styles
- [x] Created `login.css` for login page
- [x] Created `warehouse.css` for warehouse module
- [x] Updated `base.html` to include modular CSS
- [x] Updated `login.html` to use new structure
### Phase 2: Migration Plan (Next Steps)
1. **Extract module-specific styles from style.css:**
- Etiquette/Labels module → `etichete.css`
- Quality module → `quality.css`
- Scan module → `scan.css`
2. **Update templates to use modular CSS:**
```html
{% block head %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/module-name.css') }}">
{% endblock %}
```
3. **Clean up original style.css:**
- Remove extracted styles
- Keep only legacy/common styles temporarily
- Eventually eliminate when all modules migrated
## Template Usage Pattern
### Standard Template Structure:
```html
{% extends "base.html" %}
{% block title %}Page Title{% endblock %}
{% block head %}
<!-- Include module-specific CSS -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/module-name.css') }}">
<!-- Page-specific overrides -->
<style>
/* Only use this for page-specific customizations */
</style>
{% endblock %}
{% block content %}
<!-- Page content -->
{% endblock %}
```
## CSS Loading Order
1. `base.css` - Global styles, header, buttons, theme
2. `style.css` - Legacy styles (temporary, for backward compatibility)
3. Module-specific CSS (e.g., `warehouse.css`)
4. Inline `<style>` blocks for page-specific overrides
## Benefits of This Structure
### 1. **Maintainability**
- Easy to find and edit module-specific styles
- Reduced conflicts between different modules
- Clear separation of concerns
### 2. **Performance**
- Only load CSS needed for specific pages
- Smaller file sizes per page
- Better caching (module CSS rarely changes)
### 3. **Team Development**
- Different developers can work on different modules
- Less merge conflicts in CSS files
- Clear ownership of styles
### 4. **Scalability**
- Easy to add new modules
- Simple to deprecate old styles
- Clear migration path
## Migration Checklist
### For Each Template:
- [ ] Identify module/page type
- [ ] Extract relevant styles to module CSS file
- [ ] Update template to include module CSS
- [ ] Test styling works correctly
- [ ] Remove old styles from style.css
### Current Status:
- [x] Login page - Fully migrated
- [x] Warehouse module - Partially migrated (create_locations.html updated)
- [ ] Dashboard - CSS created, templates need updating
- [ ] Etiquette module - Needs CSS extraction
- [ ] Quality module - Needs CSS extraction
- [ ] Scan module - Needs CSS extraction
## Example: Migrating a Template
### Before:
```html
{% block head %}
<style>
.my-module-specific-class {
/* styles here */
}
</style>
{% endblock %}
```
### After:
1. Move styles to `css/module-name.css`
2. Update template:
```html
{% block head %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/module-name.css') }}">
{% endblock %}
```
## Best Practices
1. **Use semantic naming:** `warehouse.css`, `login.css`, not `page1.css`
2. **Keep base.css minimal:** Only truly global styles
3. **Avoid deep nesting:** Keep CSS selectors simple
4. **Use consistent naming:** Follow existing patterns
5. **Document changes:** Update this guide when adding new modules
## Next Steps
1. Extract etiquette module styles to `etichete.css`
2. Update all etiquette templates to use new CSS
3. Extract quality module styles to `quality.css`
4. Extract scan module styles to `scan.css`
5. Gradually remove migrated styles from `style.css`
6. Eventually remove `style.css` dependency from `base.html`

View File

@@ -0,0 +1,90 @@
"""
Simple access control decorators for the 4-tier system
"""
from functools import wraps
from flask import session, redirect, url_for, flash, request
from .permissions_simple import check_access, ROLES
def requires_role(min_role_level=None, required_modules=None, page=None):
"""
Simple role-based access decorator
Args:
min_role_level (int): Minimum role level required (50, 70, 90, 100)
required_modules (list): Required modules for access
page (str): Page name for automatic access checking
"""
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
# Check if user is logged in
if 'user' not in session:
flash('Please log in to access this page.')
return redirect(url_for('main.login'))
user_role = session.get('role')
user_modules = session.get('modules', [])
# If page is specified, use automatic access checking
if page:
if not check_access(user_role, user_modules, page):
flash('Access denied: You do not have permission to access this page.')
return redirect(url_for('main.dashboard'))
return f(*args, **kwargs)
# Manual role level checking
if min_role_level:
user_level = ROLES.get(user_role, {}).get('level', 0)
if user_level < min_role_level:
flash('Access denied: Insufficient privileges.')
return redirect(url_for('main.dashboard'))
# Module requirement checking
if required_modules:
if user_role in ['superadmin', 'admin']:
# Superadmin and admin have access to all modules
pass
else:
if not any(module in user_modules for module in required_modules):
flash('Access denied: You do not have access to this module.')
return redirect(url_for('main.dashboard'))
return f(*args, **kwargs)
return decorated_function
return decorator
def superadmin_only(f):
"""Decorator for superadmin-only pages"""
return requires_role(min_role_level=100)(f)
def admin_plus(f):
"""Decorator for admin and superadmin access"""
return requires_role(min_role_level=90)(f)
def manager_plus(f):
"""Decorator for manager, admin, and superadmin access"""
return requires_role(min_role_level=70)(f)
def requires_quality_module(f):
"""Decorator for quality module access"""
return requires_role(required_modules=['quality'])(f)
def requires_warehouse_module(f):
"""Decorator for warehouse module access"""
return requires_role(required_modules=['warehouse'])(f)
def requires_labels_module(f):
"""Decorator for labels module access"""
return requires_role(required_modules=['labels'])(f)
def quality_manager_plus(f):
"""Decorator for quality module manager+ access"""
return requires_role(min_role_level=70, required_modules=['quality'])(f)
def warehouse_manager_plus(f):
"""Decorator for warehouse module manager+ access"""
return requires_role(min_role_level=70, required_modules=['warehouse'])(f)
def labels_manager_plus(f):
"""Decorator for labels module manager+ access"""
return requires_role(min_role_level=70, required_modules=['labels'])(f)

View File

@@ -1,110 +0,0 @@
#!/usr/bin/env python3
import mariadb
import os
import sys
def get_external_db_connection():
"""Reads the external_server.conf file and returns a MariaDB database connection."""
# Get the instance folder path
current_dir = os.path.dirname(os.path.abspath(__file__))
instance_folder = os.path.join(current_dir, '../../instance')
settings_file = os.path.join(instance_folder, 'external_server.conf')
if not os.path.exists(settings_file):
raise FileNotFoundError(f"The external_server.conf file is missing: {settings_file}")
# Read settings from the configuration file
settings = {}
with open(settings_file, 'r') as f:
for line in f:
line = line.strip()
if line and '=' in line:
key, value = line.split('=', 1)
settings[key] = value
print(f"Connecting to MariaDB:")
print(f" Host: {settings.get('server_domain', 'N/A')}")
print(f" Port: {settings.get('port', 'N/A')}")
print(f" Database: {settings.get('database_name', 'N/A')}")
return mariadb.connect(
user=settings['username'],
password=settings['password'],
host=settings['server_domain'],
port=int(settings['port']),
database=settings['database_name']
)
def main():
try:
print("=== Adding Email Column to Users Table ===")
conn = get_external_db_connection()
cursor = conn.cursor()
# First, check the current table structure
print("\n1. Checking current table structure...")
cursor.execute("DESCRIBE users")
columns = cursor.fetchall()
has_email = False
for column in columns:
print(f" Column: {column[0]} ({column[1]})")
if column[0] == 'email':
has_email = True
if not has_email:
print("\n2. Adding email column...")
cursor.execute("ALTER TABLE users ADD COLUMN email VARCHAR(255)")
conn.commit()
print(" ✓ Email column added successfully")
else:
print("\n2. Email column already exists")
# Now check and display all users
print("\n3. Current users in database:")
cursor.execute("SELECT id, username, role, email FROM users")
users = cursor.fetchall()
if users:
print(f" Found {len(users)} users:")
for user in users:
email = user[3] if user[3] else "No email"
print(f" - ID: {user[0]}, Username: {user[1]}, Role: {user[2]}, Email: {email}")
else:
print(" No users found - creating test users...")
# Create some test users
test_users = [
('admin_user', 'admin123', 'admin', 'admin@company.com'),
('manager_user', 'manager123', 'manager', 'manager@company.com'),
('warehouse_user', 'warehouse123', 'warehouse_manager', 'warehouse@company.com'),
('quality_user', 'quality123', 'quality_manager', 'quality@company.com')
]
for username, password, role, email in test_users:
try:
cursor.execute("""
INSERT INTO users (username, password, role, email)
VALUES (%s, %s, %s, %s)
""", (username, password, role, email))
print(f" ✓ Created user: {username} ({role})")
except mariadb.IntegrityError as e:
print(f" ⚠ User {username} already exists: {e}")
conn.commit()
print(" ✓ Test users created successfully")
conn.close()
print("\n=== Database Update Complete ===")
except Exception as e:
print(f"❌ Error: {e}")
import traceback
traceback.print_exc()
return 1
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,105 +0,0 @@
#!/usr/bin/env python3
import mariadb
import os
import sys
def get_external_db_connection():
"""Reads the external_server.conf file and returns a MariaDB database connection."""
# Get the instance folder path
current_dir = os.path.dirname(os.path.abspath(__file__))
instance_folder = os.path.join(current_dir, '../../instance')
settings_file = os.path.join(instance_folder, 'external_server.conf')
if not os.path.exists(settings_file):
raise FileNotFoundError(f"The external_server.conf file is missing: {settings_file}")
# Read settings from the configuration file
settings = {}
with open(settings_file, 'r') as f:
for line in f:
line = line.strip()
if line and '=' in line:
key, value = line.split('=', 1)
settings[key] = value
print(f"Connecting to MariaDB with settings:")
print(f" Host: {settings.get('server_domain', 'N/A')}")
print(f" Port: {settings.get('port', 'N/A')}")
print(f" Database: {settings.get('database_name', 'N/A')}")
print(f" Username: {settings.get('username', 'N/A')}")
# 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']
)
def main():
try:
print("=== Checking External MariaDB Database ===")
conn = get_external_db_connection()
cursor = conn.cursor()
# Create users table if it doesn't exist
print("\n1. Creating/verifying users table...")
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
role VARCHAR(50) NOT NULL,
email VARCHAR(255)
)
''')
print(" ✓ Users table created/verified")
# Check existing users
print("\n2. Checking existing users...")
cursor.execute("SELECT id, username, role, email FROM users")
users = cursor.fetchall()
if users:
print(f" Found {len(users)} existing users:")
for user in users:
email = user[3] if user[3] else "No email"
print(f" - ID: {user[0]}, Username: {user[1]}, Role: {user[2]}, Email: {email}")
else:
print(" No users found in external database")
# Create some test users
print("\n3. Creating test users...")
test_users = [
('admin_user', 'admin123', 'admin', 'admin@company.com'),
('manager_user', 'manager123', 'manager', 'manager@company.com'),
('warehouse_user', 'warehouse123', 'warehouse_manager', 'warehouse@company.com'),
('quality_user', 'quality123', 'quality_manager', 'quality@company.com')
]
for username, password, role, email in test_users:
try:
cursor.execute("""
INSERT INTO users (username, password, role, email)
VALUES (%s, %s, %s, %s)
""", (username, password, role, email))
print(f" ✓ Created user: {username} ({role})")
except mariadb.IntegrityError as e:
print(f" ⚠ User {username} already exists: {e}")
conn.commit()
print(" ✓ Test users created successfully")
conn.close()
print("\n=== Database Check Complete ===")
except Exception as e:
print(f"❌ Error: {e}")
return 1
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,60 +0,0 @@
import mariadb
import os
def get_external_db_connection():
"""Get MariaDB connection using external_server.conf"""
settings_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../instance/external_server.conf'))
settings = {}
with open(settings_file, 'r') as f:
for line in f:
key, value = line.strip().split('=', 1)
settings[key] = value
return mariadb.connect(
user=settings['username'],
password=settings['password'],
host=settings['server_domain'],
port=int(settings['port']),
database=settings['database_name']
)
def create_external_users_table():
"""Create users table and superadmin user in external MariaDB database"""
try:
conn = get_external_db_connection()
cursor = conn.cursor()
# Create users table if not exists (MariaDB syntax)
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
role VARCHAR(50) NOT NULL
)
''')
# Insert superadmin user if not exists
cursor.execute('''
INSERT IGNORE INTO users (username, password, role)
VALUES (%s, %s, %s)
''', ('superadmin', 'superadmin123', 'superadmin'))
# Check if user was created/exists
cursor.execute("SELECT username, password, role FROM users WHERE username = %s", ('superadmin',))
result = cursor.fetchone()
if result:
print(f"SUCCESS: Superadmin user exists in external database")
print(f"Username: {result[0]}, Password: {result[1]}, Role: {result[2]}")
else:
print("ERROR: Failed to create/find superadmin user")
conn.commit()
conn.close()
print("External MariaDB users table setup completed.")
except Exception as e:
print(f"ERROR: {e}")
if __name__ == "__main__":
create_external_users_table()

View File

@@ -1,110 +0,0 @@
#!/usr/bin/env python3
"""
Database script to create the order_for_labels table
This table will store order information for label generation
"""
import sys
import os
import mariadb
from flask import Flask
# Add the app directory to the path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
def get_db_connection():
"""Get database connection using settings from external_server.conf"""
# Go up two levels from this script to reach py_app directory, then to instance
app_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
settings_file = os.path.join(app_root, 'instance', 'external_server.conf')
settings = {}
with open(settings_file, 'r') as f:
for line in f:
key, value = line.strip().split('=', 1)
settings[key] = value
return mariadb.connect(
user=settings['username'],
password=settings['password'],
host=settings['server_domain'],
port=int(settings['port']),
database=settings['database_name']
)
def create_order_for_labels_table():
"""
Creates the order_for_labels table with the specified structure
"""
try:
conn = get_db_connection()
cursor = conn.cursor()
# First check if table already exists
cursor.execute("SHOW TABLES LIKE 'order_for_labels'")
result = cursor.fetchone()
if result:
print("Table 'order_for_labels' already exists.")
# Show current structure
cursor.execute("DESCRIBE order_for_labels")
columns = cursor.fetchall()
print("\nCurrent table structure:")
for col in columns:
print(f" {col[0]} - {col[1]} {'NULL' if col[2] == 'YES' else 'NOT NULL'}")
else:
# Create the table
create_table_sql = """
CREATE TABLE order_for_labels (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 'Unique identifier',
comanda_productie VARCHAR(15) NOT NULL COMMENT 'Production Order',
cod_articol VARCHAR(15) COMMENT 'Article Code',
descr_com_prod VARCHAR(50) NOT NULL COMMENT 'Production Order Description',
cantitate INT(3) NOT NULL COMMENT 'Quantity',
com_achiz_client VARCHAR(25) COMMENT 'Client Purchase Order',
nr_linie_com_client INT(3) COMMENT 'Client Order Line Number',
customer_name VARCHAR(50) COMMENT 'Customer Name',
customer_article_number VARCHAR(25) COMMENT 'Customer Article Number',
open_for_order VARCHAR(25) COMMENT 'Open for Order Status',
line_number INT(3) COMMENT 'Line Number',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'Record creation timestamp',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Record update timestamp'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Table for storing order information for label generation'
"""
cursor.execute(create_table_sql)
conn.commit()
print("✅ Table 'order_for_labels' created successfully!")
# Show the created structure
cursor.execute("DESCRIBE order_for_labels")
columns = cursor.fetchall()
print("\n📋 Table structure:")
for col in columns:
null_info = 'NULL' if col[2] == 'YES' else 'NOT NULL'
default_info = f" DEFAULT {col[4]}" if col[4] else ""
print(f" 📌 {col[0]:<25} {col[1]:<20} {null_info}{default_info}")
conn.close()
except mariadb.Error as e:
print(f"❌ Database error: {e}")
return False
except Exception as e:
print(f"❌ Error: {e}")
return False
return True
if __name__ == "__main__":
print("🏗️ Creating order_for_labels table...")
print("="*50)
success = create_order_for_labels_table()
if success:
print("\n✅ Database setup completed successfully!")
else:
print("\n❌ Database setup failed!")
print("="*50)

View File

@@ -1,141 +0,0 @@
#!/usr/bin/env python3
import mariadb
import os
import sys
def get_external_db_connection():
"""Reads the external_server.conf file and returns a MariaDB database connection."""
# Get the instance folder path
current_dir = os.path.dirname(os.path.abspath(__file__))
instance_folder = os.path.join(current_dir, '../../instance')
settings_file = os.path.join(instance_folder, 'external_server.conf')
if not os.path.exists(settings_file):
raise FileNotFoundError(f"The external_server.conf file is missing: {settings_file}")
# Read settings from the configuration file
settings = {}
with open(settings_file, 'r') as f:
for line in f:
line = line.strip()
if line and '=' in line:
key, value = line.split('=', 1)
settings[key] = value
return mariadb.connect(
user=settings['username'],
password=settings['password'],
host=settings['server_domain'],
port=int(settings['port']),
database=settings['database_name']
)
def main():
try:
print("=== Creating Permission Management Tables ===")
conn = get_external_db_connection()
cursor = conn.cursor()
# 1. Create permissions table
print("\n1. Creating permissions table...")
cursor.execute('''
CREATE TABLE IF NOT EXISTS permissions (
id INT AUTO_INCREMENT PRIMARY KEY,
permission_key VARCHAR(255) UNIQUE NOT NULL,
page VARCHAR(100) NOT NULL,
page_name VARCHAR(255) NOT NULL,
section VARCHAR(100) NOT NULL,
section_name VARCHAR(255) NOT NULL,
action VARCHAR(50) NOT NULL,
action_name VARCHAR(255) NOT NULL,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
''')
print(" ✓ Permissions table created/verified")
# 2. Create role_permissions table
print("\n2. Creating role_permissions table...")
cursor.execute('''
CREATE TABLE IF NOT EXISTS role_permissions (
id INT AUTO_INCREMENT PRIMARY KEY,
role VARCHAR(50) NOT NULL,
permission_key VARCHAR(255) NOT NULL,
granted BOOLEAN DEFAULT TRUE,
granted_by VARCHAR(50),
granted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY unique_role_permission (role, permission_key),
FOREIGN KEY (permission_key) REFERENCES permissions(permission_key) ON DELETE CASCADE
)
''')
print(" ✓ Role permissions table created/verified")
# 3. Create role_hierarchy table for role management
print("\n3. Creating role_hierarchy table...")
cursor.execute('''
CREATE TABLE IF NOT EXISTS role_hierarchy (
id INT AUTO_INCREMENT PRIMARY KEY,
role_name VARCHAR(50) UNIQUE NOT NULL,
display_name VARCHAR(255) NOT NULL,
description TEXT,
level INT DEFAULT 0,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
''')
print(" ✓ Role hierarchy table created/verified")
# 4. Create permission_audit_log table for tracking changes
print("\n4. Creating permission_audit_log table...")
cursor.execute('''
CREATE TABLE IF NOT EXISTS permission_audit_log (
id INT AUTO_INCREMENT PRIMARY KEY,
role VARCHAR(50) NOT NULL,
permission_key VARCHAR(255) NOT NULL,
action ENUM('granted', 'revoked') NOT NULL,
changed_by VARCHAR(50) NOT NULL,
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
reason TEXT,
ip_address VARCHAR(45)
)
''')
print(" ✓ Permission audit log table created/verified")
conn.commit()
# 5. Check if we need to populate initial data
print("\n5. Checking for existing data...")
cursor.execute("SELECT COUNT(*) FROM permissions")
permission_count = cursor.fetchone()[0]
if permission_count == 0:
print(" No permissions found - will need to populate with default data")
print(" Run 'populate_permissions.py' to initialize the permission system")
else:
print(f" Found {permission_count} existing permissions")
cursor.execute("SELECT COUNT(*) FROM role_hierarchy")
role_count = cursor.fetchone()[0]
if role_count == 0:
print(" No roles found - will need to populate with default roles")
else:
print(f" Found {role_count} existing roles")
conn.close()
print("\n=== Permission Database Schema Created Successfully ===")
except Exception as e:
print(f"❌ Error: {e}")
import traceback
traceback.print_exc()
return 1
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,45 +0,0 @@
import sqlite3
import os
def create_roles_and_users_tables(db_path):
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Create users table if not exists
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
role TEXT NOT NULL
)
''')
# Insert superadmin user if not exists (default password: 'admin', change after first login)
cursor.execute('''
INSERT OR IGNORE INTO users (username, password, role)
VALUES (?, ?, ?)
''', ('superadmin', 'superadmin123', 'superadmin'))
# Create roles table if not exists
cursor.execute('''
CREATE TABLE IF NOT EXISTS roles (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL,
access_level TEXT NOT NULL,
description TEXT
)
''')
# Insert superadmin role if not exists
cursor.execute('''
INSERT OR IGNORE INTO roles (name, access_level, description)
VALUES (?, ?, ?)
''', ('superadmin', 'full', 'Full access to all app areas and functions'))
conn.commit()
conn.close()
if __name__ == "__main__":
# Default path to users.db in instance folder
instance_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../instance'))
if not os.path.exists(instance_folder):
os.makedirs(instance_folder)
db_path = os.path.join(instance_folder, 'users.db')
create_roles_and_users_tables(db_path)
print("Roles and users tables created. Superadmin user and role initialized.")

View File

@@ -1,42 +0,0 @@
import mariadb
# Database connection credentials
db_config = {
"user": "trasabilitate",
"password": "Initial01!",
"host": "localhost",
"database": "trasabilitate"
}
# Connect to the database
try:
conn = mariadb.connect(**db_config)
cursor = conn.cursor()
print("Connected to the database successfully!")
# Create the scan1_orders table
create_table_query = """
CREATE TABLE IF NOT EXISTS scan1_orders (
Id INT AUTO_INCREMENT PRIMARY KEY, -- Auto-incremented ID with 6 digits
operator_code VARCHAR(4) NOT NULL, -- Operator code with 4 characters
CP_full_code VARCHAR(15) NOT NULL UNIQUE, -- Full CP code with up to 15 characters
OC1_code VARCHAR(4) NOT NULL, -- OC1 code with 4 characters
OC2_code VARCHAR(4) NOT NULL, -- OC2 code with 4 characters
CP_base_code VARCHAR(10) GENERATED ALWAYS AS (LEFT(CP_full_code, 10)) STORED, -- Auto-generated base code (first 10 characters of CP_full_code)
quality_code INT(3) NOT NULL, -- Quality code with 3 digits
date DATE NOT NULL, -- Date in format dd-mm-yyyy
time TIME NOT NULL, -- Time in format hh:mm:ss
approved_quantity INT DEFAULT 0, -- Auto-incremented quantity for quality_code = 000
rejected_quantity INT DEFAULT 0 -- Auto-incremented quantity for quality_code != 000
);
"""
cursor.execute(create_table_query)
print("Table 'scan1_orders' created successfully!")
# Commit changes and close the connection
conn.commit()
cursor.close()
conn.close()
except mariadb.Error as e:
print(f"Error connecting to the database: {e}")

View File

@@ -1,41 +0,0 @@
import mariadb
# Database connection credentials
# (reuse from create_scan_1db.py or update as needed)
db_config = {
"user": "trasabilitate",
"password": "Initial01!",
"host": "localhost",
"database": "trasabilitate"
}
try:
conn = mariadb.connect(**db_config)
cursor = conn.cursor()
print("Connected to the database successfully!")
# Create the scanfg_orders table (same structure as scan1_orders)
create_table_query = """
CREATE TABLE IF NOT EXISTS scanfg_orders (
Id INT AUTO_INCREMENT PRIMARY KEY,
operator_code VARCHAR(4) NOT NULL,
CP_full_code VARCHAR(15) NOT NULL UNIQUE,
OC1_code VARCHAR(4) NOT NULL,
OC2_code VARCHAR(4) NOT NULL,
CP_base_code VARCHAR(10) GENERATED ALWAYS AS (LEFT(CP_full_code, 10)) STORED,
quality_code INT(3) NOT NULL,
date DATE NOT NULL,
time TIME NOT NULL,
approved_quantity INT DEFAULT 0,
rejected_quantity INT DEFAULT 0
);
"""
cursor.execute(create_table_query)
print("Table 'scanfg_orders' created successfully!")
conn.commit()
cursor.close()
conn.close()
except mariadb.Error as e:
print(f"Error connecting to the database: {e}")

View File

@@ -1,70 +0,0 @@
import mariadb
# Database connection credentials
db_config = {
"user": "trasabilitate",
"password": "Initial01!",
"host": "localhost",
"database": "trasabilitate"
}
# Connect to the database
try:
conn = mariadb.connect(**db_config)
cursor = conn.cursor()
print("Connected to the database successfully!")
# Delete old triggers if they exist
try:
cursor.execute("DROP TRIGGER IF EXISTS increment_approved_quantity;")
print("Old trigger 'increment_approved_quantity' deleted successfully.")
except mariadb.Error as e:
print(f"Error deleting old trigger 'increment_approved_quantity': {e}")
try:
cursor.execute("DROP TRIGGER IF EXISTS increment_rejected_quantity;")
print("Old trigger 'increment_rejected_quantity' deleted successfully.")
except mariadb.Error as e:
print(f"Error deleting old trigger 'increment_rejected_quantity': {e}")
# Create corrected trigger for approved_quantity
create_approved_trigger = """
CREATE TRIGGER increment_approved_quantity
BEFORE INSERT ON scan1_orders
FOR EACH ROW
BEGIN
IF NEW.quality_code = 000 THEN
SET NEW.approved_quantity = (
SELECT COUNT(*)
FROM scan1_orders
WHERE CP_base_code = NEW.CP_base_code AND quality_code = 000
) + 1;
SET NEW.rejected_quantity = (
SELECT COUNT(*)
FROM scan1_orders
WHERE CP_base_code = NEW.CP_base_code AND quality_code != 000
);
ELSE
SET NEW.approved_quantity = (
SELECT COUNT(*)
FROM scan1_orders
WHERE CP_base_code = NEW.CP_base_code AND quality_code = 000
);
SET NEW.rejected_quantity = (
SELECT COUNT(*)
FROM scan1_orders
WHERE CP_base_code = NEW.CP_base_code AND quality_code != 000
) + 1;
END IF;
END;
"""
cursor.execute(create_approved_trigger)
print("Trigger 'increment_approved_quantity' created successfully!")
# Commit changes and close the connection
conn.commit()
cursor.close()
conn.close()
except mariadb.Error as e:
print(f"Error connecting to the database or creating triggers: {e}")

View File

@@ -1,73 +0,0 @@
import mariadb
# Database connection credentials
db_config = {
"user": "trasabilitate",
"password": "Initial01!",
"host": "localhost",
"database": "trasabilitate"
}
# Connect to the database
try:
conn = mariadb.connect(**db_config)
cursor = conn.cursor()
print("Connected to the database successfully!")
# Delete old triggers if they exist
try:
cursor.execute("DROP TRIGGER IF EXISTS increment_approved_quantity_fg;")
print("Old trigger 'increment_approved_quantity_fg' deleted successfully.")
except mariadb.Error as e:
print(f"Error deleting old trigger 'increment_approved_quantity_fg': {e}")
try:
cursor.execute("DROP TRIGGER IF EXISTS increment_rejected_quantity_fg;")
print("Old trigger 'increment_rejected_quantity_fg' deleted successfully.")
except mariadb.Error as e:
print(f"Error deleting old trigger 'increment_rejected_quantity_fg': {e}")
# Create corrected trigger for approved_quantity in scanfg_orders
create_approved_trigger_fg = """
CREATE TRIGGER increment_approved_quantity_fg
BEFORE INSERT ON scanfg_orders
FOR EACH ROW
BEGIN
IF NEW.quality_code = 000 THEN
SET NEW.approved_quantity = (
SELECT COUNT(*)
FROM scanfg_orders
WHERE CP_base_code = NEW.CP_base_code AND quality_code = 000
) + 1;
SET NEW.rejected_quantity = (
SELECT COUNT(*)
FROM scanfg_orders
WHERE CP_base_code = NEW.CP_base_code AND quality_code != 000
);
ELSE
SET NEW.approved_quantity = (
SELECT COUNT(*)
FROM scanfg_orders
WHERE CP_base_code = NEW.CP_base_code AND quality_code = 000
);
SET NEW.rejected_quantity = (
SELECT COUNT(*)
FROM scanfg_orders
WHERE CP_base_code = NEW.CP_base_code AND quality_code != 000
) + 1;
END IF;
END;
"""
cursor.execute(create_approved_trigger_fg)
print("Trigger 'increment_approved_quantity_fg' created successfully for scanfg_orders table!")
# Commit changes and close the connection
conn.commit()
cursor.close()
conn.close()
print("\n✅ All triggers for scanfg_orders table created successfully!")
print("The approved_quantity and rejected_quantity will now be calculated automatically.")
except mariadb.Error as e:
print(f"Error connecting to the database or creating triggers: {e}")

View File

@@ -1,25 +0,0 @@
import mariadb
from app.warehouse import get_db_connection
from flask import Flask
import os
def create_warehouse_locations_table():
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS warehouse_locations (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
location_code VARCHAR(12) NOT NULL UNIQUE,
size INT,
description VARCHAR(250)
)
''')
conn.commit()
conn.close()
if __name__ == "__main__":
instance_path = os.path.abspath("instance")
app = Flask(__name__, instance_path=instance_path)
with app.app_context():
create_warehouse_locations_table()
print("warehouse_locations table created or already exists.")

View File

@@ -1,30 +0,0 @@
import mariadb
# Database connection credentials
def get_db_connection():
return mariadb.connect(
user="trasabilitate", # Replace with your username
password="Initial01!", # Replace with your password
host="localhost", # Replace with your host
port=3306, # Default MariaDB port
database="trasabilitate_database" # Replace with your database name
)
try:
# Connect to the database
conn = get_db_connection()
cursor = conn.cursor()
# Delete query
delete_query = "DELETE FROM scan1_orders"
cursor.execute(delete_query)
conn.commit()
print("All data from the 'scan1_orders' table has been deleted successfully.")
# Close the connection
cursor.close()
conn.close()
except mariadb.Error as e:
print(f"Error deleting data: {e}")

View File

@@ -1,26 +0,0 @@
import mariadb
import os
def get_external_db_connection():
settings_file = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../instance/external_server.conf'))
settings = {}
with open(settings_file, 'r') as f:
for line in f:
key, value = line.strip().split('=', 1)
settings[key] = value
return mariadb.connect(
user=settings['username'],
password=settings['password'],
host=settings['server_domain'],
port=int(settings['port']),
database=settings['database_name']
)
if __name__ == "__main__":
conn = get_external_db_connection()
cursor = conn.cursor()
cursor.execute("DROP TABLE IF EXISTS users")
cursor.execute("DROP TABLE IF EXISTS roles")
conn.commit()
conn.close()
print("Dropped users and roles tables from external database.")

View File

@@ -1,53 +0,0 @@
import sqlite3
import os
def check_database(db_path, description):
"""Check if a database exists and show its users."""
if os.path.exists(db_path):
print(f"\n{description}: FOUND at {db_path}")
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Check if users table exists
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='users'")
if cursor.fetchone():
cursor.execute("SELECT id, username, password, role FROM users")
users = cursor.fetchall()
if users:
print("Users in this database:")
for user in users:
print(f" ID: {user[0]}, Username: {user[1]}, Password: {user[2]}, Role: {user[3]}")
else:
print(" Users table exists but is empty")
else:
print(" No users table found")
conn.close()
except Exception as e:
print(f" Error reading database: {e}")
else:
print(f"\n{description}: NOT FOUND at {db_path}")
if __name__ == "__main__":
# Check different possible locations for users.db
# 1. Root quality_recticel/instance/users.db
root_instance = "/home/ske087/quality_recticel/instance/users.db"
check_database(root_instance, "Root instance users.db")
# 2. App instance folder
app_instance = "/home/ske087/quality_recticel/py_app/instance/users.db"
check_database(app_instance, "App instance users.db")
# 3. Current working directory
cwd_db = "/home/ske087/quality_recticel/py_app/users.db"
check_database(cwd_db, "Working directory users.db")
# 4. Flask app database (relative to py_app)
flask_db = "/home/ske087/quality_recticel/py_app/app/users.db"
check_database(flask_db, "Flask app users.db")
print("\n" + "="*50)
print("RECOMMENDATION:")
print("The login should use the external MariaDB database.")
print("Make sure you have created the superadmin user in MariaDB using create_roles_table.py")

View File

@@ -1,143 +0,0 @@
#!/usr/bin/env python3
import mariadb
import os
import sys
# Add the app directory to the path so we can import our permissions module
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from permissions import APP_PERMISSIONS, ROLE_HIERARCHY, ACTIONS, get_all_permissions, get_default_permissions_for_role
def get_external_db_connection():
"""Reads the external_server.conf file and returns a MariaDB database connection."""
current_dir = os.path.dirname(os.path.abspath(__file__))
instance_folder = os.path.join(current_dir, '../../instance')
settings_file = os.path.join(instance_folder, 'external_server.conf')
if not os.path.exists(settings_file):
raise FileNotFoundError(f"The external_server.conf file is missing: {settings_file}")
settings = {}
with open(settings_file, 'r') as f:
for line in f:
line = line.strip()
if line and '=' in line:
key, value = line.split('=', 1)
settings[key] = value
return mariadb.connect(
user=settings['username'],
password=settings['password'],
host=settings['server_domain'],
port=int(settings['port']),
database=settings['database_name']
)
def main():
try:
print("=== Populating Permission System ===")
conn = get_external_db_connection()
cursor = conn.cursor()
# 1. Populate all permissions
print("\n1. Populating permissions...")
permissions = get_all_permissions()
for perm in permissions:
try:
cursor.execute('''
INSERT INTO permissions (permission_key, page, page_name, section, section_name, action, action_name)
VALUES (%s, %s, %s, %s, %s, %s, %s)
ON DUPLICATE KEY UPDATE
page_name = VALUES(page_name),
section_name = VALUES(section_name),
action_name = VALUES(action_name),
updated_at = CURRENT_TIMESTAMP
''', (
perm['key'],
perm['page'],
perm['page_name'],
perm['section'],
perm['section_name'],
perm['action'],
perm['action_name']
))
except Exception as e:
print(f" ⚠ Error inserting permission {perm['key']}: {e}")
conn.commit()
print(f" ✓ Populated {len(permissions)} permissions")
# 2. Populate role hierarchy
print("\n2. Populating role hierarchy...")
for role_name, role_data in ROLE_HIERARCHY.items():
try:
cursor.execute('''
INSERT INTO role_hierarchy (role_name, display_name, description, level)
VALUES (%s, %s, %s, %s)
ON DUPLICATE KEY UPDATE
display_name = VALUES(display_name),
description = VALUES(description),
level = VALUES(level),
updated_at = CURRENT_TIMESTAMP
''', (
role_name,
role_data['name'],
role_data['description'],
role_data['level']
))
except Exception as e:
print(f" ⚠ Error inserting role {role_name}: {e}")
conn.commit()
print(f" ✓ Populated {len(ROLE_HIERARCHY)} roles")
# 3. Set default permissions for each role
print("\n3. Setting default role permissions...")
for role_name in ROLE_HIERARCHY.keys():
default_permissions = get_default_permissions_for_role(role_name)
print(f" Setting permissions for {role_name}: {len(default_permissions)} permissions")
for permission_key in default_permissions:
try:
cursor.execute('''
INSERT INTO role_permissions (role, permission_key, granted, granted_by)
VALUES (%s, %s, TRUE, 'system')
ON DUPLICATE KEY UPDATE
granted = TRUE,
updated_at = CURRENT_TIMESTAMP
''', (role_name, permission_key))
except Exception as e:
print(f" ⚠ Error setting permission {permission_key} for {role_name}: {e}")
conn.commit()
# 4. Show summary
print("\n4. Permission Summary:")
cursor.execute('''
SELECT r.role_name, r.display_name, COUNT(rp.permission_key) as permission_count
FROM role_hierarchy r
LEFT JOIN role_permissions rp ON r.role_name = rp.role AND rp.granted = TRUE
GROUP BY r.role_name, r.display_name
ORDER BY r.level DESC
''')
results = cursor.fetchall()
for role_name, display_name, count in results:
print(f" {display_name} ({role_name}): {count} permissions")
conn.close()
print("\n=== Permission System Initialization Complete ===")
except Exception as e:
print(f"❌ Error: {e}")
import traceback
traceback.print_exc()
return 1
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,30 +0,0 @@
import sqlite3
import os
instance_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../instance'))
db_path = os.path.join(instance_folder, 'users.db')
if not os.path.exists(db_path):
print("users.db not found at", db_path)
exit(1)
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Check if users table exists
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='users'")
if not cursor.fetchone():
print("No users table found in users.db.")
conn.close()
exit(1)
# Print all users
cursor.execute("SELECT id, username, password, role FROM users")
rows = cursor.fetchall()
if not rows:
print("No users found in users.db.")
else:
print("Users in users.db:")
for row in rows:
print(f"id={row[0]}, username={row[1]}, password={row[2]}, role={row[3]}")
conn.close()

View File

@@ -1,34 +0,0 @@
import mariadb
# Database connection credentials
db_config = {
"user": "trasabilitate",
"password": "Initial01!",
"host": "localhost",
"database": "trasabilitate_database"
}
try:
# Connect to the database
conn = mariadb.connect(**db_config)
cursor = conn.cursor()
# Query to fetch all records from the scan1 table
query = "SELECT * FROM scan1_orders ORDER BY Id DESC LIMIT 15"
cursor.execute(query)
# Fetch and print the results
rows = cursor.fetchall()
if rows:
print("Records in the 'scan1_orders' table:")
for row in rows:
print(row)
else:
print("No records found in the 'scan1_orders' table.")
# Close the connection
cursor.close()
conn.close()
except mariadb.Error as e:
print(f"Error connecting to the database: {e}")

View File

@@ -1,50 +0,0 @@
import mariadb
# Database connection credentials
DB_CONFIG = {
"user": "sa",
"password": "12345678",
"host": "localhost",
"database": "recticel"
}
def recreate_order_for_labels_table():
conn = mariadb.connect(**DB_CONFIG)
cursor = conn.cursor()
print("Connected to the database successfully!")
# Drop the table if it exists
cursor.execute("DROP TABLE IF EXISTS order_for_labels")
print("Dropped existing 'order_for_labels' table.")
# Create the table with the new unique constraint
create_table_sql = """
CREATE TABLE order_for_labels (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 'Unique identifier',
comanda_productie VARCHAR(15) NOT NULL UNIQUE COMMENT 'Production Order (unique)',
cod_articol VARCHAR(15) COMMENT 'Article Code',
descr_com_prod VARCHAR(50) NOT NULL COMMENT 'Production Order Description',
cantitate INT(3) NOT NULL COMMENT 'Quantity',
data_livrare DATE COMMENT 'Delivery date',
dimensiune VARCHAR(20) COMMENT 'Dimensions',
com_achiz_client VARCHAR(25) COMMENT 'Client Purchase Order',
nr_linie_com_client INT(3) COMMENT 'Client Order Line Number',
customer_name VARCHAR(50) COMMENT 'Customer Name',
customer_article_number VARCHAR(25) COMMENT 'Customer Article Number',
open_for_order VARCHAR(25) COMMENT 'Open for Order Status',
line_number INT(3) COMMENT 'Line Number',
printed_labels TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'Boolean flag: 0=labels not printed, 1=labels printed',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT 'Record creation timestamp',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Record update timestamp'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Table for storing order information for label generation';
"""
cursor.execute(create_table_sql)
print("Created new 'order_for_labels' table with unique comanda_productie.")
conn.commit()
cursor.close()
conn.close()
print("Done.")
if __name__ == "__main__":
recreate_order_for_labels_table()

View File

@@ -1,34 +0,0 @@
import sqlite3
import os
from flask import Flask
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key' # Use the same key as in __init__.py
instance_folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../instance'))
if not os.path.exists(instance_folder):
os.makedirs(instance_folder)
db_path = os.path.join(instance_folder, 'users.db')
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Create users table if not exists
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
role TEXT NOT NULL
)
''')
# Insert superadmin user if not exists
cursor.execute('''
INSERT OR IGNORE INTO users (username, password, role)
VALUES (?, ?, ?)
''', ('superadmin', 'superadmin123', 'superadmin'))
conn.commit()
conn.close()
print("Internal users.db seeded with superadmin user.")

View File

@@ -1,37 +0,0 @@
import mariadb
# Database connection credentials
def get_db_connection():
return mariadb.connect(
user="trasabilitate", # Replace with your username
password="Initial01!", # Replace with your password
host="localhost", # Replace with your host
port=3306, # Default MariaDB port
database="trasabilitate_database" # Replace with your database name
)
try:
# Connect to the database
conn = get_db_connection()
cursor = conn.cursor()
# Insert query
insert_query = """
INSERT INTO scan1_orders (operator_code, CP_full_code, OC1_code, OC2_code, quality_code, date, time)
VALUES (?, ?, ?, ?, ?, ?, ?)
"""
# Values to insert
values = ('OP01', 'CP12345678-0002', 'OC11', 'OC22', 000, '2025-04-22', '14:30:00')
# Execute the query
cursor.execute(insert_query, values)
conn.commit()
print("Test data inserted successfully into scan1_orders.")
# Close the connection
cursor.close()
conn.close()
except mariadb.Error as e:
print(f"Error inserting data: {e}")

View File

@@ -0,0 +1,226 @@
"""
Simplified 4-Tier Role-Based Access Control System
Clear hierarchy: Superadmin → Admin → Manager → Worker
Module-based permissions: Quality, Labels, Warehouse
"""
# APPLICATION MODULES
MODULES = {
'quality': {
'name': 'Quality Control',
'scan_pages': ['quality', 'fg_quality'],
'management_pages': ['quality_reports', 'quality_settings'],
'worker_access': ['scan_only'] # Workers can only scan, no reports
},
'labels': {
'name': 'Label Management',
'scan_pages': ['label_scan'],
'management_pages': ['label_creation', 'label_reports'],
'worker_access': ['scan_only']
},
'warehouse': {
'name': 'Warehouse Management',
'scan_pages': ['move_orders'],
'management_pages': ['create_locations', 'warehouse_reports', 'inventory_management'],
'worker_access': ['move_orders_only'] # Workers can move orders but not create locations
}
}
# 4-TIER ROLE STRUCTURE
ROLES = {
'superadmin': {
'name': 'Super Administrator',
'level': 100,
'description': 'Full system access - complete control over all modules and system settings',
'access': {
'all_modules': True,
'all_pages': True,
'restricted_pages': [] # No restrictions
}
},
'admin': {
'name': 'Administrator',
'level': 90,
'description': 'Full app access except role permissions and extension download',
'access': {
'all_modules': True,
'all_pages': True,
'restricted_pages': ['role_permissions', 'download_extension']
}
},
'manager': {
'name': 'Manager',
'level': 70,
'description': 'Complete module access - can manage one or more modules (quality/labels/warehouse)',
'access': {
'all_modules': False, # Only assigned modules
'module_access': 'full', # Full access to assigned modules
'can_cumulate': True, # Can have multiple modules
'restricted_pages': ['role_permissions', 'download_extension', 'system_settings']
}
},
'worker': {
'name': 'Worker',
'level': 50,
'description': 'Limited module access - can perform basic operations in assigned modules',
'access': {
'all_modules': False, # Only assigned modules
'module_access': 'limited', # Limited access (scan pages only)
'can_cumulate': True, # Can have multiple modules
'restricted_pages': ['role_permissions', 'download_extension', 'system_settings', 'reports']
}
}
}
# PAGE ACCESS RULES
PAGE_ACCESS = {
# System pages accessible by role level
'dashboard': {'min_level': 50, 'modules': []},
'settings': {'min_level': 90, 'modules': []},
'role_permissions': {'min_level': 100, 'modules': []}, # Superadmin only
'download_extension': {'min_level': 100, 'modules': []}, # Superadmin only
# Quality module pages
'quality': {'min_level': 50, 'modules': ['quality']},
'fg_quality': {'min_level': 50, 'modules': ['quality']},
'quality_reports': {'min_level': 70, 'modules': ['quality']}, # Manager+ only
'reports': {'min_level': 70, 'modules': ['quality']}, # Manager+ only for quality reports
# Warehouse module pages
'warehouse': {'min_level': 50, 'modules': ['warehouse']},
'move_orders': {'min_level': 50, 'modules': ['warehouse']},
'create_locations': {'min_level': 70, 'modules': ['warehouse']}, # Manager+ only
'warehouse_reports': {'min_level': 70, 'modules': ['warehouse']}, # Manager+ only
# Labels module pages
'labels': {'min_level': 50, 'modules': ['labels']},
'label_scan': {'min_level': 50, 'modules': ['labels']},
'label_creation': {'min_level': 70, 'modules': ['labels']}, # Manager+ only
'label_reports': {'min_level': 70, 'modules': ['labels']} # Manager+ only
}
def check_access(user_role, user_modules, page):
"""
Simple access check for the 4-tier system
Args:
user_role (str): User's role (superadmin, admin, manager, worker)
user_modules (list): User's assigned modules ['quality', 'warehouse']
page (str): Page being accessed
Returns:
bool: True if access granted, False otherwise
"""
if user_role not in ROLES:
return False
user_level = ROLES[user_role]['level']
# Check if page exists in our access rules
if page not in PAGE_ACCESS:
return False
page_config = PAGE_ACCESS[page]
# Check minimum level requirement
if user_level < page_config['min_level']:
return False
# Check restricted pages for this role
if page in ROLES[user_role]['access']['restricted_pages']:
return False
# Check module requirements
required_modules = page_config['modules']
if required_modules:
# Page requires specific modules
# Superadmin and admin have access to all modules by default
if ROLES[user_role]['access']['all_modules']:
return True
# Other roles need to have the required module assigned
if not any(module in user_modules for module in required_modules):
return False
return True
def get_user_accessible_pages(user_role, user_modules):
"""
Get list of pages accessible to a user
Args:
user_role (str): User's role
user_modules (list): User's assigned modules
Returns:
list: List of accessible page names
"""
accessible_pages = []
for page in PAGE_ACCESS.keys():
if check_access(user_role, user_modules, page):
accessible_pages.append(page)
return accessible_pages
def validate_user_modules(user_role, user_modules):
"""
Validate that user's module assignment is valid for their role
Args:
user_role (str): User's role
user_modules (list): User's assigned modules
Returns:
tuple: (is_valid, error_message)
"""
if user_role not in ROLES:
return False, "Invalid role"
role_config = ROLES[user_role]
# Superadmin and admin have access to all modules by default
if role_config['access']['all_modules']:
return True, ""
# Manager can have multiple modules
if user_role == 'manager':
if not user_modules:
return False, "Managers must have at least one module assigned"
valid_modules = list(MODULES.keys())
for module in user_modules:
if module not in valid_modules:
return False, f"Invalid module: {module}"
return True, ""
# Worker can have multiple modules now
if user_role == 'worker':
if not user_modules:
return False, "Workers must have at least one module assigned"
valid_modules = list(MODULES.keys())
for module in user_modules:
if module not in valid_modules:
return False, f"Invalid module: {module}"
return True, ""
return True, ""
def get_role_description(role):
"""Get human-readable role description"""
return ROLES.get(role, {}).get('description', 'Unknown role')
def get_available_modules():
"""Get list of available modules"""
return list(MODULES.keys())
def can_access_reports(user_role, user_modules, module):
"""
Check if user can access reports for a specific module
Worker level users cannot access reports
"""
if user_role == 'worker':
return False
if module in user_modules or ROLES[user_role]['access']['all_modules']:
return True
return False

View File

@@ -32,10 +32,8 @@ bp = Blueprint('main', __name__)
warehouse_bp = Blueprint('warehouse', __name__)
@bp.route('/main_scan')
@requires_quality_module
def main_scan():
if 'role' not in session or session['role'] not in ['superadmin', 'admin', 'administrator', 'scan']:
flash('Access denied: Scan users only.')
return redirect(url_for('main.dashboard'))
return render_template('main_page_scan.html')
@bp.route('/', methods=['GET', 'POST'])
@@ -386,6 +384,58 @@ def delete_user_simple():
flash('Error deleting user.')
return redirect(url_for('main.user_management_simple'))
@bp.route('/quick_update_modules', methods=['POST'])
@admin_plus
def quick_update_modules():
"""Quick update of user modules without changing other details"""
try:
user_id = request.form.get('user_id')
modules = request.form.getlist('modules')
if not user_id:
flash('User ID is required.')
return redirect(url_for('main.user_management_simple'))
# Get current user to validate role
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("SELECT username, role FROM users WHERE id=%s", (user_id,))
user_row = cursor.fetchone()
if not user_row:
flash('User not found.')
conn.close()
return redirect(url_for('main.user_management_simple'))
username, role = user_row
# Validate modules for the role
from app.permissions_simple import validate_user_modules
is_valid, error_msg = validate_user_modules(role, modules)
if not is_valid:
flash(f'Invalid module assignment: {error_msg}')
conn.close()
return redirect(url_for('main.user_management_simple'))
# Prepare modules JSON
modules_json = None
if modules and role in ['manager', 'worker']:
import json
modules_json = json.dumps(modules)
# Update modules only
cursor.execute("UPDATE users SET modules=%s WHERE id=%s", (modules_json, user_id))
conn.commit()
conn.close()
flash(f'Modules updated successfully for user "{username}".')
return redirect(url_for('main.user_management_simple'))
except Exception as e:
print(f"Error updating modules: {e}")
flash('Error updating modules.')
return redirect(url_for('main.user_management_simple'))
@bp.route('/reports')
@requires_quality_module
def reports():
@@ -499,10 +549,8 @@ def logout():
# Finish Goods Scan Route
@bp.route('/fg_scan', methods=['GET', 'POST'])
@requires_quality_module
def fg_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'))
if request.method == 'POST':
# Handle form submission

View File

@@ -1,361 +0,0 @@
# Quality Recticel Windows Print Service - Installation Guide
## 📋 Overview
The Quality Recticel Windows Print Service enables **silent PDF printing** directly from the web application through a Chrome extension. This system eliminates the need for manual PDF downloads and provides seamless label printing functionality.
## 🏗️ System Architecture
```
Web Application (print_module.html)
Windows Print Service (localhost:8765)
Chrome Extension (Native Messaging)
Windows Print System
```
## 📦 Package Contents
```
windows_print_service/
├── print_service.py # Main Windows service (Flask API)
├── service_manager.py # Service installation & management
├── install_service.bat # Automated installation script
├── chrome_extension/ # Chrome extension files
│ ├── manifest.json # Extension configuration
│ ├── background.js # Service worker
│ ├── content.js # Page integration
│ ├── popup.html # Extension UI
│ ├── popup.js # Extension logic
│ └── icons/ # Extension icons
└── INSTALLATION_GUIDE.md # This documentation
```
## 🔧 Prerequisites
### System Requirements
- **Operating System**: Windows 10/11 (64-bit)
- **Python**: Python 3.8 or higher
- **Browser**: Google Chrome (latest version)
- **Privileges**: Administrator access required for installation
### Python Dependencies
The following packages will be installed automatically:
- `flask` - Web service framework
- `flask-cors` - Cross-origin resource sharing
- `requests` - HTTP client library
- `pywin32` - Windows service integration
## 🚀 Installation Process
### Step 1: Download and Extract Files
1. Download the `windows_print_service` folder to your system
2. Extract to a permanent location (e.g., `C:\QualityRecticel\PrintService\`)
3. **Do not move or delete this folder after installation**
### Step 2: Install Windows Service
#### Method A: Automated Installation (Recommended)
1. **Right-click** on `install_service.bat`
2. Select **"Run as administrator"**
3. Click **"Yes"** when Windows UAC prompt appears
4. Wait for installation to complete
#### Method B: Manual Installation
If the automated script fails, follow these steps:
```bash
# Open Command Prompt as Administrator
cd C:\path\to\windows_print_service
# Install Python dependencies
pip install flask flask-cors requests pywin32
# Install Windows service
python service_manager.py install
# Add firewall exception
netsh advfirewall firewall add rule name="Quality Recticel Print Service" dir=in action=allow protocol=TCP localport=8765
# Create Chrome extension registry entry
reg add "HKEY_CURRENT_USER\Software\Google\Chrome\NativeMessagingHosts\com.qualityrecticel.printservice" /ve /d "%cd%\chrome_extension\manifest.json" /f
```
### Step 3: Install Chrome Extension
1. Open **Google Chrome**
2. Navigate to `chrome://extensions/`
3. Enable **"Developer mode"** (toggle in top-right corner)
4. Click **"Load unpacked"**
5. Select the `chrome_extension` folder
6. Verify the extension appears with a printer icon
### Step 4: Verify Installation
#### Check Windows Service Status
1. Press `Win + R`, type `services.msc`, press Enter
2. Look for **"Quality Recticel Print Service"**
3. Status should show **"Running"**
4. Startup type should be **"Automatic"**
#### Test API Endpoints
Open a web browser and visit:
- **Health Check**: `http://localhost:8765/health`
- **Printer List**: `http://localhost:8765/printers`
Expected response for health check:
```json
{
"status": "healthy",
"service": "Quality Recticel Print Service",
"version": "1.0",
"timestamp": "2025-09-21T10:30:00"
}
```
#### Test Chrome Extension
1. Click the extension icon in Chrome toolbar
2. Verify it shows "Service Status: Connected ✅"
3. Check that printers are listed
4. Try the "Test Print" button
## 🔄 Web Application Integration
The web application automatically detects the Windows service and adapts the user interface:
### Service Available (Green Button)
- Button text: **"🖨️ Print Labels (Silent)"**
- Functionality: Direct printing to default printer
- User experience: Click → Labels print immediately
### Service Unavailable (Blue Button)
- Button text: **"📄 Generate PDF"**
- Functionality: PDF download for manual printing
- User experience: Click → PDF downloads to browser
### Detection Logic
```javascript
// Automatic service detection on page load
const response = await fetch('http://localhost:8765/health');
if (response.ok) {
// Service available - enable silent printing
} else {
// Service unavailable - fallback to PDF download
}
```
## 🛠️ Configuration
### Service Configuration
The service runs with the following default settings:
| Setting | Value | Description |
|---------|-------|-------------|
| **Port** | 8765 | Local API port |
| **Host** | localhost | Service binding |
| **Startup** | Automatic | Starts with Windows |
| **Printer** | Default | Uses system default printer |
| **Copies** | 1 | Default print copies |
### Chrome Extension Permissions
The extension requires these permissions:
- `printing` - Access to printer functionality
- `nativeMessaging` - Communication with Windows service
- `activeTab` - Access to current webpage
- `storage` - Save extension settings
## 🔍 Troubleshooting
### Common Issues
#### 1. Service Not Starting
**Symptoms**: API not accessible at localhost:8765
**Solutions**:
```bash
# Check service status
python -c "from service_manager import service_status; service_status()"
# Restart service manually
python service_manager.py restart
# Check Windows Event Viewer for service errors
```
#### 2. Chrome Extension Not Working
**Symptoms**: Extension shows "Service Status: Disconnected ❌"
**Solutions**:
- Verify Windows service is running
- Check firewall settings (port 8765 must be open)
- Reload the Chrome extension
- Restart Chrome browser
#### 3. Firewall Blocking Connection
**Symptoms**: Service runs but web page can't connect
**Solutions**:
```bash
# Add firewall rule manually
netsh advfirewall firewall add rule name="Quality Recticel Print Service" dir=in action=allow protocol=TCP localport=8765
# Or disable Windows Firewall temporarily to test
```
#### 4. Permission Denied Errors
**Symptoms**: Installation fails with permission errors
**Solutions**:
- Ensure running as Administrator
- Check Windows UAC settings
- Verify Python installation permissions
#### 5. Print Jobs Not Processing
**Symptoms**: API accepts requests but nothing prints
**Solutions**:
- Check default printer configuration
- Verify printer drivers are installed
- Test manual printing from other applications
- Check Windows Print Spooler service
### Log Files
Check these locations for troubleshooting:
| Component | Log Location |
|-----------|--------------|
| **Windows Service** | `print_service.log` (same folder as service) |
| **Chrome Extension** | Chrome DevTools → Extensions → Background page |
| **Windows Event Log** | Event Viewer → Windows Logs → System |
### Diagnostic Commands
```bash
# Check service status
python service_manager.py status
# Test API manually
curl http://localhost:8765/health
# List available printers
curl http://localhost:8765/printers
# Check Windows service
sc query QualityRecticelPrintService
# Check listening ports
netstat -an | findstr :8765
```
## 🔄 Maintenance
### Updating the Service
1. Stop the current service:
```bash
python service_manager.py stop
```
2. Replace service files with new versions
3. Restart the service:
```bash
python service_manager.py start
```
### Uninstalling
#### Remove Chrome Extension
1. Go to `chrome://extensions/`
2. Find "Quality Recticel Print Service"
3. Click "Remove"
#### Remove Windows Service
```bash
# Run as Administrator
python service_manager.py uninstall
```
#### Remove Firewall Rule
```bash
netsh advfirewall firewall delete rule name="Quality Recticel Print Service"
```
## 📞 Support Information
### API Endpoints Reference
| Endpoint | Method | Purpose |
|----------|--------|---------|
| `/health` | GET | Service health check |
| `/printers` | GET | List available printers |
| `/print/pdf` | POST | Print PDF from URL |
| `/print/silent` | POST | Silent print with metadata |
### Request Examples
**Silent Print Request**:
```json
POST /print/silent
{
"pdf_url": "http://localhost:5000/generate_labels_pdf/123",
"printer_name": "default",
"copies": 1,
"silent": true,
"order_id": "123",
"quantity": "10"
}
```
**Expected Response**:
```json
{
"success": true,
"message": "Print job sent successfully",
"job_id": "print_20250921_103000",
"printer": "HP LaserJet Pro",
"timestamp": "2025-09-21T10:30:00"
}
```
## 📚 Technical Details
### Service Architecture
- **Framework**: Flask (Python)
- **Service Type**: Windows Service (pywin32)
- **Communication**: HTTP REST API + Native Messaging
- **Security**: Localhost binding only (127.0.0.1:8765)
### Chrome Extension Architecture
- **Manifest Version**: 3
- **Service Worker**: Handles background print requests
- **Content Script**: Integrates with Quality Recticel web pages
- **Native Messaging**: Communicates with Windows service
### Security Considerations
- Service only accepts local connections (localhost)
- No external network access required
- Chrome extension runs in sandboxed environment
- Windows service runs with system privileges (required for printing)
---
## 📋 Quick Start Checklist
- [ ] Download `windows_print_service` folder
- [ ] Right-click `install_service.bat` → "Run as administrator"
- [ ] Install Chrome extension from `chrome_extension` folder
- [ ] Verify service at `http://localhost:8765/health`
- [ ] Test printing from Quality Recticel web application
**Installation Time**: ~5 minutes
**User Training Required**: Minimal (automatic detection and fallback)
**Maintenance**: Zero (auto-starts with Windows)
For additional support, check the log files and diagnostic commands listed above.

View File

@@ -1,69 +0,0 @@
# 🚀 Quality Recticel Print Service - Quick Setup
## 📦 What You Get
- **Silent PDF Printing** - No more manual downloads!
- **Automatic Detection** - Smart fallback when service unavailable
- **Zero Configuration** - Works out of the box
## ⚡ 2-Minute Installation
### Step 1: Install Windows Service
1. **Right-click** `install_service.bat`
2. Select **"Run as administrator"**
3. Click **"Yes"** and wait for completion
### Step 2: Install Chrome Extension
1. Open Chrome → `chrome://extensions/`
2. Enable **"Developer mode"**
3. Click **"Load unpacked"** → Select `chrome_extension` folder
### Step 3: Verify Installation
- Visit: `http://localhost:8765/health`
- Should see: `{"status": "healthy"}`
## 🎯 How It Works
| Service Status | Button Appearance | What Happens |
|---------------|-------------------|--------------|
| **Running** ✅ | 🖨️ **Print Labels (Silent)** (Green) | Direct printing |
| **Not Running** ❌ | 📄 **Generate PDF** (Blue) | PDF download |
## ⚠️ Troubleshooting
| Problem | Solution |
|---------|----------|
| **Service won't start** | Run `install_service.bat` as Administrator |
| **Chrome extension not working** | Reload extension in `chrome://extensions/` |
| **Can't connect to localhost:8765** | Check Windows Firewall (port 8765) |
| **Nothing prints** | Verify default printer is set up |
## 🔧 Management Commands
```bash
# Check service status
python service_manager.py status
# Restart service
python service_manager.py restart
# Uninstall service
python service_manager.py uninstall
```
## 📍 Important Notes
-**Auto-starts** with Windows - no manual intervention needed
- 🔒 **Local only** - service only accessible from same computer
- 🖨️ **Uses default printer** - configure your default printer in Windows
- 💾 **Don't move files** after installation - keep folder in same location
## 🆘 Quick Support
**Service API**: `http://localhost:8765`
**Health Check**: `http://localhost:8765/health`
**Printer List**: `http://localhost:8765/printers`
**Log File**: `print_service.log` (same folder as installation)
---
*Installation takes ~5 minutes • Zero maintenance required • Works with existing Quality Recticel web application*

View File

@@ -1,348 +0,0 @@
# Quality Recticel Windows Print Service
## 🏗️ Technical Architecture
Local Windows service providing REST API for silent PDF printing via Chrome extension integration.
```
┌─────────────────────────────────────────────────────────────┐
│ Quality Recticel Web App │
│ (print_module.html) │
└─────────────────────┬───────────────────────────────────────┘
│ HTTP Request
┌─────────────────────────────────────────────────────────────┐
│ Windows Print Service │
│ (localhost:8765) │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │ Flask │ │ CORS │ │ PDF Handler │ │
│ │ Server │ │ Support │ │ │ │
│ └─────────────┘ └──────────────┘ └─────────────────┘ │
└─────────────────────┬───────────────────────────────────────┘
│ Native Messaging
┌─────────────────────────────────────────────────────────────┐
│ Chrome Extension │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │ Background │ │ Content │ │ Popup │ │
│ │ Service │ │ Script │ │ UI │ │
│ │ Worker │ │ │ │ │ │
│ └─────────────┘ └──────────────┘ └─────────────────┘ │
└─────────────────────┬───────────────────────────────────────┘
│ Windows API
┌─────────────────────────────────────────────────────────────┐
│ Windows Print System │
└─────────────────────────────────────────────────────────────┘
```
## 📁 Project Structure
```
windows_print_service/
├── 📄 print_service.py # Main Flask service
├── 📄 service_manager.py # Windows service wrapper
├── 📄 install_service.bat # Installation script
├── 📄 INSTALLATION_GUIDE.md # Complete documentation
├── 📄 QUICK_SETUP.md # User quick reference
├── 📄 README.md # This file
└── 📁 chrome_extension/ # Chrome extension
├── 📄 manifest.json # Extension manifest v3
├── 📄 background.js # Service worker
├── 📄 content.js # Page content integration
├── 📄 popup.html # Extension popup UI
├── 📄 popup.js # Popup functionality
└── 📁 icons/ # Extension icons
```
## 🚀 API Endpoints
### Base URL: `http://localhost:8765`
| Endpoint | Method | Description | Request Body | Response |
|----------|--------|-------------|--------------|----------|
| `/health` | GET | Service health check | None | `{"status": "healthy", ...}` |
| `/printers` | GET | List available printers | None | `{"printers": [...]}` |
| `/print/pdf` | POST | Print PDF from URL | `{"url": "...", "printer": "..."}` | `{"success": true, ...}` |
| `/print/silent` | POST | Silent print with metadata | `{"pdf_url": "...", "order_id": "..."}` | `{"success": true, ...}` |
### Example API Usage
```javascript
// Health Check
const health = await fetch('http://localhost:8765/health');
const status = await health.json();
// Silent Print
const printRequest = {
pdf_url: 'http://localhost:5000/generate_labels_pdf/123',
printer_name: 'default',
copies: 1,
silent: true,
order_id: '123',
quantity: '10'
};
const response = await fetch('http://localhost:8765/print/silent', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(printRequest)
});
```
## 🔧 Development Setup
### Prerequisites
- Python 3.8+
- Windows 10/11
- Chrome Browser
- Administrator privileges
### Local Development
```bash
# Clone/download the project
cd windows_print_service
# Install dependencies
pip install flask flask-cors requests pywin32
# Run development server (not as service)
python print_service.py
# Install as Windows service
python service_manager.py install
# Service management
python service_manager.py start
python service_manager.py stop
python service_manager.py restart
python service_manager.py uninstall
```
### Chrome Extension Development
```bash
# Load extension in Chrome
chrome://extensions/ → Developer mode ON → Load unpacked
# Debug extension
chrome://extensions/ → Details → Background page (for service worker)
chrome://extensions/ → Details → Inspect views (for popup)
```
## 📋 Configuration
### Service Configuration (`print_service.py`)
```python
class WindowsPrintService:
def __init__(self, host='127.0.0.1', port=8765):
self.host = host # Localhost binding only
self.port = port # Service port
self.app = Flask(__name__)
```
### Chrome Extension Permissions (`manifest.json`)
```json
{
"permissions": [
"printing", // Access to printer API
"nativeMessaging", // Communication with Windows service
"activeTab", // Current tab access
"storage" // Extension settings storage
]
}
```
## 🔄 Integration Flow
### 1. Service Detection
```javascript
// Web page detects service availability
const isServiceAvailable = await checkServiceHealth();
updatePrintButton(isServiceAvailable);
```
### 2. Print Request Flow
```
User clicks print → Web app → Windows service → Chrome extension → Printer
```
### 3. Fallback Mechanism
```
Service unavailable → Fallback to PDF download → Manual printing
```
## 🛠️ Customization
### Adding New Print Options
```python
# In print_service.py
@app.route('/print/custom', methods=['POST'])
def print_custom():
data = request.json
# Custom print logic here
return jsonify({'success': True})
```
### Modifying Chrome Extension
```javascript
// In background.js - Add new message handler
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'CUSTOM_PRINT') {
// Custom print logic
}
});
```
### Web Application Integration
```javascript
// In print_module.html - Modify print function
async function customPrintFunction(orderId) {
const response = await fetch('http://localhost:8765/print/custom', {
method: 'POST',
body: JSON.stringify({orderId, customOptions: {...}})
});
}
```
## 🧪 Testing
### Unit Tests (Future Enhancement)
```python
# test_print_service.py
import unittest
from print_service import WindowsPrintService
class TestPrintService(unittest.TestCase):
def test_health_endpoint(self):
# Test implementation
pass
```
### Manual Testing Checklist
- [ ] Service starts automatically on Windows boot
- [ ] API endpoints respond correctly
- [ ] Chrome extension loads without errors
- [ ] Print jobs execute successfully
- [ ] Fallback works when service unavailable
- [ ] Firewall allows port 8765 traffic
## 📊 Monitoring & Logging
### Log Files
- **Service Log**: `print_service.log` (Flask application logs)
- **Windows Event Log**: Windows Services logs
- **Chrome DevTools**: Extension console logs
### Health Monitoring
```python
# Monitor service health
import requests
try:
response = requests.get('http://localhost:8765/health', timeout=5)
if response.status_code == 200:
print("✅ Service healthy")
except:
print("❌ Service unavailable")
```
## 🔒 Security Considerations
### Network Security
- **Localhost Only**: Service binds to 127.0.0.1 (no external access)
- **No Authentication**: Relies on local machine security
- **Firewall Rule**: Port 8765 opened for local connections only
### Chrome Extension Security
- **Manifest V3**: Latest security standards
- **Minimal Permissions**: Only necessary permissions requested
- **Sandboxed**: Runs in Chrome's security sandbox
### Windows Service Security
- **System Service**: Runs with appropriate Windows service privileges
- **Print Permissions**: Requires printer access (normal for print services)
## 🚀 Deployment
### Production Deployment
1. **Package Distribution**:
```bash
# Create deployment package
zip -r quality_recticel_print_service.zip windows_print_service/
```
2. **Installation Script**: Use `install_service.bat` for end users
3. **Group Policy Deployment**: Deploy Chrome extension via enterprise policies
### Enterprise Considerations
- **Silent Installation**: Modify `install_service.bat` for unattended install
- **Registry Deployment**: Pre-configure Chrome extension registry entries
- **Network Policies**: Ensure firewall policies allow localhost:8765
## 📚 Dependencies
### Python Packages
```
flask>=2.3.0 # Web framework
flask-cors>=4.0.0 # CORS support
requests>=2.31.0 # HTTP client
pywin32>=306 # Windows service integration
```
### Chrome APIs
- `chrome.printing.*` - Printing functionality
- `chrome.runtime.*` - Extension messaging
- `chrome.nativeMessaging.*` - Native app communication
## 🐛 Debugging
### Common Debug Commands
```bash
# Check service status
sc query QualityRecticelPrintService
# Test API manually
curl http://localhost:8765/health
# Check listening ports
netstat -an | findstr :8765
# View service logs
type print_service.log
```
### Chrome Extension Debugging
```javascript
// In background.js - Add debug logging
console.log('Print request received:', message);
// In popup.js - Test API connection
fetch('http://localhost:8765/health')
.then(r => r.json())
.then(data => console.log('Service status:', data));
```
---
## 📄 License & Support
**Project**: Quality Recticel Print Service
**Version**: 1.0
**Compatibility**: Windows 10/11, Chrome 88+
**Maintenance**: Zero-maintenance after installation
For technical support, refer to `INSTALLATION_GUIDE.md` troubleshooting section.

View File

@@ -0,0 +1,539 @@
// FG Quality specific JavaScript - Standalone version
document.addEventListener('DOMContentLoaded', function() {
// Prevent conflicts with main script.js by removing existing listeners
console.log('FG Quality JavaScript loaded');
const reportButtons = document.querySelectorAll('.report-btn');
const reportTable = document.getElementById('report-table');
const reportTitle = document.getElementById('report-title');
const exportCsvButton = document.getElementById('export-csv');
// Calendar elements
const calendarModal = document.getElementById('calendar-modal');
const dateRangeModal = document.getElementById('date-range-modal');
const selectDayReport = document.getElementById('select-day-report');
const selectDayDefectsReport = document.getElementById('select-day-defects-report');
const dateRangeReport = document.getElementById('date-range-report');
const dateRangeDefectsReport = document.getElementById('date-range-defects-report');
let currentReportType = null;
let currentDate = new Date();
let selectedDate = null;
// Clear any existing event listeners by cloning elements
function clearExistingListeners() {
if (selectDayReport) {
const newSelectDayReport = selectDayReport.cloneNode(true);
selectDayReport.parentNode.replaceChild(newSelectDayReport, selectDayReport);
}
if (selectDayDefectsReport) {
const newSelectDayDefectsReport = selectDayDefectsReport.cloneNode(true);
selectDayDefectsReport.parentNode.replaceChild(newSelectDayDefectsReport, selectDayDefectsReport);
}
if (dateRangeReport) {
const newDateRangeReport = dateRangeReport.cloneNode(true);
dateRangeReport.parentNode.replaceChild(newDateRangeReport, dateRangeReport);
}
if (dateRangeDefectsReport) {
const newDateRangeDefectsReport = dateRangeDefectsReport.cloneNode(true);
dateRangeDefectsReport.parentNode.replaceChild(newDateRangeDefectsReport, dateRangeDefectsReport);
}
}
// Clear existing listeners first
clearExistingListeners();
// Re-get elements after cloning
const newSelectDayReport = document.getElementById('select-day-report');
const newSelectDayDefectsReport = document.getElementById('select-day-defects-report');
const newDateRangeReport = document.getElementById('date-range-report');
const newDateRangeDefectsReport = document.getElementById('date-range-defects-report');
// Add event listeners to report buttons
reportButtons.forEach(button => {
const reportType = button.getAttribute('data-report');
if (reportType) {
// Clone to remove existing listeners
const newButton = button.cloneNode(true);
button.parentNode.replaceChild(newButton, button);
newButton.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
console.log('FG Report button clicked:', reportType);
fetchFGReportData(reportType);
});
}
});
// Calendar-based report buttons with FG-specific handlers
if (newSelectDayReport) {
newSelectDayReport.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
console.log('FG Select Day Report clicked');
currentReportType = '6';
showCalendarModal();
});
}
if (newSelectDayDefectsReport) {
newSelectDayDefectsReport.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
console.log('FG Select Day Defects Report clicked');
currentReportType = '8';
showCalendarModal();
});
}
if (newDateRangeReport) {
newDateRangeReport.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
console.log('FG Date Range Report clicked');
currentReportType = '7';
showDateRangeModal();
});
}
if (newDateRangeDefectsReport) {
newDateRangeDefectsReport.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
console.log('FG Date Range Defects Report clicked');
currentReportType = '9';
showDateRangeModal();
});
}
// Function to fetch FG report data
function fetchFGReportData(reportType) {
const url = `/get_fg_report_data?report=${reportType}`;
console.log('Fetching FG data from:', url);
reportTitle.textContent = 'Loading FG data...';
fetch(url)
.then(response => response.json())
.then(data => {
console.log('FG Report data received:', data);
if (data.error) {
reportTitle.textContent = data.error;
return;
}
populateFGTable(data);
updateReportTitle(reportType);
})
.catch(error => {
console.error('Error fetching FG report data:', error);
reportTitle.textContent = 'Error loading FG data.';
});
}
// Function to fetch FG report data for specific dates
function fetchFGDateReportData(reportType, date, startDate = null, endDate = null) {
let url = `/generate_fg_report?report=${reportType}`;
if (date) {
url += `&date=${date}`;
}
if (startDate && endDate) {
url += `&start_date=${startDate}&end_date=${endDate}`;
}
console.log('Fetching FG date report from:', url);
reportTitle.textContent = 'Loading FG data...';
fetch(url)
.then(response => response.json())
.then(data => {
console.log('FG Date report data received:', data);
if (data.error) {
reportTitle.textContent = data.error;
return;
}
populateFGTable(data);
updateDateReportTitle(reportType, date, startDate, endDate);
})
.catch(error => {
console.error('Error fetching FG date report data:', error);
reportTitle.textContent = 'Error loading FG data.';
});
}
// Function to populate the table with FG data
function populateFGTable(data) {
const thead = reportTable.querySelector('thead tr');
const tbody = reportTable.querySelector('tbody');
// Clear existing content
thead.innerHTML = '';
tbody.innerHTML = '';
// Add headers
if (data.headers && data.headers.length > 0) {
data.headers.forEach(header => {
const th = document.createElement('th');
th.textContent = header;
thead.appendChild(th);
});
}
// Add rows
if (data.rows && data.rows.length > 0) {
data.rows.forEach(row => {
const tr = document.createElement('tr');
row.forEach(cell => {
const td = document.createElement('td');
td.textContent = cell || '';
tr.appendChild(td);
});
tbody.appendChild(tr);
});
} else {
// Show no data message
const tr = document.createElement('tr');
const td = document.createElement('td');
td.colSpan = data.headers ? data.headers.length : 1;
td.textContent = data.message || 'No FG data found for the selected criteria.';
td.style.textAlign = 'center';
td.style.fontStyle = 'italic';
td.style.padding = '20px';
tr.appendChild(td);
tbody.appendChild(tr);
}
}
// Function to update report title based on type
function updateReportTitle(reportType) {
const titles = {
'1': 'Daily Complete FG Orders Report',
'2': '5-Day Complete FG Orders Report',
'3': 'FG Items with Defects for Current Day',
'4': 'FG Items with Defects for Last 5 Days',
'5': 'Complete FG Database Report'
};
reportTitle.textContent = titles[reportType] || 'FG Quality Report';
}
// Function to update report title for date-based reports
function updateDateReportTitle(reportType, date, startDate, endDate) {
const titles = {
'6': `FG Daily Report for ${date}`,
'7': `FG Date Range Report (${startDate} to ${endDate})`,
'8': `FG Quality Defects Report for ${date}`,
'9': `FG Quality Defects Range Report (${startDate} to ${endDate})`
};
reportTitle.textContent = titles[reportType] || 'FG Quality Report';
}
// Calendar functionality
function showCalendarModal() {
if (calendarModal) {
calendarModal.style.display = 'block';
generateCalendar();
}
}
function hideCalendarModal() {
if (calendarModal) {
calendarModal.style.display = 'none';
selectedDate = null;
updateConfirmButton();
}
}
function showDateRangeModal() {
if (dateRangeModal) {
dateRangeModal.style.display = 'block';
const today = new Date().toISOString().split('T')[0];
document.getElementById('start-date').value = today;
document.getElementById('end-date').value = today;
}
}
function hideDataRangeModal() {
if (dateRangeModal) {
dateRangeModal.style.display = 'none';
}
}
function generateCalendar() {
const calendarDays = document.getElementById('calendar-days');
const monthYear = document.getElementById('calendar-month-year');
if (!calendarDays || !monthYear) return;
const year = currentDate.getFullYear();
const month = currentDate.getMonth();
monthYear.textContent = `${currentDate.toLocaleString('default', { month: 'long' })} ${year}`;
// Clear previous days
calendarDays.innerHTML = '';
// Get first day of month and number of days
const firstDay = new Date(year, month, 1).getDay();
const daysInMonth = new Date(year, month + 1, 0).getDate();
// Add empty cells for previous month
for (let i = 0; i < firstDay; i++) {
const emptyDay = document.createElement('div');
emptyDay.className = 'calendar-day empty';
calendarDays.appendChild(emptyDay);
}
// Add days of current month
for (let day = 1; day <= daysInMonth; day++) {
const dayElement = document.createElement('div');
dayElement.className = 'calendar-day';
dayElement.textContent = day;
// Check if it's today
const today = new Date();
if (year === today.getFullYear() && month === today.getMonth() && day === today.getDate()) {
dayElement.classList.add('today');
}
dayElement.addEventListener('click', () => {
// Remove previous selection
document.querySelectorAll('.calendar-day.selected').forEach(el => {
el.classList.remove('selected');
});
// Add selection to clicked day
dayElement.classList.add('selected');
// Set selected date
selectedDate = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
console.log('FG Calendar date selected:', selectedDate);
updateConfirmButton();
});
calendarDays.appendChild(dayElement);
}
}
function updateConfirmButton() {
const confirmButton = document.getElementById('confirm-date');
if (confirmButton) {
confirmButton.disabled = !selectedDate;
}
}
// Calendar navigation
const prevMonthBtn = document.getElementById('prev-month');
const nextMonthBtn = document.getElementById('next-month');
if (prevMonthBtn) {
// Clone to remove existing listeners
const newPrevBtn = prevMonthBtn.cloneNode(true);
prevMonthBtn.parentNode.replaceChild(newPrevBtn, prevMonthBtn);
newPrevBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
currentDate.setMonth(currentDate.getMonth() - 1);
generateCalendar();
});
}
if (nextMonthBtn) {
// Clone to remove existing listeners
const newNextBtn = nextMonthBtn.cloneNode(true);
nextMonthBtn.parentNode.replaceChild(newNextBtn, nextMonthBtn);
newNextBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
currentDate.setMonth(currentDate.getMonth() + 1);
generateCalendar();
});
}
// Calendar modal buttons
const cancelDateBtn = document.getElementById('cancel-date');
const confirmDateBtn = document.getElementById('confirm-date');
if (cancelDateBtn) {
// Clone to remove existing listeners
const newCancelBtn = cancelDateBtn.cloneNode(true);
cancelDateBtn.parentNode.replaceChild(newCancelBtn, cancelDateBtn);
newCancelBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
hideCalendarModal();
});
}
if (confirmDateBtn) {
// Clone to remove existing listeners
const newConfirmBtn = confirmDateBtn.cloneNode(true);
confirmDateBtn.parentNode.replaceChild(newConfirmBtn, confirmDateBtn);
newConfirmBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
console.log('FG Calendar confirm clicked with date:', selectedDate, 'report type:', currentReportType);
if (selectedDate && currentReportType) {
fetchFGDateReportData(currentReportType, selectedDate);
hideCalendarModal();
}
});
}
// Date range modal buttons
const cancelDateRangeBtn = document.getElementById('cancel-date-range');
const confirmDateRangeBtn = document.getElementById('confirm-date-range');
if (cancelDateRangeBtn) {
// Clone to remove existing listeners
const newCancelRangeBtn = cancelDateRangeBtn.cloneNode(true);
cancelDateRangeBtn.parentNode.replaceChild(newCancelRangeBtn, cancelDateRangeBtn);
newCancelRangeBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
hideDataRangeModal();
});
}
if (confirmDateRangeBtn) {
// Clone to remove existing listeners
const newConfirmRangeBtn = confirmDateRangeBtn.cloneNode(true);
confirmDateRangeBtn.parentNode.replaceChild(newConfirmRangeBtn, confirmDateRangeBtn);
newConfirmRangeBtn.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const startDate = document.getElementById('start-date').value;
const endDate = document.getElementById('end-date').value;
console.log('FG Date range confirm clicked:', startDate, 'to', endDate, 'report type:', currentReportType);
if (startDate && endDate && currentReportType) {
fetchFGDateReportData(currentReportType, null, startDate, endDate);
hideDataRangeModal();
}
});
}
// Enable/disable date range confirm button
const startDateInput = document.getElementById('start-date');
const endDateInput = document.getElementById('end-date');
function updateDateRangeConfirmButton() {
const confirmBtn = document.getElementById('confirm-date-range');
if (confirmBtn && startDateInput && endDateInput) {
confirmBtn.disabled = !startDateInput.value || !endDateInput.value;
}
}
if (startDateInput) {
startDateInput.addEventListener('change', updateDateRangeConfirmButton);
}
if (endDateInput) {
endDateInput.addEventListener('change', updateDateRangeConfirmButton);
}
// Close modals when clicking outside
window.addEventListener('click', (event) => {
if (event.target === calendarModal) {
hideCalendarModal();
}
if (event.target === dateRangeModal) {
hideDataRangeModal();
}
});
// Close modals with X button
document.querySelectorAll('.close-modal').forEach(closeBtn => {
// Clone to remove existing listeners
const newCloseBtn = closeBtn.cloneNode(true);
closeBtn.parentNode.replaceChild(newCloseBtn, closeBtn);
newCloseBtn.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
const modal = event.target.closest('.modal');
if (modal) {
modal.style.display = 'none';
}
});
});
// Export functionality
if (exportCsvButton) {
exportCsvButton.addEventListener('click', () => {
const rows = reportTable.querySelectorAll('tr');
if (rows.length === 0) {
alert('No FG data available to export.');
return;
}
const reportTitleText = reportTitle.textContent.trim();
const filename = `${reportTitleText.replace(/\s+/g, '_')}.csv`;
exportTableToCSV(filename);
});
}
// Export to CSV function
function exportTableToCSV(filename) {
const table = reportTable;
const rows = Array.from(table.querySelectorAll('tr'));
const csvContent = rows.map(row => {
const cells = Array.from(row.querySelectorAll('th, td'));
return cells.map(cell => {
let text = cell.textContent.trim();
// Escape quotes and wrap in quotes if necessary
if (text.includes(',') || text.includes('"') || text.includes('\n')) {
text = '"' + text.replace(/"/g, '""') + '"';
}
return text;
}).join(',');
}).join('\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
const url = URL.createObjectURL(blob);
link.setAttribute('href', url);
link.setAttribute('download', filename);
link.style.visibility = 'hidden';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
// Test Database Button
const testDatabaseBtn = document.getElementById('test-database');
if (testDatabaseBtn) {
testDatabaseBtn.addEventListener('click', () => {
console.log('Testing FG database connection...');
reportTitle.textContent = 'Testing FG Database Connection...';
fetch('/test_fg_database')
.then(response => response.json())
.then(data => {
console.log('FG Database test results:', data);
if (data.success) {
reportTitle.textContent = `FG Database Test Results - ${data.total_records} records found`;
// Show alert with summary
alert(`FG Database Test Complete!\n\nConnection: ${data.database_connection}\nTable exists: ${data.table_exists}\nTotal records: ${data.total_records}\nMessage: ${data.message}`);
} else {
reportTitle.textContent = 'FG Database Test Failed';
alert(`FG Database test failed: ${data.message}`);
}
})
.catch(error => {
console.error('FG Database test error:', error);
reportTitle.textContent = 'Error testing FG database.';
alert('Error testing FG database connection.');
});
});
}
console.log('FG Quality JavaScript setup complete');
});

View File

@@ -1,35 +0,0 @@
QZ TRAY LIBRARY PATCH NOTES
===========================
Version: 2.2.4 (patched for custom QZ Tray with pairing key authentication)
Date: October 2, 2025
CHANGES MADE:
-------------
1. Line ~387: Commented out certificate sending
- Original: _qz.websocket.connection.sendData({ certificate: cert, promise: openPromise });
- Patched: openPromise.resolve(); (resolves immediately without sending certificate)
2. Line ~391-403: Bypassed certificate retrieval
- Original: Called _qz.security.callCert() to get certificate from user
- Patched: Directly calls sendCert(null) without trying to get certificate
3. Comments added to indicate patches
REASON FOR PATCHES:
------------------
The custom QZ Tray server has certificate validation COMPLETELY DISABLED.
It uses ONLY pairing key (HMAC) authentication instead of certificates.
The original qz-tray.js library expects certificate-based authentication and
fails when the server doesn't respond to certificate requests.
COMPATIBILITY:
-------------
- Works with custom QZ Tray server (forked version with certificate validation disabled)
- NOT compatible with standard QZ Tray servers
- Connects to both ws://localhost:8181 and wss://localhost:8182
- Authentication handled by server-side pairing keys
BACKUP:
-------
Original unpatched version saved as: qz-tray.js.backup

View File

@@ -4,11 +4,16 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Flask App{% endblock %}</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Font Awesome for icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<!-- Base CSS for common styles -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/base.css') }}">
<!-- Legacy CSS for backward compatibility (temporarily) -->
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<!-- Page-specific CSS -->
{% block extra_css %}{% endblock %}
{% block head %}{% endblock %}
</head>
<body class="light-mode">
@@ -56,5 +61,8 @@
{% if request.endpoint != 'main.fg_quality' %}
<script src="{{ url_for('static', filename='script.js') }}"></script>
{% endif %}
<!-- Bootstrap JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

View File

@@ -0,0 +1,440 @@
{% extends "base.html" %}
{% block title %}FG Quality Module{% endblock %}
{% block content %}
<div class="scan-container">
<!-- Reports Card -->
<div class="card report-form-card">
<h3>FG Quality Reports</h3>
<!-- Reports List with Label-Button Layout -->
<div class="reports-grid">
<div class="form-centered">
<label class="report-description">Daily report will export all FG orders scanned at quality scanning points</label>
<button class="btn report-btn" data-report="1">Daily Complete FG Orders Report</button>
</div>
<div class="form-centered">
<label class="report-description">Select day for daily FG report will export all orders scanned at quality scanning points</label>
<button class="btn report-btn" id="select-day-report">Select Day Daily FG Report</button>
</div>
<div class="form-centered">
<label class="report-description">Select date range for custom FG report - from start date 00:00 to end date 23:59</label>
<button class="btn report-btn" id="date-range-report">Date Range FG Report</button>
</div>
<div class="form-centered">
<label class="report-description">5-day report will export all FG orders scanned at quality scanning points</label>
<button class="btn report-btn" data-report="2">5-Day Complete FG Orders Report</button>
</div>
<div class="form-centered">
<label class="report-description">Report on FG items with quality issues for the current day</label>
<button class="btn report-btn" data-report="3">Report on FG Items with Defects for the Current Day</button>
</div>
<div class="form-centered">
<label class="report-description">Select specific day for FG quality defects report - items with quality issues</label>
<button class="btn report-btn" id="select-day-defects-report">Select Day FG Quality Defects Report</button>
</div>
<div class="form-centered">
<label class="report-description">Select date range for FG quality defects report - items with quality issues between two dates</label>
<button class="btn report-btn" id="date-range-defects-report">Date Range FG Quality Defects Report</button>
</div>
<div class="form-centered">
<label class="report-description">Report on FG items with quality issues for the last 5 days</label>
<button class="btn report-btn" data-report="4">Report on FG Items with Defects for the Last 5 Days</button>
</div>
<div class="form-centered">
<label class="report-description">Report all FG entries from the database</label>
<button class="btn report-btn" data-report="5">Report FG Database</button>
</div>
</div>
<!-- Separator -->
<div class="report-separator"></div>
<!-- Export Section -->
<div class="export-section">
<div class="form-centered last-buttons">
<label class="export-description">Export current report as:</label>
<div class="button-row">
<button class="btn export-btn" id="export-csv">Export CSV</button>
<!-- <button class="btn export-btn" id="export-excel">Export excell</button> -->
{% if session.get('role') == 'superadmin' %}
<button class="btn export-btn test-db-btn" id="test-database">Test FG Database</button>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Data Display Card -->
<div class="card report-table-card">
<h3 id="report-title">No data to display, please select a report.</h3>
<div class="report-table-container">
<table class="scan-table" id="report-table">
<thead>
<tr>
<!-- Table headers will be dynamically populated -->
</tr>
</thead>
<tbody>
<!-- Table data will be dynamically populated -->
</tbody>
</table>
</div>
</div>
</div>
<!-- Calendar Popup Modal -->
<div id="calendar-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h4>Select Date for Daily FG Report</h4>
<span class="close-modal">&times;</span>
</div>
<div class="modal-body">
<div class="calendar-container">
<div class="calendar-header">
<button id="prev-month" class="calendar-nav">&lt;</button>
<h3 id="calendar-month-year"></h3>
<button id="next-month" class="calendar-nav">&gt;</button>
</div>
<div class="calendar-grid">
<div class="calendar-weekdays">
<div>Sun</div>
<div>Mon</div>
<div>Tue</div>
<div>Wed</div>
<div>Thu</div>
<div>Fri</div>
<div>Sat</div>
</div>
<div class="calendar-days" id="calendar-days">
<!-- Days will be populated by JavaScript -->
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" id="cancel-date">Cancel</button>
<button class="btn btn-primary" id="confirm-date" disabled>Generate Report</button>
</div>
</div>
</div>
<!-- Date Range Popup Modal -->
<div id="date-range-modal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h4>Select Date Range for FG Report</h4>
<span class="close-modal" id="close-date-range">&times;</span>
</div>
<div class="modal-body">
<div class="date-range-container">
<div class="date-input-group">
<label for="start-date">Start Date:</label>
<input type="date" id="start-date" class="date-input" />
<small class="date-help">Report will include data from 00:00:00</small>
</div>
<div class="date-input-group">
<label for="end-date">End Date:</label>
<input type="date" id="end-date" class="date-input" />
<small class="date-help">Report will include data until 23:59:59</small>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" id="cancel-date-range">Cancel</button>
<button class="btn btn-primary" id="confirm-date-range" disabled>Generate Report</button>
</div>
</div>
</div>
{% endblock %}
{% block head %}
<script>
// Debug check - this will run immediately when the page loads
console.log('FG Quality page loaded - checking script conflicts');
window.addEventListener('DOMContentLoaded', function() {
console.log('FG Quality DOM loaded');
// Add a visible indicator that our script loaded
setTimeout(() => {
const titleElement = document.getElementById('report-title');
if (titleElement && titleElement.textContent === 'No data to display, please select a report.') {
titleElement.textContent = '🟢 FG Quality Module Ready - Select a report';
}
}, 100);
});
</script>
<script src="{{ url_for('static', filename='fg_quality.js') }}"></script>
<script>
// Theme functionality for FG Quality page
document.addEventListener('DOMContentLoaded', function() {
const body = document.body;
const themeToggleButton = document.getElementById('theme-toggle');
// Helper function to update the theme toggle button text
function updateThemeToggleButtonText() {
if (body.classList.contains('dark-mode')) {
themeToggleButton.textContent = 'Change to Light Mode';
} else {
themeToggleButton.textContent = 'Change to Dark Mode';
}
}
// Check and apply the saved theme from localStorage
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
body.classList.toggle('dark-mode', savedTheme === 'dark');
}
// Update the button text based on the current theme
if (themeToggleButton) {
updateThemeToggleButtonText();
// Toggle the theme on button click
themeToggleButton.addEventListener('click', () => {
const isDarkMode = body.classList.toggle('dark-mode');
localStorage.setItem('theme', isDarkMode ? 'dark' : 'light');
updateThemeToggleButtonText(); // Update the button text after toggling
});
}
});
// Override script - runs after both script.js and fg_quality.js
window.addEventListener('DOMContentLoaded', function() {
// Wait a bit to ensure all scripts have loaded
setTimeout(() => {
console.log('🔧 FG Quality Override Script - Fixing event handlers');
// Override the problematic calendar functionality
const selectDayReport = document.getElementById('select-day-report');
const selectDayDefectsReport = document.getElementById('select-day-defects-report');
const dateRangeReport = document.getElementById('date-range-report');
const dateRangeDefectsReport = document.getElementById('date-range-defects-report');
let currentFGReportType = null;
let selectedFGDate = null;
// Clear and re-add event listeners with FG-specific functionality
if (selectDayReport) {
// Remove all existing listeners by cloning
const newSelectDayReport = selectDayReport.cloneNode(true);
selectDayReport.parentNode.replaceChild(newSelectDayReport, selectDayReport);
newSelectDayReport.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
console.log('🎯 FG Select Day Report clicked');
currentFGReportType = '6';
showFGCalendarModal();
});
}
if (dateRangeReport) {
const newDateRangeReport = dateRangeReport.cloneNode(true);
dateRangeReport.parentNode.replaceChild(newDateRangeReport, dateRangeReport);
newDateRangeReport.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
console.log('🎯 FG Date Range Report clicked');
currentFGReportType = '7';
showFGDateRangeModal();
});
}
// FG-specific calendar modal functionality
function showFGCalendarModal() {
const calendarModal = document.getElementById('calendar-modal');
if (calendarModal) {
calendarModal.style.display = 'block';
generateFGCalendar();
}
}
function showFGDateRangeModal() {
const dateRangeModal = document.getElementById('date-range-modal');
if (dateRangeModal) {
dateRangeModal.style.display = 'block';
const today = new Date().toISOString().split('T')[0];
document.getElementById('start-date').value = today;
document.getElementById('end-date').value = today;
}
}
function generateFGCalendar() {
const calendarDays = document.getElementById('calendar-days');
const monthYear = document.getElementById('calendar-month-year');
if (!calendarDays || !monthYear) return;
const currentDate = new Date();
const year = currentDate.getFullYear();
const month = currentDate.getMonth();
monthYear.textContent = `${currentDate.toLocaleString('default', { month: 'long' })} ${year}`;
// Clear previous days
calendarDays.innerHTML = '';
// Get first day of month and number of days
const firstDay = new Date(year, month, 1).getDay();
const daysInMonth = new Date(year, month + 1, 0).getDate();
// Add empty cells for previous month
for (let i = 0; i < firstDay; i++) {
const emptyDay = document.createElement('div');
emptyDay.className = 'calendar-day empty';
calendarDays.appendChild(emptyDay);
}
// Add days of current month
for (let day = 1; day <= daysInMonth; day++) {
const dayElement = document.createElement('div');
dayElement.className = 'calendar-day';
dayElement.textContent = day;
// Highlight October 15 as it has FG data
if (month === 9 && day === 15) { // October is month 9
dayElement.style.backgroundColor = '#e3f2fd';
dayElement.style.border = '2px solid #2196f3';
dayElement.title = 'FG data available';
}
dayElement.addEventListener('click', () => {
// Remove previous selection
document.querySelectorAll('.calendar-day.selected').forEach(el => {
el.classList.remove('selected');
});
// Add selection to clicked day
dayElement.classList.add('selected');
// Set selected date
selectedFGDate = `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
console.log('🗓️ FG Calendar date selected:', selectedFGDate);
// Enable confirm button
const confirmButton = document.getElementById('confirm-date');
if (confirmButton) {
confirmButton.disabled = false;
}
});
calendarDays.appendChild(dayElement);
}
}
// Override confirm button
const confirmDateBtn = document.getElementById('confirm-date');
if (confirmDateBtn) {
const newConfirmBtn = confirmDateBtn.cloneNode(true);
confirmDateBtn.parentNode.replaceChild(newConfirmBtn, confirmDateBtn);
newConfirmBtn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
console.log('✅ FG Calendar confirm clicked:', selectedFGDate, 'Report type:', currentFGReportType);
if (selectedFGDate && currentFGReportType) {
const url = `/generate_fg_report?report=${currentFGReportType}&date=${selectedFGDate}`;
console.log('🚀 Calling FG endpoint:', url);
document.getElementById('report-title').textContent = 'Loading FG data...';
fetch(url)
.then(response => response.json())
.then(data => {
console.log('📊 FG Data received:', data);
const table = document.getElementById('report-table');
const thead = table.querySelector('thead tr');
const tbody = table.querySelector('tbody');
// Clear existing content
thead.innerHTML = '';
tbody.innerHTML = '';
// Add headers
if (data.headers) {
data.headers.forEach(header => {
const th = document.createElement('th');
th.textContent = header;
thead.appendChild(th);
});
}
// Add rows
if (data.rows && data.rows.length > 0) {
data.rows.forEach(row => {
const tr = document.createElement('tr');
row.forEach(cell => {
const td = document.createElement('td');
td.textContent = cell || '';
tr.appendChild(td);
});
tbody.appendChild(tr);
});
document.getElementById('report-title').textContent = `FG Daily Report for ${selectedFGDate} (${data.rows.length} records)`;
} else {
const tr = document.createElement('tr');
const td = document.createElement('td');
td.colSpan = data.headers ? data.headers.length : 1;
td.textContent = data.message || 'No FG data found';
td.style.textAlign = 'center';
tr.appendChild(td);
tbody.appendChild(tr);
document.getElementById('report-title').textContent = `FG Daily Report for ${selectedFGDate} - No data`;
}
})
.catch(error => {
console.error('❌ Error fetching FG data:', error);
document.getElementById('report-title').textContent = 'Error loading FG data';
});
// Hide modal
document.getElementById('calendar-modal').style.display = 'none';
}
});
}
// Override date range confirm button
const confirmDateRangeBtn = document.getElementById('confirm-date-range');
if (confirmDateRangeBtn) {
const newConfirmRangeBtn = confirmDateRangeBtn.cloneNode(true);
confirmDateRangeBtn.parentNode.replaceChild(newConfirmRangeBtn, confirmDateRangeBtn);
newConfirmRangeBtn.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
const startDate = document.getElementById('start-date').value;
const endDate = document.getElementById('end-date').value;
console.log('📅 FG Date range confirm:', startDate, 'to', endDate);
if (startDate && endDate && currentFGReportType) {
const url = `/generate_fg_report?report=${currentFGReportType}&start_date=${startDate}&end_date=${endDate}`;
console.log('🚀 Calling FG range endpoint:', url);
fetch(url)
.then(response => response.json())
.then(data => {
console.log('📊 FG Range data received:', data);
// Handle response similar to above
document.getElementById('report-title').textContent = `FG Date Range Report (${startDate} to ${endDate})`;
})
.catch(error => {
console.error('❌ Error fetching FG range data:', error);
});
// Hide modal
document.getElementById('date-range-modal').style.display = 'none';
}
});
}
console.log('✅ FG Quality Override Script - Event handlers fixed');
}, 500); // Wait 500ms to ensure all other scripts are loaded
});
</script>
{% endblock %}

View File

@@ -0,0 +1,40 @@
{% extends "base.html" %}
{% block title %}Reports Module{% endblock %}
{% block content %}
<div class="reports-container">
<h1>Reports Module</h1>
<p>Access different quality and production reporting modules for data analysis and verification.</p>
<!-- Row of evenly distributed cards -->
<div class="dashboard-container">
<!-- Card 1: Quality Reports -->
<div class="dashboard-card">
<h3>Quality Reports</h3>
<p>Access quality scanning reports and analysis for production process verification.</p>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<a href="{{ url_for('main.quality') }}" class="btn">Launch Quality Reports</a>
</div>
</div>
<!-- Card 2: FG Quality Reports -->
<div class="dashboard-card">
<h3>FG Quality Reports</h3>
<p>Finished Goods quality reports and analysis for final product verification.</p>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<a href="{{ url_for('main.fg_quality') }}" class="btn">Launch FG Quality Reports</a>
</div>
</div>
<!-- Card 3: Additional Reports (Placeholder for future expansion) -->
<div class="dashboard-card">
<h3>Additional Reports</h3>
<p>Access additional reporting modules and data analysis tools.</p>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<button class="btn" disabled style="opacity: 0.5;">Coming Soon</button>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -1,454 +0,0 @@
{% extends "base.html" %}
{% block title %}Role Permissions Management{% endblock %}
{% block head %}
<style>
.permissions-container {
max-width: 1600px;
margin: 0 auto;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.permissions-table-container {
background: white;
border-radius: 15px;
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
overflow: hidden;
margin: 0 auto 30px auto;
border: 2px solid #dee2e6;
max-width: 100%;
}
.permissions-table {
width: 100%;
border-collapse: collapse;
font-size: 14px;
margin: 0;
}
.permissions-table thead {
background: linear-gradient(135deg, #007bff, #0056b3);
color: white;
}
.permissions-table th {
padding: 15px 12px;
text-align: left;
font-weight: 600;
border-bottom: 2px solid rgba(255,255,255,0.2);
}
.permissions-table th:nth-child(1) { width: 15%; }
.permissions-table th:nth-child(2) { width: 20%; }
.permissions-table th:nth-child(3) { width: 25%; }
.permissions-table th:nth-child(4) { width: 40%; }
.permission-row {
border-bottom: 2px solid #dee2e6 !important;
transition: all 0.3s ease;
}
.permission-row:hover {
background: linear-gradient(135deg, #e3f2fd, #f0f8ff) !important;
transform: translateY(-1px) !important;
box-shadow: 0 4px 12px rgba(0,123,255,0.15) !important;
}
.role-cell, .module-cell, .page-cell, .functions-cell {
padding: 15px 12px !important;
vertical-align: top !important;
border-right: 1px solid #f1f3f4 !important;
}
.role-cell {
border-left: 4px solid #007bff !important;
}
.module-cell {
border-left: 2px solid #28a745 !important;
}
.page-cell {
border-left: 2px solid #ffc107 !important;
}
.functions-cell {
border-left: 2px solid #dc3545 !important;
}
.role-badge {
display: flex;
align-items: center;
gap: 8px;
background: #e3f2fd;
padding: 8px 12px;
border-radius: 20px;
}
.functions-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px;
}
.function-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: #f8f9fa;
border-radius: 8px;
border: 1px solid #dee2e6;
}
.function-toggle {
display: flex;
align-items: center;
cursor: pointer;
}
.toggle-slider {
position: relative;
display: inline-block;
width: 40px;
height: 20px;
background: #ccc;
border-radius: 20px;
transition: all 0.3s ease;
}
.toggle-slider::before {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: 16px;
height: 16px;
background: white;
border-radius: 50%;
transition: all 0.3s ease;
}
input[type="checkbox"]:checked + .toggle-slider {
background: #007bff;
}
input[type="checkbox"]:checked + .toggle-slider::before {
transform: translateX(20px);
}
input[type="checkbox"] {
display: none;
}
.function-text {
font-size: 12px;
font-weight: 500;
}
.role-separator, .module-separator {
background: #f8f9fa;
border-bottom: 1px solid #dee2e6;
}
.separator-line {
padding: 12px 20px;
font-weight: 600;
color: #495057;
background: linear-gradient(135deg, #e9ecef, #f8f9fa);
}
.module-badge {
padding: 8px 15px;
background: linear-gradient(135deg, #28a745, #20c997);
color: white;
border-radius: 15px;
font-weight: 500;
}
.action-buttons-container {
text-align: center;
margin: 30px 0;
}
.action-buttons {
display: flex;
justify-content: center;
gap: 20px;
flex-wrap: wrap;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: #007bff;
color: white;
}
.btn-primary:hover {
background: #0056b3;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,123,255,0.3);
}
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-secondary:hover {
background: #545b62;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(108,117,125,0.3);
}
</style>
{% endblock %}
{% block content %}
<div class="permissions-container">
<div style="text-align: center; margin-bottom: 40px;">
<h1 style="color: #2c3e50; margin-bottom: 15px; font-weight: 700; font-size: 32px;">
🔐 Role Permissions Management
</h1>
<p style="color: #6c757d; font-size: 16px;">
Configure granular access permissions for each role in the system
</p>
</div>
<!-- 4-Column Permissions Table -->
<div class="permissions-table-container">
<table class="permissions-table" id="permissionsTable">
<thead>
<tr>
<th>👤 Role Name</th>
<th>🏢 Module Name</th>
<th>📄 Page Name</th>
<th>⚙️ Functions & Permissions</th>
</tr>
</thead>
<tbody>
{% set current_role = '' %}
{% set current_module = '' %}
{% for role_name, role_data in roles.items() %}
{% for page_key, page_data in pages.items() %}
{% for section_key, section_data in page_data.sections.items() %}
<!-- Role separator row -->
{% if current_role != role_name %}
{% set current_role = role_name %}
<tr class="role-separator">
<td colspan="4">
<div class="separator-line">
<span>{{ role_data.display_name }} (Level {{ role_data.level }})</span>
</div>
</td>
</tr>
{% endif %}
<!-- Module separator -->
{% if current_module != page_key %}
{% set current_module = page_key %}
<tr class="module-separator">
<td></td>
<td colspan="3">
<div style="padding: 8px 15px;">
<span class="module-badge">{{ page_data.name }}</span>
</div>
</td>
</tr>
{% endif %}
<tr class="permission-row" data-role="{{ role_name }}" data-module="{{ page_key }}">
<td class="role-cell">
<div class="role-badge">
<span>👤</span>
<span>{{ role_data.display_name }}</span>
</div>
</td>
<td class="module-cell">
<span>{{ page_data.name }}</span>
</td>
<td class="page-cell">
<div style="display: flex; align-items: center; gap: 8px;">
<span>📋</span>
<span>{{ section_data.name }}</span>
</div>
</td>
<td class="functions-cell">
<div class="functions-grid">
{% for action in section_data.actions %}
{% set permission_key = page_key + '.' + section_key + '.' + action %}
<div class="function-item" data-permission="{{ permission_key }}" data-role="{{ role_name }}">
<label class="function-toggle">
<input type="checkbox"
data-role="{{ role_name }}"
data-page="{{ page_key }}"
data-section="{{ section_key }}"
data-action="{{ action }}"
onchange="togglePermission('{{ role_name }}', '{{ page_key }}', '{{ section_key }}', '{{ action }}', this)">
<span class="toggle-slider"></span>
</label>
<span class="function-text">{{ action_names[action] }}</span>
</div>
{% endfor %}
</div>
</td>
</tr>
{% endfor %}
{% set current_module = '' %}
{% endfor %}
{% endfor %}
</tbody>
</table>
</div>
<!-- Action Buttons -->
<div class="action-buttons-container">
<div class="action-buttons">
<button class="btn btn-secondary" onclick="resetAllToDefaults()">
<span>🔄</span>
Reset All to Defaults
</button>
<button class="btn btn-primary" onclick="saveAllPermissions()">
<span>💾</span>
Save All Changes
</button>
</div>
</div>
</div>
<script>
// Initialize data from backend
let permissions = {{ permissions_json|safe }};
let rolePermissions = {{ role_permissions_json|safe }};
// Toggle permission function
function togglePermission(roleName, pageKey, sectionKey, action, checkbox) {
const isChecked = checkbox.checked;
const permissionKey = `${pageKey}.${sectionKey}.${action}`;
// Update visual state of the function item
const functionItem = checkbox.closest('.function-item');
if (isChecked) {
functionItem.classList.remove('disabled');
} else {
functionItem.classList.add('disabled');
}
// Update data structure (flat array format)
if (!rolePermissions[roleName]) {
rolePermissions[roleName] = [];
}
if (isChecked && !rolePermissions[roleName].includes(permissionKey)) {
rolePermissions[roleName].push(permissionKey);
} else if (!isChecked) {
const index = rolePermissions[roleName].indexOf(permissionKey);
if (index > -1) {
rolePermissions[roleName].splice(index, 1);
}
}
}
// Save all permissions
function saveAllPermissions() {
// Convert flat permission arrays to nested structure for backend
const structuredPermissions = {};
for (const [roleName, permissions] of Object.entries(rolePermissions)) {
structuredPermissions[roleName] = {};
permissions.forEach(permissionKey => {
const [pageKey, sectionKey, action] = permissionKey.split('.');
if (!structuredPermissions[roleName][pageKey]) {
structuredPermissions[roleName][pageKey] = {};
}
if (!structuredPermissions[roleName][pageKey][sectionKey]) {
structuredPermissions[roleName][pageKey][sectionKey] = [];
}
structuredPermissions[roleName][pageKey][sectionKey].push(action);
});
}
fetch('/settings/save_all_role_permissions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
permissions: structuredPermissions
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('All permissions saved successfully!');
} else {
alert('Error saving permissions: ' + data.error);
}
})
.catch(error => {
alert('Error saving permissions: ' + error);
});
}
// Reset all permissions to defaults
function resetAllToDefaults() {
if (confirm('Are you sure you want to reset ALL role permissions to defaults? This will overwrite all current settings.')) {
fetch('/settings/reset_all_role_permissions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert('Error resetting permissions: ' + data.error);
}
})
.catch(error => {
alert('Error resetting permissions: ' + error);
});
}
}
// Initialize checkbox states when page loads
document.addEventListener('DOMContentLoaded', function() {
// Set initial states based on data
document.querySelectorAll('.function-item').forEach(item => {
const roleName = item.dataset.role;
const permissionKey = item.dataset.permission;
const checkbox = item.querySelector('input[type="checkbox"]');
// Check if this role has this permission
const hasPermission = rolePermissions[roleName] && rolePermissions[roleName].includes(permissionKey);
if (hasPermission) {
checkbox.checked = true;
item.classList.remove('disabled');
} else {
checkbox.checked = false;
item.classList.add('disabled');
}
});
});
</script>
{% endblock %}

View File

@@ -44,9 +44,7 @@
<a href="{{ url_for('main.user_management_simple') }}" class="btn" style="background-color: #2196f3; color: white; margin-right: 10px;">
🎯 Manage Users (Simplified)
</a>
<a href="{{ url_for('main.role_permissions') }}" class="btn" style="background-color: #6c757d; color: white;">
⚙️ Advanced Role Permissions
</a>
</div>
<small style="display: block; margin-top: 10px; color: #666;">
Recommended: Use the simplified user management for easier administration

View File

@@ -0,0 +1,890 @@
{% extends "base.html" %}
{% block title %}User Management - Simplified{% endblock %}
{% block extra_css %}
<style>
/* Theme-aware card styles */
.user-management-page .card {
text-align: left !important;
flex: none !important;
max-width: 100% !important;
padding: 0 !important;
border-radius: 5px !important;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1) !important;
}
/* Light mode card styles */
body.light-mode .user-management-page .card {
background: #fff !important;
color: #000 !important;
border: 1px solid #ddd !important;
}
body.light-mode .user-management-page .card-body {
background: #fff !important;
color: #000 !important;
padding: 1.25rem !important;
}
body.light-mode .user-management-page .card-header {
background-color: #f8f9fa !important;
color: #333 !important;
border-bottom: 1px solid #ddd !important;
padding: 0.75rem 1.25rem !important;
border-top-left-radius: 4px !important;
border-top-right-radius: 4px !important;
}
/* Dark mode card styles */
body.dark-mode .user-management-page .card {
background: #1e1e1e !important;
color: #fff !important;
border: 1px solid #444 !important;
}
body.dark-mode .user-management-page .card-body {
background: #1e1e1e !important;
color: #fff !important;
padding: 1.25rem !important;
}
body.dark-mode .user-management-page .card-header {
background-color: #2a2a2a !important;
color: #ccc !important;
border-bottom: 1px solid #444 !important;
padding: 0.75rem 1.25rem !important;
border-top-left-radius: 4px !important;
border-top-right-radius: 4px !important;
}
/* Theme-aware header text */
.user-management-page .card-header h5 {
margin: 0 !important;
font-weight: 600 !important;
}
body.light-mode .user-management-page .card-header h5 {
color: #333 !important;
}
body.dark-mode .user-management-page .card-header h5 {
color: #ccc !important;
}
/* Theme-aware role badges */
.user-role-badge {
padding: 4px 8px;
border-radius: 4px;
font-size: 0.8em;
font-weight: bold;
}
.role-superadmin { background-color: #dc3545; color: white; }
.role-admin { background-color: #fd7e14; color: white; }
.role-manager { background-color: #007bff; color: white; }
.role-worker { background-color: #28a745; color: white; }
/* Dark mode badge adjustments */
body.dark-mode .role-manager { background-color: #0056b3; }
body.dark-mode .role-worker { background-color: #1e7e34; }
/* Theme-aware module badges */
.module-badges .badge {
margin-right: 5px;
margin-bottom: 3px;
}
body.light-mode .module-badges .badge {
background-color: #007bff;
color: white;
}
body.dark-mode .module-badges .badge {
background-color: #0056b3;
color: white;
}
.form-group {
margin-bottom: 15px;
}
.module-checkboxes {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-top: 5px;
}
.module-checkbox {
display: flex;
align-items: center;
gap: 5px;
}
/* Theme-aware info panels */
.access-level-info {
border-left: 4px solid #007bff;
padding: 10px;
margin-top: 10px;
font-size: 0.9em;
}
body.light-mode .access-level-info {
background-color: #f8f9fa;
color: #333;
}
body.dark-mode .access-level-info {
background-color: #2a2a2a;
color: #ccc;
border-left-color: #0056b3;
}
.table-warning {
background-color: #fff3cd !important;
}
body.dark-mode .table-warning {
background-color: #664d03 !important;
color: #fff !important;
}
/* Colored card headers - respect theme but use accent colors */
.card-header.bg-primary {
background-color: #007bff !important;
color: white !important;
border-color: #007bff !important;
}
.card-header.bg-primary h5 {
color: white !important;
margin: 0 !important;
}
.card-header.bg-success {
background-color: #28a745 !important;
color: white !important;
border-color: #28a745 !important;
}
.card-header.bg-success h5 {
color: white !important;
margin: 0 !important;
}
/* Dark mode adjustments for colored headers */
body.dark-mode .card-header.bg-primary {
background-color: #0056b3 !important;
border-color: #0056b3 !important;
}
body.dark-mode .card-header.bg-success {
background-color: #1e7e34 !important;
border-color: #1e7e34 !important;
}
.btn-block {
width: 100%;
}
#quickModuleEdit {
border: 2px solid #198754;
border-radius: 8px;
padding: 15px;
background-color: #f8fff8;
}
/* Theme-aware table styling */
.thead-dark {
background-color: #343a40 !important;
color: white !important;
}
body.dark-mode .thead-dark {
background-color: #1a1a1a !important;
}
.user-management-page .table {
background-color: transparent !important;
}
body.light-mode .user-management-page .table {
color: #000 !important;
}
body.dark-mode .user-management-page .table {
color: #fff !important;
}
.user-management-page .table th,
.user-management-page .table td {
border-top: 1px solid #dee2e6 !important;
padding: 0.75rem !important;
vertical-align: top !important;
}
body.dark-mode .user-management-page .table th,
body.dark-mode .user-management-page .table td {
border-top: 1px solid #444 !important;
}
.user-management-page .table-striped tbody tr:nth-of-type(odd) {
background-color: rgba(0, 0, 0, 0.05) !important;
}
body.dark-mode .user-management-page .table-striped tbody tr:nth-of-type(odd) {
background-color: rgba(255, 255, 255, 0.05) !important;
}
.user-management-page .table-hover tbody tr:hover {
background-color: rgba(0, 0, 0, 0.075) !important;
cursor: pointer;
}
body.dark-mode .user-management-page .table-hover tbody tr:hover {
background-color: rgba(255, 255, 255, 0.075) !important;
}
/* Theme-aware form elements */
.user-management-page .form-control {
border: 1px solid #ced4da !important;
}
body.light-mode .user-management-page .form-control {
background-color: #fff !important;
color: #000 !important;
border-color: #ced4da !important;
}
body.dark-mode .user-management-page .form-control {
background-color: #2a2a2a !important;
color: #fff !important;
border-color: #444 !important;
}
.user-management-page .form-control:focus {
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25) !important;
}
body.light-mode .user-management-page .form-control:focus {
border-color: #80bdff !important;
}
body.dark-mode .user-management-page .form-control:focus {
border-color: #0056b3 !important;
}
.user-management-page label {
font-weight: 500 !important;
margin-bottom: 0.5rem !important;
}
body.light-mode .user-management-page label {
color: #333 !important;
}
body.dark-mode .user-management-page label {
color: #ccc !important;
}
/* Theme-aware buttons */
.user-management-page .btn-primary {
background-color: #007bff !important;
border-color: #007bff !important;
}
.user-management-page .btn-primary:hover {
background-color: #0056b3 !important;
border-color: #0056b3 !important;
}
body.dark-mode .user-management-page .btn-primary {
background-color: #0056b3 !important;
border-color: #0056b3 !important;
}
body.dark-mode .user-management-page .btn-primary:hover {
background-color: #004085 !important;
border-color: #004085 !important;
}
/* Additional theme-aware elements */
#quickModuleEdit {
border-radius: 8px;
padding: 15px;
border: 2px solid #28a745;
}
body.light-mode #quickModuleEdit {
background-color: #f8fff8;
color: #333;
}
body.dark-mode #quickModuleEdit {
background-color: #1a2f1a;
color: #ccc;
border-color: #1e7e34;
}
/* Theme-aware text colors */
body.light-mode .user-management-page h2,
body.light-mode .user-management-page p {
color: #000 !important;
}
body.dark-mode .user-management-page h2,
body.dark-mode .user-management-page p {
color: #fff !important;
}
body.light-mode .user-management-page .text-muted {
color: #6c757d !important;
}
body.dark-mode .user-management-page .text-muted {
color: #adb5bd !important;
}
/* Theme-aware select dropdown */
body.light-mode .user-management-page select.form-control {
background-color: #fff !important;
color: #000 !important;
}
body.dark-mode .user-management-page select.form-control {
background-color: #2a2a2a !important;
color: #fff !important;
}
</style>
{% endblock %}
{% block content %}
<div class="container mt-4 user-management-page">
<h2>Simplified User Management</h2>
<p class="text-muted">Manage users with the new 4-tier permission system: Superadmin → Admin → Manager → Worker</p>
<div class="row">
<!-- Create User Card -->
<div class="col-md-6">
<div class="card mb-4">
<div class="card-header bg-primary text-white">
<h5 class="mb-0">👤 Create New User</h5>
</div>
<div class="card-body">
<form id="addUserForm" method="POST" action="/create_user_simple">
<div class="form-group">
<label for="username">Username:</label>
<input type="text" id="username" name="username" class="form-control" required>
</div>
<div class="form-group">
<label for="password">Password:</label>
<input type="password" id="password" name="password" class="form-control" required>
</div>
<div class="form-group">
<label for="role">Role:</label>
<select id="role" name="role" class="form-control" required onchange="updateModuleSelection()">
<option value="">Select a role...</option>
<option value="superadmin">Superadmin - Full system access</option>
<option value="admin">Admin - Full app access (except role permissions)</option>
<option value="manager">Manager - Module-based access (can have multiple modules)</option>
<option value="worker">Worker - Limited module access (can have multiple modules)</option>
</select>
</div>
<div class="form-group" id="moduleSelection" style="display: none;">
<label>Modules:</label>
<div class="module-checkboxes">
<div class="module-checkbox">
<input type="checkbox" id="module_quality" name="modules" value="quality">
<label for="module_quality">Quality Control</label>
</div>
<div class="module-checkbox">
<input type="checkbox" id="module_warehouse" name="modules" value="warehouse">
<label for="module_warehouse">Warehouse Management</label>
</div>
<div class="module-checkbox">
<input type="checkbox" id="module_labels" name="modules" value="labels">
<label for="module_labels">Label Management</label>
</div>
</div>
<div id="accessLevelInfo" class="access-level-info" style="display: none;"></div>
</div>
<button type="submit" class="btn btn-primary btn-block">Create User</button>
</form>
</div>
</div>
</div>
<!-- Edit User Rights Card -->
<div class="col-md-6">
<div class="card mb-4">
<div class="card-header bg-success text-white">
<h5 class="mb-0">⚙️ Edit User Rights</h5>
</div>
<div class="card-body">
<div id="userRightsPanel">
<div class="text-center text-muted py-4">
<i class="fas fa-user-edit fa-3x mb-3"></i>
<p>Select a user from the table below to edit their rights and module access.</p>
</div>
<!-- Quick Module Management for Selected User -->
<div id="quickModuleEdit" style="display: none;">
<h6>Quick Module Management</h6>
<div class="mb-3">
<strong>User:</strong> <span id="selectedUserName"></span>
<br><small class="text-muted">Role: <span id="selectedUserRole"></span></small>
</div>
<div class="form-group">
<label>Current Modules:</label>
<div id="currentModules" class="module-checkboxes">
<div class="module-checkbox">
<input type="checkbox" id="quick_module_quality" name="quick_modules" value="quality">
<label for="quick_module_quality">Quality Control</label>
</div>
<div class="module-checkbox">
<input type="checkbox" id="quick_module_warehouse" name="quick_modules" value="warehouse">
<label for="quick_module_warehouse">Warehouse Management</label>
</div>
<div class="module-checkbox">
<input type="checkbox" id="quick_module_labels" name="quick_modules" value="labels">
<label for="quick_module_labels">Label Management</label>
</div>
</div>
</div>
<div class="btn-group btn-group-sm w-100">
<button type="button" class="btn btn-success" onclick="updateUserModules()">
💾 Save Module Changes
</button>
<button type="button" class="btn btn-info" onclick="showFullEditModal()">
✏️ Full Edit
</button>
<button type="button" class="btn btn-danger" onclick="deleteSelectedUser()">
🗑️ Delete User
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Current Users Table -->
<div class="card">
<div class="card-header">
<h5>👥 Current Users</h5>
</div>
<div class="card-body">
{% if users %}
<div class="table-responsive">
<table class="table table-striped table-hover">
<thead class="thead-dark">
<tr>
<th>Username</th>
<th>Role</th>
<th>Modules</th>
<th>Access Level</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr class="user-row" data-user-id="{{ user.id }}" data-username="{{ user.username }}" data-role="{{ user.role }}" data-modules="{{ (user.get_modules() or []) | tojson }}">
<td>
<strong>{{ user.username }}</strong>
</td>
<td>
<span class="user-role-badge role-{{ user.role }}">
{{ user.role.title() }}
</span>
</td>
<td>
<div class="module-badges">
{% if user.role in ['superadmin', 'admin'] %}
<span class="badge bg-secondary">All Modules</span>
{% elif user.modules %}
{% for module in user.get_modules() %}
<span class="badge bg-info">{{ module.title() }}</span>
{% endfor %}
{% else %}
<span class="text-muted">No modules assigned</span>
{% endif %}
</div>
</td>
<td>
<small class="text-muted">
{% if user.role == 'superadmin' %}
Full system access
{% elif user.role == 'admin' %}
Full app access
{% elif user.role == 'manager' %}
Full module access + reports
{% elif user.role == 'worker' %}
Basic operations only (no reports) - Can have multiple modules
{% endif %}
</small>
</td>
<td>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-primary select-user-btn"
data-user-id="{{ user.id }}"
data-username="{{ user.username }}"
data-role="{{ user.role }}"
data-modules="{{ (user.get_modules() or []) | tojson }}"
title="Select for quick edit">
📝 Select
</button>
<button class="btn btn-outline-info edit-user-btn"
data-user-id="{{ user.id }}"
data-username="{{ user.username }}"
data-role="{{ user.role }}"
data-modules="{{ (user.get_modules() or []) | tojson }}"
title="Full edit">
⚙️ Edit
</button>
{% if user.username != session.get('user') %}
<button class="btn btn-outline-danger delete-user-btn"
data-user-id="{{ user.id }}"
data-username="{{ user.username }}"
title="Delete user">
🗑️
</button>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="text-center text-muted py-4">
<i class="fas fa-users fa-3x mb-3"></i>
<p>No users found. Create your first user above!</p>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Edit User Modal -->
<div class="modal fade" id="editUserModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Edit User</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<form id="editUserForm" method="POST" action="/edit_user_simple">
<div class="modal-body">
<input type="hidden" id="edit_user_id" name="user_id">
<div class="form-group">
<label for="edit_username">Username:</label>
<input type="text" id="edit_username" name="username" class="form-control" required>
</div>
<div class="form-group">
<label for="edit_password">Password (leave blank to keep current):</label>
<input type="password" id="edit_password" name="password" class="form-control">
</div>
<div class="form-group">
<label for="edit_role">Role:</label>
<select id="edit_role" name="role" class="form-control" required onchange="updateEditModuleSelection()">
<option value="">Select a role...</option>
<option value="superadmin">Superadmin - Full system access</option>
<option value="admin">Admin - Full app access (except role permissions)</option>
<option value="manager">Manager - Module-based access (can have multiple modules)</option>
<option value="worker">Worker - Limited module access (can have multiple modules)</option>
</select>
</div>
<div class="form-group" id="editModuleSelection" style="display: none;">
<label>Modules:</label>
<div class="module-checkboxes">
<div class="module-checkbox">
<input type="checkbox" id="edit_module_quality" name="modules" value="quality">
<label for="edit_module_quality">Quality Control</label>
</div>
<div class="module-checkbox">
<input type="checkbox" id="edit_module_warehouse" name="modules" value="warehouse">
<label for="edit_module_warehouse">Warehouse Management</label>
</div>
<div class="module-checkbox">
<input type="checkbox" id="edit_module_labels" name="modules" value="labels">
<label for="edit_module_labels">Label Management</label>
</div>
</div>
<div id="editAccessLevelInfo" class="access-level-info" style="display: none;"></div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</form>
</div>
</div>
</div>
<script>
function updateModuleSelection() {
const role = document.getElementById('role').value;
const moduleSelection = document.getElementById('moduleSelection');
const accessLevelInfo = document.getElementById('accessLevelInfo');
const checkboxes = document.querySelectorAll('#moduleSelection input[type="checkbox"]');
if (role === 'superadmin' || role === 'admin') {
moduleSelection.style.display = 'none';
checkboxes.forEach(cb => cb.checked = false);
accessLevelInfo.style.display = 'none';
} else if (role === 'manager' || role === 'worker') {
moduleSelection.style.display = 'block';
checkboxes.forEach(cb => cb.checked = false);
if (role === 'manager') {
accessLevelInfo.style.display = 'block';
accessLevelInfo.innerHTML = '<strong>Manager Access:</strong> Can have multiple modules. Full access to assigned modules including reports and management functions.';
} else if (role === 'worker') {
accessLevelInfo.style.display = 'block';
accessLevelInfo.innerHTML = '<strong>Worker Access:</strong> Can have multiple modules. Limited to basic operations only (no reports or management functions). <br><small>Examples: Quality worker can scan, Warehouse worker can move orders, etc.</small>';
}
} else {
moduleSelection.style.display = 'none';
accessLevelInfo.style.display = 'none';
}
}
function updateEditModuleSelection() {
const role = document.getElementById('edit_role').value;
const moduleSelection = document.getElementById('editModuleSelection');
const accessLevelInfo = document.getElementById('editAccessLevelInfo');
const checkboxes = document.querySelectorAll('#editModuleSelection input[type="checkbox"]');
if (role === 'superadmin' || role === 'admin') {
moduleSelection.style.display = 'none';
checkboxes.forEach(cb => cb.checked = false);
accessLevelInfo.style.display = 'none';
} else if (role === 'manager' || role === 'worker') {
moduleSelection.style.display = 'block';
if (role === 'manager') {
accessLevelInfo.style.display = 'block';
accessLevelInfo.innerHTML = '<strong>Manager Access:</strong> Can have multiple modules. Full access to assigned modules including reports and management functions.';
} else if (role === 'worker') {
accessLevelInfo.style.display = 'block';
accessLevelInfo.innerHTML = '<strong>Worker Access:</strong> Can have multiple modules. Limited to basic operations only (no reports or management functions). <br><small>Examples: Quality worker can scan, Warehouse worker can move orders, etc.</small>';
}
} else {
moduleSelection.style.display = 'none';
accessLevelInfo.style.display = 'none';
}
}
// Global variable to store selected user
let selectedUser = null;
function selectUserForQuickEdit(userId, username, role, modules) {
selectedUser = {id: userId, username: username, role: role, modules: modules};
// Update the quick edit panel
document.getElementById('selectedUserName').textContent = username;
document.getElementById('selectedUserRole').textContent = role;
// Clear all module checkboxes first
const checkboxes = document.querySelectorAll('#currentModules input[type="checkbox"]');
checkboxes.forEach(cb => cb.checked = false);
// Check the appropriate modules
if (modules && Array.isArray(modules)) {
modules.forEach(module => {
const checkbox = document.getElementById('quick_module_' + module);
if (checkbox) checkbox.checked = true;
});
}
// Show the quick edit panel
document.getElementById('quickModuleEdit').style.display = 'block';
document.querySelector('#userRightsPanel .text-center').style.display = 'none';
// Highlight selected row
document.querySelectorAll('.user-row').forEach(row => {
row.classList.remove('table-warning');
});
document.querySelector(`[data-user-id="${userId}"]`).classList.add('table-warning');
}
function updateUserModules() {
if (!selectedUser) {
alert('No user selected');
return;
}
// Get selected modules
const checkboxes = document.querySelectorAll('#currentModules input[type="checkbox"]:checked');
const modules = Array.from(checkboxes).map(cb => cb.value);
// Validate modules for the role
if ((selectedUser.role === 'manager' || selectedUser.role === 'worker') && modules.length === 0) {
alert(`${selectedUser.role}s must have at least one module assigned.`);
return;
}
// Create and submit form
const form = document.createElement('form');
form.method = 'POST';
form.action = '/quick_update_modules';
// Add user ID
const userIdInput = document.createElement('input');
userIdInput.type = 'hidden';
userIdInput.name = 'user_id';
userIdInput.value = selectedUser.id;
form.appendChild(userIdInput);
// Add modules
modules.forEach(module => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'modules';
input.value = module;
form.appendChild(input);
});
document.body.appendChild(form);
form.submit();
}
function showFullEditModal() {
if (!selectedUser) {
alert('No user selected');
return;
}
editUser(selectedUser.id, selectedUser.username, selectedUser.role, selectedUser.modules);
}
function deleteSelectedUser() {
if (!selectedUser) {
alert('No user selected');
return;
}
deleteUser(selectedUser.id, selectedUser.username);
}
function editUser(userId, username, role, modules) {
document.getElementById('edit_user_id').value = userId;
document.getElementById('edit_username').value = username;
document.getElementById('edit_role').value = role;
document.getElementById('edit_password').value = '';
// Clear all module checkboxes first
const checkboxes = document.querySelectorAll('#editModuleSelection input[type="checkbox"]');
checkboxes.forEach(cb => cb.checked = false);
// Check the appropriate modules
if (modules && Array.isArray(modules)) {
modules.forEach(module => {
const checkbox = document.getElementById('edit_module_' + module);
if (checkbox) checkbox.checked = true;
});
}
updateEditModuleSelection();
const modal = new bootstrap.Modal(document.getElementById('editUserModal'));
modal.show();
}
function deleteUser(userId, username) {
if (confirm(`Are you sure you want to delete user "${username}"?`)) {
const form = document.createElement('form');
form.method = 'POST';
form.action = '/delete_user_simple';
const userIdInput = document.createElement('input');
userIdInput.type = 'hidden';
userIdInput.name = 'user_id';
userIdInput.value = userId;
form.appendChild(userIdInput);
document.body.appendChild(form);
form.submit();
}
}
function deleteUser(userId, username) {
if (confirm(`Are you sure you want to delete user "${username}"?`)) {
const form = document.createElement('form');
form.method = 'POST';
form.action = '/delete_user_simple';
const userIdInput = document.createElement('input');
userIdInput.type = 'hidden';
userIdInput.name = 'user_id';
userIdInput.value = userId;
form.appendChild(userIdInput);
document.body.appendChild(form);
form.submit();
}
}
// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
updateModuleSelection();
// Add event listeners for select user buttons
document.querySelectorAll('.select-user-btn').forEach(button => {
button.addEventListener('click', function() {
const userId = this.dataset.userId;
const username = this.dataset.username;
const role = this.dataset.role;
let modules = [];
try {
modules = JSON.parse(this.dataset.modules) || [];
} catch (e) {
console.log('Error parsing modules for user:', username, e);
modules = [];
}
selectUserForQuickEdit(userId, username, role, modules);
});
});
// Add event listeners for edit buttons
document.querySelectorAll('.edit-user-btn').forEach(button => {
button.addEventListener('click', function() {
const userId = this.dataset.userId;
const username = this.dataset.username;
const role = this.dataset.role;
let modules = [];
try {
modules = JSON.parse(this.dataset.modules) || [];
} catch (e) {
console.log('Error parsing modules for user:', username, e);
modules = [];
}
editUser(userId, username, role, modules);
});
});
// Add event listeners for delete buttons
document.querySelectorAll('.delete-user-btn').forEach(button => {
button.addEventListener('click', function() {
const userId = this.dataset.userId;
const username = this.dataset.username;
deleteUser(userId, username);
});
});
});
</script>
{% endblock %}

29
py_app/check_users.py Normal file
View File

@@ -0,0 +1,29 @@
#!/usr/bin/env python3
import pymysql
try:
# Connect to the database
conn = pymysql.connect(
host='localhost',
database='trasabilitate',
user='trasabilitate',
password='Initial01!',
cursorclass=pymysql.cursors.DictCursor
)
with conn.cursor() as cursor:
cursor.execute("SELECT id, username, role, modules FROM users")
users = cursor.fetchall()
print("Current users in database:")
print("-" * 50)
for user in users:
print(f"ID: {user['id']}")
print(f"Username: {user['username']}")
print(f"Role: {user['role']}")
print(f"Modules: {user['modules']}")
print("-" * 30)
finally:
conn.close()

43
py_app/debug_modules.py Normal file
View File

@@ -0,0 +1,43 @@
#!/usr/bin/env python3
import pymysql
import json
try:
# Connect to the database
conn = pymysql.connect(
host='localhost',
database='trasabilitate',
user='trasabilitate',
password='Initial01!',
cursorclass=pymysql.cursors.DictCursor
)
with conn.cursor() as cursor:
cursor.execute("SELECT id, username, role, modules FROM users")
users = cursor.fetchall()
print("Debug: User data and get_modules() output:")
print("=" * 60)
for user_data in users:
print(f"Username: {user_data['username']}")
print(f"Role: {user_data['role']}")
print(f"Raw modules: {user_data['modules']} (type: {type(user_data['modules'])})")
# Simulate the get_modules() method
modules = user_data['modules']
if not modules:
parsed_modules = []
else:
try:
parsed_modules = json.loads(modules)
except:
parsed_modules = []
print(f"Parsed modules: {parsed_modules} (type: {type(parsed_modules)})")
print(f"JSON output: {json.dumps(parsed_modules)}")
print("-" * 40)
finally:
conn.close()

44
py_app/fix_user_data.py Normal file
View File

@@ -0,0 +1,44 @@
#!/usr/bin/env python3
import pymysql
import json
try:
# Connect to the database
conn = pymysql.connect(
host='localhost',
database='trasabilitate',
user='trasabilitate',
password='Initial01!',
cursorclass=pymysql.cursors.DictCursor
)
with conn.cursor() as cursor:
# Update Ciprian's role from quality_manager to manager
print("Updating Ciprian's role from 'quality_manager' to 'manager'...")
cursor.execute("UPDATE users SET role = 'manager' WHERE username = 'Ciprian'")
# Assign quality module to Ciprian since he was a quality manager
quality_modules = json.dumps(['quality'])
print(f"Assigning quality module to Ciprian: {quality_modules}")
cursor.execute("UPDATE users SET modules = %s WHERE username = 'Ciprian'", (quality_modules,))
# Commit the changes
conn.commit()
print("Database updated successfully!")
# Show updated users
print("\nUpdated users:")
print("-" * 50)
cursor.execute("SELECT id, username, role, modules FROM users")
users = cursor.fetchall()
for user in users:
print(f"ID: {user['id']}")
print(f"Username: {user['username']}")
print(f"Role: {user['role']}")
print(f"Modules: {user['modules']}")
print("-" * 30)
finally:
conn.close()

View File

@@ -0,0 +1,73 @@
#!/usr/bin/env python3
import pymysql
import json
def test_login_data():
try:
# Connect to the database
conn = pymysql.connect(
host='localhost',
database='trasabilitate',
user='trasabilitate',
password='Initial01!',
cursorclass=pymysql.cursors.DictCursor
)
with conn.cursor() as cursor:
# Simulate login for Ciprian
cursor.execute("SELECT username, password, role, modules FROM users WHERE username = 'Ciprian'")
user = cursor.fetchone()
if user:
print("Ciprian's database record:")
print(f"Username: {user['username']}")
print(f"Role: {user['role']}")
print(f"Raw modules: {user['modules']}")
# Simulate what happens in login
user_modules = []
if user['modules']:
try:
user_modules = json.loads(user['modules'])
print(f"Parsed modules: {user_modules}")
except Exception as e:
print(f"Error parsing modules: {e}")
user_modules = []
# Check if user should have quality access
has_quality = 'quality' in user_modules
print(f"Has quality module access: {has_quality}")
# Check role level
ROLES = {
'superadmin': {'level': 100},
'admin': {'level': 90},
'manager': {'level': 70},
'worker': {'level': 50}
}
user_level = ROLES.get(user['role'], {}).get('level', 0)
print(f"Role level: {user_level}")
# Test access control logic
print("\nAccess Control Test:")
print(f"Required modules: ['quality']")
print(f"User role: {user['role']}")
print(f"User modules: {user_modules}")
if user['role'] in ['superadmin', 'admin']:
print("✅ Access granted: Superadmin/Admin has access to all modules")
elif any(module in user_modules for module in ['quality']):
print("✅ Access granted: User has required quality module")
else:
print("❌ Access denied: User does not have quality module")
else:
print("User 'Ciprian' not found!")
finally:
conn.close()
if __name__ == "__main__":
test_login_data()

View File

@@ -0,0 +1,49 @@
#!/usr/bin/env python3
"""
Quick test for updated worker permissions
"""
import sys
import os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'app'))
from permissions_simple import validate_user_modules
def test_worker_multiple_modules():
"""Test that workers can now have multiple modules"""
print("Testing Updated Worker Module Permissions")
print("=" * 45)
test_cases = [
# (role, modules, expected_result, description)
('worker', ['quality'], True, "Worker with quality module"),
('worker', ['warehouse'], True, "Worker with warehouse module"),
('worker', ['quality', 'warehouse'], True, "Worker with multiple modules (NEW)"),
('worker', ['quality', 'warehouse', 'labels'], True, "Worker with all modules (NEW)"),
('worker', [], False, "Worker with no modules"),
('manager', ['quality', 'warehouse'], True, "Manager with multiple modules"),
]
passed = 0
failed = 0
for role, modules, expected, description in test_cases:
is_valid, error_msg = validate_user_modules(role, modules)
status = "PASS" if is_valid == expected else "FAIL"
print(f"{status}: {description}")
print(f" Role: {role}, Modules: {modules} -> {is_valid} (expected {expected})")
if error_msg:
print(f" Error: {error_msg}")
print()
if is_valid == expected:
passed += 1
else:
failed += 1
print(f"Results: {passed} passed, {failed} failed")
print("\n✅ Workers can now have multiple modules!" if failed == 0 else "❌ Some tests failed")
if __name__ == "__main__":
test_worker_multiple_modules()