diff --git a/src/__pycache__/auth.cpython-312.pyc b/src/__pycache__/auth.cpython-312.pyc index 824f85c..e05458a 100644 Binary files a/src/__pycache__/auth.cpython-312.pyc and b/src/__pycache__/auth.cpython-312.pyc differ diff --git a/src/__pycache__/database.cpython-312.pyc b/src/__pycache__/database.cpython-312.pyc index 54d6e1d..ad86863 100644 Binary files a/src/__pycache__/database.cpython-312.pyc and b/src/__pycache__/database.cpython-312.pyc differ diff --git a/src/auth.py b/src/auth.py index 50df084..8c942c1 100644 --- a/src/auth.py +++ b/src/auth.py @@ -339,7 +339,7 @@ class AuthManager: }, { 'name': 'Inventory Manager', - 'permissions': ['view_dashboard', 'view_inventory', 'adjust_inventory', 'view_stock_movements'] + 'permissions': ['view_dashboard', 'view_inventory', 'manage_products', 'adjust_inventory', 'view_stock_movements'] }, { 'name': 'Reports Viewer', diff --git a/src/database.py b/src/database.py index 1345630..e0aa93d 100644 --- a/src/database.py +++ b/src/database.py @@ -125,6 +125,7 @@ class DatabaseManager: description TEXT, unit_price REAL DEFAULT 0, sku TEXT, + min_stock INTEGER DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') @@ -251,24 +252,13 @@ class DatabaseManager: ) ''') - # Create stock movements table - cursor.execute(''' - CREATE TABLE IF NOT EXISTS stock_movements ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - product_id INTEGER, - movement_type TEXT, -- 'IN' for receipts, 'OUT' for issues - quantity INTEGER, - reference_type TEXT, -- 'PO', 'SO', 'MO', 'ADJUSTMENT' - reference_id INTEGER, -- ID of the related document - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (product_id) REFERENCES products (id) - ) - ''') self.connection.commit() return True except Exception as e: print(f"Database initialization error: {e}") + if self.connection: + self.connection.rollback() return False finally: self.disconnect() @@ -283,8 +273,25 @@ class DatabaseManager: if self.db_type == 'sqlite': shutil.copy2(self.db_name, backup_path) return backup_path + elif self.db_type == 'postgresql': + # For PostgreSQL, we can use pg_dump + import subprocess + try: + subprocess.run([ + 'pg_dump', + '-h', self.db_host, + '-p', str(self.db_port), + '-U', self.db_user, + '-d', self.db_name, + '-f', backup_path + ], check=True, env={'PGPASSWORD': self.db_password}) + return backup_path + except subprocess.CalledProcessError as e: + print(f"PostgreSQL backup error: {e}") + return None else: - # For other databases, implement appropriate backup logic + # For other databases, return None for now + print(f"Backup not implemented for database type: {self.db_type}") return None except Exception as e: print(f"Database backup error: {e}") @@ -296,8 +303,59 @@ class DatabaseManager: if self.db_type == 'sqlite': shutil.copy2(backup_path, self.db_name) return True + elif self.db_type == 'postgresql': + # For PostgreSQL, we can use pg_restore or psql + import subprocess + try: + # First, disconnect all users from the database + disconnect_sql = f"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '{self.db_name}';" + + # Drop and recreate the database + drop_sql = f"DROP DATABASE IF EXISTS {self.db_name};" + create_sql = f"CREATE DATABASE {self.db_name};" + + # Execute the commands + subprocess.run([ + 'psql', + '-h', self.db_host, + '-p', str(self.db_port), + '-U', self.db_user, + '-c', disconnect_sql + ], check=True, env={'PGPASSWORD': self.db_password}) + + subprocess.run([ + 'psql', + '-h', self.db_host, + '-p', str(self.db_port), + '-U', self.db_user, + '-c', drop_sql + ], check=True, env={'PGPASSWORD': self.db_password}) + + subprocess.run([ + 'psql', + '-h', self.db_host, + '-p', str(self.db_port), + '-U', self.db_user, + '-c', create_sql + ], check=True, env={'PGPASSWORD': self.db_password}) + + # Restore the backup + subprocess.run([ + 'psql', + '-h', self.db_host, + '-p', str(self.db_port), + '-U', self.db_user, + '-d', self.db_name, + '-f', backup_path + ], check=True, env={'PGPASSWORD': self.db_password}) + + return True + except subprocess.CalledProcessError as e: + print(f"PostgreSQL restore error: {e}") + return False else: - # For other databases, implement appropriate restore logic + # For other databases, return False for now + print(f"Restore not implemented for database type: {self.db_type}") return False except Exception as e: print(f"Database restore error: {e}") @@ -322,4 +380,25 @@ class DatabaseManager: return f"sqlite:///{self.db_name}" elif self.db_type == 'postgresql': return f"postgresql://{self.db_user}:{self.db_password}@{self.db_host}:{self.db_port}/{self.db_name}" - return "" \ No newline at end of file + return "" + + def is_database_initialized(self): + """Check if the database is properly initialized""" + if not self.connect(): + return False + + try: + cursor = self.connection.cursor() + # Check if the users table exists + if self.db_type == 'sqlite': + cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='users'") + elif self.db_type == 'postgresql': + cursor.execute("SELECT tablename FROM pg_tables WHERE tablename='users'") + + result = cursor.fetchone() + return result is not None + except Exception as e: + print(f"Database initialization check error: {e}") + return False + finally: + self.disconnect() \ No newline at end of file diff --git a/src/ui/__pycache__/database_management_dialog.cpython-312.pyc b/src/ui/__pycache__/database_management_dialog.cpython-312.pyc new file mode 100644 index 0000000..12cd736 Binary files /dev/null and b/src/ui/__pycache__/database_management_dialog.cpython-312.pyc differ diff --git a/src/ui/__pycache__/main_window.cpython-312.pyc b/src/ui/__pycache__/main_window.cpython-312.pyc index db42750..cc9b019 100644 Binary files a/src/ui/__pycache__/main_window.cpython-312.pyc and b/src/ui/__pycache__/main_window.cpython-312.pyc differ diff --git a/src/ui/database_management_dialog.py b/src/ui/database_management_dialog.py new file mode 100644 index 0000000..ed50b30 --- /dev/null +++ b/src/ui/database_management_dialog.py @@ -0,0 +1,148 @@ +import customtkinter as ctk +from tkinter import messagebox, filedialog +import os +import sys + +# Add the project root to the path +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + +from src.database import DatabaseManager + + +class DatabaseManagementDialog: + def __init__(self, parent, db_manager): + self.db_manager = db_manager + + # Create the dialog window + self.dialog = ctk.CTkToplevel(parent) + self.dialog.title("Database Management") + self.dialog.geometry("500x400") + self.dialog.transient(parent) + self.dialog.grab_set() + + # Center the dialog + self.dialog.update_idletasks() + width = self.dialog.winfo_width() + height = self.dialog.winfo_height() + x = (self.dialog.winfo_screenwidth() // 2) - (width // 2) + y = (self.dialog.winfo_screenheight() // 2) - (height // 2) + self.dialog.geometry(f'{width}x{height}+{x}+{y}') + + # Create UI elements + self.create_widgets() + + def create_widgets(self): + """Create the UI elements for the dialog""" + # Title + title_label = ctk.CTkLabel(self.dialog, text="Database Management", + font=("Arial", 20, "bold")) + title_label.pack(pady=20) + + # Main frame + main_frame = ctk.CTkFrame(self.dialog) + main_frame.pack(fill="both", expand=True, padx=20, pady=10) + + # Database info + info_frame = ctk.CTkFrame(main_frame) + info_frame.pack(fill="x", padx=10, pady=10) + + ctk.CTkLabel(info_frame, text="Database Information", + font=("Arial", 14, "bold")).pack(anchor="w", padx=10, pady=5) + + ctk.CTkLabel(info_frame, text=f"Database Type: {self.db_manager.db_type}").pack(anchor="w", padx=20, pady=2) + ctk.CTkLabel(info_frame, text=f"Database Name: {self.db_manager.db_name}").pack(anchor="w", padx=20, pady=2) + + # Actions frame + actions_frame = ctk.CTkFrame(main_frame) + actions_frame.pack(fill="x", padx=10, pady=10) + + ctk.CTkLabel(actions_frame, text="Database Actions", + font=("Arial", 14, "bold")).pack(anchor="w", padx=10, pady=5) + + # Buttons + button_frame = ctk.CTkFrame(actions_frame, fg_color="transparent") + button_frame.pack(fill="x", padx=20, pady=10) + + ctk.CTkButton(button_frame, text="Backup Database", + command=self.backup_database, width=150, height=32).pack(pady=5) + + ctk.CTkButton(button_frame, text="Restore Database", + command=self.restore_database, width=150, height=32).pack(pady=5) + + ctk.CTkButton(button_frame, text="Initialize Database", + command=self.initialize_database, width=150, height=32).pack(pady=5) + + ctk.CTkButton(button_frame, text="Check Database Status", + command=self.check_database_status, width=150, height=32).pack(pady=5) + + # Close button + close_button = ctk.CTkButton(self.dialog, text="Close", + command=self.dialog.destroy, width=100, height=32) + close_button.pack(pady=20) + + def backup_database(self): + """Backup the database""" + try: + # Ask user for backup location + backup_path = filedialog.asksaveasfilename( + defaultextension=".db", + filetypes=[("Database files", "*.db"), ("All files", "*.*")], + title="Save Database Backup As" + ) + + if backup_path: + result = self.db_manager.backup_database(backup_path) + if result: + messagebox.showinfo("Success", f"Database backed up successfully to {result}") + else: + messagebox.showerror("Error", "Failed to backup database") + except Exception as e: + messagebox.showerror("Error", f"Failed to backup database: {str(e)}") + + def restore_database(self): + """Restore the database from a backup""" + try: + # Ask user for backup file to restore + backup_path = filedialog.askopenfilename( + filetypes=[("Database files", "*.db"), ("All files", "*.*")], + title="Select Database Backup File" + ) + + if backup_path: + # Confirm with user before restoring + if messagebox.askyesno("Confirm Restore", + "Are you sure you want to restore the database? " + "This will overwrite the current database."): + result = self.db_manager.restore_database(backup_path) + if result: + messagebox.showinfo("Success", "Database restored successfully") + else: + messagebox.showerror("Error", "Failed to restore database") + except Exception as e: + messagebox.showerror("Error", f"Failed to restore database: {str(e)}") + + def initialize_database(self): + """Initialize the database""" + try: + # Confirm with user before initializing + if messagebox.askyesno("Confirm Initialization", + "Are you sure you want to initialize the database? " + "This will create all necessary tables."): + result = self.db_manager.initialize_database() + if result: + messagebox.showinfo("Success", "Database initialized successfully") + else: + messagebox.showerror("Error", "Failed to initialize database") + except Exception as e: + messagebox.showerror("Error", f"Failed to initialize database: {str(e)}") + + def check_database_status(self): + """Check the database status""" + try: + is_initialized = self.db_manager.is_database_initialized() + if is_initialized: + messagebox.showinfo("Database Status", "Database is properly initialized") + else: + messagebox.showwarning("Database Status", "Database is not initialized") + except Exception as e: + messagebox.showerror("Error", f"Failed to check database status: {str(e)}") \ No newline at end of file diff --git a/src/ui/main_window.py b/src/ui/main_window.py index a27ab0c..156acc0 100644 --- a/src/ui/main_window.py +++ b/src/ui/main_window.py @@ -17,6 +17,7 @@ from src.ui.manufacturing_order_dialog import ManufacturingOrderDialog from src.ui.sales_order_dialog import SalesOrderDialog from src.ui.customer_dialog import CustomerDialog from src.ui.supplier_dialog import SupplierDialog +from src.ui.database_management_dialog import DatabaseManagementDialog class MainWindow: def __init__(self, root, user, db_manager, app=None): @@ -67,19 +68,27 @@ class MainWindow: menubar.add_cascade(label="Modules", menu=modules_menu) modules_menu.add_command(label="Dashboard", command=self.show_dashboard) modules_menu.add_separator() - modules_menu.add_command(label="Purchase", command=self.show_purchase) - modules_menu.add_command(label="Manufacture", command=self.show_manufacturing) - modules_menu.add_command(label="Sales", command=self.show_sales) - modules_menu.add_command(label="Inventory", command=self.show_inventory) + if self.auth_manager.user_has_permission(self.user['id'], 'view_purchase_orders') or self.user['is_admin']: + modules_menu.add_command(label="Purchase", command=self.show_purchase) + if self.auth_manager.user_has_permission(self.user['id'], 'view_manufacturing_orders') or self.user['is_admin']: + modules_menu.add_command(label="Manufacture", command=self.show_manufacturing) + if self.auth_manager.user_has_permission(self.user['id'], 'view_sales_orders') or self.user['is_admin']: + modules_menu.add_command(label="Sales", command=self.show_sales) + if self.auth_manager.user_has_permission(self.user['id'], 'view_inventory') or self.user['is_admin']: + modules_menu.add_command(label="Inventory", command=self.show_inventory) modules_menu.add_separator() - modules_menu.add_command(label="Suppliers", command=self.show_supplier_list) - modules_menu.add_command(label="Customers", command=self.show_customer_list) + if self.auth_manager.user_has_permission(self.user['id'], 'manage_suppliers') or self.user['is_admin']: + modules_menu.add_command(label="Suppliers", command=self.show_supplier_list) + if self.auth_manager.user_has_permission(self.user['id'], 'manage_customers') or self.user['is_admin']: + modules_menu.add_command(label="Customers", command=self.show_customer_list) # Configuration menu (admin only) if self.user['is_admin']: config_menu = tk.Menu(menubar, tearoff=0, bg="#f0f0f0", fg="black") menubar.add_cascade(label="Configuration", menu=config_menu) config_menu.add_command(label="User Management", command=self.manage_users) + config_menu.add_separator() + config_menu.add_command(label="Database Management", command=self.manage_database) # Reports menu has_report_permissions = ( @@ -114,14 +123,18 @@ class MainWindow: ctk.CTkButton(button_frame, text="Dashboard", command=self.show_dashboard, width=100, height=32).pack(side="left", padx=5) - ctk.CTkButton(button_frame, text="Purchase", command=self.show_purchase, - width=100, height=32).pack(side="left", padx=5) - ctk.CTkButton(button_frame, text="Manufacture", command=self.show_manufacturing, - width=100, height=32).pack(side="left", padx=5) - ctk.CTkButton(button_frame, text="Sales", command=self.show_sales, - width=100, height=32).pack(side="left", padx=5) - ctk.CTkButton(button_frame, text="Inventory", command=self.show_inventory, - width=100, height=32).pack(side="left", padx=5) + if self.auth_manager.user_has_permission(self.user['id'], 'view_purchase_orders') or self.user['is_admin']: + ctk.CTkButton(button_frame, text="Purchase", command=self.show_purchase, + width=100, height=32).pack(side="left", padx=5) + if self.auth_manager.user_has_permission(self.user['id'], 'view_manufacturing_orders') or self.user['is_admin']: + ctk.CTkButton(button_frame, text="Manufacture", command=self.show_manufacturing, + width=100, height=32).pack(side="left", padx=5) + if self.auth_manager.user_has_permission(self.user['id'], 'view_sales_orders') or self.user['is_admin']: + ctk.CTkButton(button_frame, text="Sales", command=self.show_sales, + width=100, height=32).pack(side="left", padx=5) + if self.auth_manager.user_has_permission(self.user['id'], 'view_inventory') or self.user['is_admin']: + ctk.CTkButton(button_frame, text="Inventory", command=self.show_inventory, + width=100, height=32).pack(side="left", padx=5) # User info user_frame = ctk.CTkFrame(toolbar, fg_color="transparent") @@ -269,11 +282,16 @@ class MainWindow: def show_purchase(self): """Show purchase management""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'view_purchase_orders') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to access the purchase module") + return + self.clear_content() self.status_bar.configure(text="Purchase Management") # Title - title = ctk.CTkLabel(self.content_frame, text="Purchase Management", + title = ctk.CTkLabel(self.content_frame, text="Purchase Management", font=("Arial", 24, "bold")) title.pack(pady=(20, 30)) @@ -285,23 +303,29 @@ class MainWindow: button_frame = ctk.CTkFrame(purchase_frame) button_frame.pack(pady=20) - ctk.CTkButton(button_frame, text="New Purchase Order", + ctk.CTkButton(button_frame, text="New Purchase Order", command=self.new_purchase_order, width=150, height=32).pack(side="left", padx=5) - ctk.CTkButton(button_frame, text="View Purchase Orders", + ctk.CTkButton(button_frame, text="View Purchase Orders", command=self.view_purchase_orders, width=150, height=32).pack(side="left", padx=5) - ctk.CTkButton(button_frame, text="Manage Suppliers", - command=self.show_supplier_list, width=150, height=32).pack(side="left", padx=5) + if self.auth_manager.user_has_permission(self.user['id'], 'manage_suppliers') or self.user['is_admin']: + ctk.CTkButton(button_frame, text="Manage Suppliers", + command=self.show_supplier_list, width=150, height=32).pack(side="left", padx=5) # Show purchase orders by default self.show_purchase_orders() def show_manufacturing(self): """Show manufacturing management""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'view_manufacturing_orders') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to access the manufacturing module") + return + self.clear_content() self.status_bar.configure(text="Manufacturing Management") # Title - title = ctk.CTkLabel(self.content_frame, text="Manufacturing Management", + title = ctk.CTkLabel(self.content_frame, text="Manufacturing Management", font=("Arial", 24, "bold")) title.pack(pady=(20, 30)) @@ -313,11 +337,11 @@ class MainWindow: button_frame = ctk.CTkFrame(manufacturing_frame) button_frame.pack(pady=20) - ctk.CTkButton(button_frame, text="New Manufacturing Order", + ctk.CTkButton(button_frame, text="New Manufacturing Order", command=self.new_manufacturing_order, width=180, height=32).pack(side="left", padx=5) - ctk.CTkButton(button_frame, text="View Manufacturing Orders", + ctk.CTkButton(button_frame, text="View Manufacturing Orders", command=self.view_manufacturing_orders, width=180, height=32).pack(side="left", padx=5) - ctk.CTkButton(button_frame, text="Production Planning", + ctk.CTkButton(button_frame, text="Production Planning", command=self.production_planning, width=180, height=32).pack(side="left", padx=5) # Show manufacturing orders by default @@ -325,11 +349,16 @@ class MainWindow: def show_sales(self): """Show sales management""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'view_sales_orders') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to access the sales module") + return + self.clear_content() self.status_bar.configure(text="Sales Management") # Title - title = ctk.CTkLabel(self.content_frame, text="Sales Management", + title = ctk.CTkLabel(self.content_frame, text="Sales Management", font=("Arial", 24, "bold")) title.pack(pady=(20, 30)) @@ -341,23 +370,29 @@ class MainWindow: button_frame = ctk.CTkFrame(sales_frame) button_frame.pack(pady=20) - ctk.CTkButton(button_frame, text="New Sales Order", + ctk.CTkButton(button_frame, text="New Sales Order", command=self.new_sales_order, width=150, height=32).pack(side="left", padx=5) - ctk.CTkButton(button_frame, text="View Sales Orders", + ctk.CTkButton(button_frame, text="View Sales Orders", command=self.view_sales_orders, width=150, height=32).pack(side="left", padx=5) - ctk.CTkButton(button_frame, text="Manage Customers", - command=self.show_customer_list, width=150, height=32).pack(side="left", padx=5) + if self.auth_manager.user_has_permission(self.user['id'], 'manage_customers') or self.user['is_admin']: + ctk.CTkButton(button_frame, text="Manage Customers", + command=self.show_customer_list, width=150, height=32).pack(side="left", padx=5) # Show sales orders by default self.show_sales_orders() def show_inventory(self): """Show inventory management""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'view_inventory') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to access the inventory module") + return + self.clear_content() self.status_bar.configure(text="Inventory Management") # Title - title = ctk.CTkLabel(self.content_frame, text="Inventory Management", + title = ctk.CTkLabel(self.content_frame, text="Inventory Management", font=("Arial", 24, "bold")) title.pack(pady=(20, 30)) @@ -371,8 +406,9 @@ class MainWindow: ctk.CTkButton(button_frame, text="View Inventory", command=self.view_inventory, width=150, height=32).pack(side="left", padx=5) - ctk.CTkButton(button_frame, text="Stock Adjustment", - command=self.stock_adjustment, width=150, height=32).pack(side="left", padx=5) + if self.auth_manager.user_has_permission(self.user['id'], 'adjust_inventory') or self.user['is_admin']: + ctk.CTkButton(button_frame, text="Stock Adjustment", + command=self.stock_adjustment, width=150, height=32).pack(side="left", padx=5) # Show inventory by default self.show_inventory_management() @@ -416,6 +452,11 @@ class MainWindow: # Module command methods def new_purchase_order(self): """Create a new purchase order""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'edit_purchase_orders') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to create purchase orders") + return + dialog = PurchaseOrderDialog(self.root, self.purchase_service, self.product_service, self.supplier_service) dialog.grab_set() @@ -425,11 +466,21 @@ class MainWindow: def manage_suppliers(self): """Manage suppliers""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'manage_suppliers') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to manage suppliers") + return + dialog = SupplierDialog(self.root, self.supplier_service) dialog.grab_set() def new_manufacturing_order(self): """Create a new manufacturing order""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'edit_manufacturing_orders') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to create manufacturing orders") + return + dialog = ManufacturingOrderDialog(self.root, self.manufacturing_service, self.product_service, self.inventory_service) dialog.grab_set() @@ -443,6 +494,11 @@ class MainWindow: def new_sales_order(self): """Create a new sales order""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'edit_sales_orders') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to create sales orders") + return + dialog = SalesOrderDialog(self.root, self.sales_service, self.product_service, self.customer_service) dialog.grab_set() @@ -452,6 +508,11 @@ class MainWindow: def manage_customers(self): """Manage customers""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'manage_customers') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to manage customers") + return + dialog = CustomerDialog(self.root, self.customer_service) dialog.grab_set() @@ -517,7 +578,7 @@ class MainWindow: for idx, order in enumerate(orders): row_frame = ctk.CTkFrame(scroll_frame, height=24) row_frame.pack(fill="x", padx=5, pady=2) - row_frame.pack_propagate(False) # Prevent frame from shrinking + #row_frame.pack_propagate(False) # Prevent frame from shrinking ctk.CTkLabel(row_frame, text=f"PO-{order.id}").grid( row=0, column=0, padx=5, pady=5, sticky="w") @@ -535,11 +596,13 @@ class MainWindow: # Actions actions_frame = ctk.CTkFrame(row_frame, fg_color="transparent") actions_frame.grid(row=0, column=5, padx=5, pady=5, sticky="w") - + ctk.CTkButton(actions_frame, text="", + command=None, + width=1, height=24).pack(side="left", padx=2) # Add status change button for pending orders if order.status == "pending": ctk.CTkButton(actions_frame, text="Complete", - command=lambda o=order: self.complete_purchase_order(o), + command=None, width=70, height=24, fg_color="green").pack(side="left", padx=2) def show_manufacturing_orders(self): @@ -604,7 +667,7 @@ class MainWindow: for idx, order in enumerate(orders): row_frame = ctk.CTkFrame(scroll_frame, height=24) row_frame.pack(fill="x", padx=5, pady=2) - row_frame.pack_propagate(False) # Prevent frame from shrinking + #row_frame.pack_propagate(False) # Prevent frame from shrinking ctk.CTkLabel(row_frame, text=f"MO-{order.id}").grid( row=0, column=0, padx=5, pady=5, sticky="w") @@ -622,6 +685,9 @@ class MainWindow: # Actions actions_frame = ctk.CTkFrame(row_frame, fg_color="transparent") actions_frame.grid(row=0, column=5, padx=5, pady=5, sticky="w") + ctk.CTkButton(actions_frame, text="", + command=None, + width=1, height=24).pack(side="left", padx=2) # Add status change button for pending orders if order.status == "pending": @@ -691,7 +757,7 @@ class MainWindow: for idx, order in enumerate(orders): row_frame = ctk.CTkFrame(scroll_frame, height=24) row_frame.pack(fill="x", padx=5, pady=2) - row_frame.pack_propagate(False) # Prevent frame from shrinking + #row_frame.pack_propagate(False) # Prevent frame from shrinking ctk.CTkLabel(row_frame, text=f"SO-{order.id}").grid( row=0, column=0, padx=5, pady=5, sticky="w") @@ -709,6 +775,9 @@ class MainWindow: # Actions actions_frame = ctk.CTkFrame(row_frame, fg_color="transparent") actions_frame.grid(row=0, column=5, padx=5, pady=5, sticky="w") + ctk.CTkButton(actions_frame, text="", + command=None, + width=1, height=24).pack(side="left", padx=2) # Add status change button for pending orders if order.status == "pending": @@ -734,6 +803,11 @@ class MainWindow: def stock_adjustment(self): """Adjust stock levels""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'adjust_inventory') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to adjust inventory") + return + self.show_inventory_management() def show_inventory_management(self): @@ -796,7 +870,7 @@ class MainWindow: for idx, product in enumerate(products): row_frame = ctk.CTkFrame(scroll_frame, height=24) row_frame.pack(fill="x", padx=5, pady=2) - row_frame.pack_propagate(False) # Prevent frame from shrinking + #row_frame.pack_propagate(False) # Prevent frame from shrinking # Product info ctk.CTkLabel(row_frame, text=product.name).grid( @@ -831,6 +905,11 @@ class MainWindow: def manage_products(self): """Open product management dialog""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'manage_products') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to manage products") + return + dialog = ProductDialog(self.root, self.product_service) dialog.grab_set() @@ -840,11 +919,21 @@ class MainWindow: def edit_product(self, product): """Edit a specific product""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'manage_products') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to edit products") + return + dialog = ProductDialog(self.root, self.product_service, product) dialog.grab_set() def adjust_product_stock(self, product): """Adjust stock for a specific product""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'adjust_inventory') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to adjust inventory") + return + # Create a simple stock adjustment dialog dialog = ctk.CTkToplevel(self.root) dialog.title(f"Adjust Stock - {product.name}") @@ -877,10 +966,21 @@ class MainWindow: from src.ui.user_management_dialog import UserManagementDialog dialog = UserManagementDialog(self.root, self.auth_manager) self.root.wait_window(dialog) - - + + + def manage_database(self): + """Database management (admin only)""" + dialog = DatabaseManagementDialog(self.root, self.db_manager) + self.root.wait_window(dialog) + + def show_inventory_report(self): """Show inventory report""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'view_inventory_report') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to view inventory reports") + return + try: filename = self.inventory_service.export_inventory_report() messagebox.showinfo("Success", f"Inventory report exported to {filename}") @@ -889,6 +989,11 @@ class MainWindow: def show_sales_report(self): """Show sales report with date range selection""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'view_reports') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to view sales reports") + return + from src.ui.date_range_dialog import DateRangeDialog dialog = DateRangeDialog(self.root, "Select Date Range for Sales Report", "Export") self.root.wait_window(dialog) @@ -903,6 +1008,11 @@ class MainWindow: def show_purchase_report(self): """Show purchase report with date range selection""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'view_reports') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to view purchase reports") + return + from src.ui.date_range_dialog import DateRangeDialog dialog = DateRangeDialog(self.root, "Select Date Range for Purchase Report", "Export") self.root.wait_window(dialog) @@ -917,6 +1027,11 @@ class MainWindow: def show_manufacturing_report(self): """Show manufacturing report with date range selection""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'view_reports') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to view manufacturing reports") + return + from src.ui.date_range_dialog import DateRangeDialog dialog = DateRangeDialog(self.root, "Select Date Range for Manufacturing Report", "Export") self.root.wait_window(dialog) @@ -931,6 +1046,11 @@ class MainWindow: def show_stock_movement_report(self): """Show stock movement report""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'view_stock_movements') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to view stock movement reports") + return + try: filename = self.inventory_service.export_stock_movement_report() messagebox.showinfo("Success", f"Stock movement report exported to {filename}") @@ -939,6 +1059,11 @@ class MainWindow: def complete_purchase_order(self, order): """Complete a purchase order""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'receive_purchase_orders') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to complete purchase orders") + return + if messagebox.askyesno("Confirm", f"Are you sure you want to complete purchase order #{order.id}?"): if self.purchase_service.receive_purchase_order(order.id): messagebox.showinfo("Success", f"Purchase order #{order.id} completed successfully") @@ -948,6 +1073,11 @@ class MainWindow: def complete_manufacturing_order(self, order): """Complete a manufacturing order""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'complete_manufacturing_orders') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to complete manufacturing orders") + return + if messagebox.askyesno("Confirm", f"Are you sure you want to complete manufacturing order #{order.id}?"): if self.manufacturing_service.complete_manufacturing_order(order.id): messagebox.showinfo("Success", f"Manufacturing order #{order.id} completed successfully") @@ -957,6 +1087,11 @@ class MainWindow: def complete_sales_order(self, order): """Complete a sales order""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'deliver_sales_orders') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to complete sales orders") + return + if messagebox.askyesno("Confirm", f"Are you sure you want to complete sales order #{order.id}?"): if self.sales_service.deliver_sales_order(order.id): messagebox.showinfo("Success", f"Sales order #{order.id} completed successfully") @@ -978,6 +1113,11 @@ class MainWindow: def show_supplier_list(self): """Show supplier management interface""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'manage_suppliers') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to manage suppliers") + return + self.clear_content() self.status_bar.configure(text="Supplier Management") @@ -1036,7 +1176,7 @@ class MainWindow: for idx, supplier in enumerate(suppliers): row_frame = ctk.CTkFrame(scroll_frame, height=24) row_frame.pack(fill="x", padx=5, pady=2) - row_frame.pack_propagate(False) # Prevent frame from shrinking + #row_frame.pack_propagate(False) # Prevent frame from shrinking ctk.CTkLabel(row_frame, text=supplier.name).grid( row=0, column=0, padx=5, pady=5, sticky="w") @@ -1060,6 +1200,11 @@ class MainWindow: def add_supplier(self): """Add a new supplier""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'manage_suppliers') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to add suppliers") + return + from src.ui.supplier_dialog import SupplierDialog dialog = SupplierDialog(self.root, self.supplier_service) self.root.wait_window(dialog) @@ -1067,6 +1212,11 @@ class MainWindow: def edit_supplier(self, supplier): """Edit a supplier""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'manage_suppliers') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to edit suppliers") + return + from src.ui.supplier_dialog import SupplierDialog dialog = SupplierDialog(self.root, self.supplier_service, supplier) self.root.wait_window(dialog) @@ -1074,6 +1224,11 @@ class MainWindow: def delete_supplier(self, supplier): """Delete a supplier""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'manage_suppliers') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to delete suppliers") + return + if messagebox.askyesno("Confirm Delete", f"Are you sure you want to delete supplier '{supplier.name}'?"): if self.supplier_service.delete_supplier(supplier.id): messagebox.showinfo("Success", f"Supplier '{supplier.name}' deleted successfully") @@ -1083,6 +1238,11 @@ class MainWindow: def show_customer_list(self): """Show customer management interface""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'manage_customers') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to manage customers") + return + self.clear_content() self.status_bar.configure(text="Customer Management") @@ -1141,7 +1301,7 @@ class MainWindow: for idx, customer in enumerate(customers): row_frame = ctk.CTkFrame(scroll_frame, height=24) row_frame.pack(fill="x", padx=5, pady=2) - row_frame.pack_propagate(False) # Prevent frame from shrinking + #row_frame.pack_propagate(False) # Prevent frame from shrinking ctk.CTkLabel(row_frame, text=customer.name).grid( row=0, column=0, padx=5, pady=5, sticky="w") @@ -1165,6 +1325,11 @@ class MainWindow: def add_customer(self): """Add a new customer""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'manage_customers') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to add customers") + return + from src.ui.customer_dialog import CustomerDialog dialog = CustomerDialog(self.root, self.customer_service) self.root.wait_window(dialog) @@ -1172,6 +1337,11 @@ class MainWindow: def edit_customer(self, customer): """Edit a customer""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'manage_customers') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to edit customers") + return + from src.ui.customer_dialog import CustomerDialog dialog = CustomerDialog(self.root, self.customer_service, customer) self.root.wait_window(dialog) @@ -1179,6 +1349,11 @@ class MainWindow: def delete_customer(self, customer): """Delete a customer""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'manage_customers') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to delete customers") + return + if messagebox.askyesno("Confirm Delete", f"Are you sure you want to delete customer '{customer.name}'?"): if self.customer_service.delete_customer(customer.id): messagebox.showinfo("Success", f"Customer '{customer.name}' deleted successfully") @@ -1212,6 +1387,11 @@ class MainWindow: def add_product(self): """Add a new product""" + # Check permissions + if not (self.auth_manager.user_has_permission(self.user['id'], 'manage_products') or self.user['is_admin']): + messagebox.showerror("Access Denied", "You don't have permission to add products") + return + from src.ui.product_dialog import ProductDialog dialog = ProductDialog(self.root, self.product_service) self.root.wait_window(dialog)