From 8747d112f2f17fa222c1b5b6a7270c0f133c3c10 Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Sun, 17 Aug 2025 22:24:55 +0700 Subject: [PATCH] add database management and fix some visual glitch --- src/__pycache__/auth.cpython-312.pyc | Bin 17606 -> 17626 bytes src/__pycache__/database.cpython-312.pyc | Bin 14090 -> 17338 bytes src/auth.py | 2 +- src/database.py | 111 ++++++-- ...database_management_dialog.cpython-312.pyc | Bin 0 -> 8885 bytes .../__pycache__/main_window.cpython-312.pyc | Bin 70712 -> 82458 bytes src/ui/database_management_dialog.py | 148 ++++++++++ src/ui/main_window.py | 262 +++++++++++++++--- 8 files changed, 465 insertions(+), 58 deletions(-) create mode 100644 src/ui/__pycache__/database_management_dialog.cpython-312.pyc create mode 100644 src/ui/database_management_dialog.py diff --git a/src/__pycache__/auth.cpython-312.pyc b/src/__pycache__/auth.cpython-312.pyc index 824f85c259160e8ec25ea40e785355632b6cd682..e05458a5beb34d77e7176ad64f57d9761f71944a 100644 GIT binary patch delta 98 zcmX@s$#|=ik@qw&FBbz46n|WpF?%DghNC)bQ96)+i?1v*wLCsEuPimMB)_Qg7JqJH zUSfJ`d_hruN@;RQ@n&yF1xCh4lXILJ**-BbC^$1s{^0b1v2OB1=bel#lV`hV0|1`F BBqRU; delta 78 zcmccB$#|@jk@qw&FBbz4$Q3Qj=-9}s;V8yZln&(I;w#HcEsxL4D@)BQ$uFwh9OJ0K g$asHpty3fOE&2S(EY2SoYbSqo-pSZJdA*A^0NQsOYybcN diff --git a/src/__pycache__/database.cpython-312.pyc b/src/__pycache__/database.cpython-312.pyc index 54d6e1d55e6c4fd8a0b20da1af1ae792997e7a44..ad86863f85508c390a7ecc026c3e6a74b5cf386d 100644 GIT binary patch delta 4033 zcmcJSZERE58Gz6IupNJ0UwepK*dd(U-D z40KZVqgT4`Ip;m+y!ZH=^FHtE4D^50W2U$k<&uHjcyF zPU6hAt@xkYW>VIV4}WlY)l{==s#!7BW$a}+4w+Tu*QQP1JTfD``~hP1<@Ze1w4-gd zXSOYEdwIq5N(OAEK1^kJ$7RcmWtFdA=IdAZhQHWd_f0&p3DckC%rNsw&a`gval8RG z0{g7K4z$>z_rGFh7WcIKOPD{lv3?$XRig2mnXk+Ysq;2}4HKjbTM4BZUIyNW5gNBN z{T?qHN=rq}xq5`;pr{pfVcj&NM@eSfimj|&S0d_@Xxv8X7K7p}7y?}>?7ZE)b|#_` zG%hkqV}hEA)OMSb)LEm*i5AdQ6lSiMA?geMbFvh8D3qjzk+M18f-|>~g{7W3b~L4{ zB@3%63|cSY%DAT4qUqKo`|Mn}AG&Gw0z0M4mv2&UqPLin=uHZ)o=~v#oShaV+Ba^< z>%cpkUrxzhuqKl&AI$1vXGg`!iP&ShX$)Rcz6!4h@0pRT;mH0YJ;TGt4-ff%MQ1g^ zF)4U1%Se$o66HstapkNm4Idp4$VHrt36d;F*xW6pv>ac`_o~VTD;HuE;VuRk@4^ z7tz^z1L3eF9?1{xBj1&GkluC>UX5%dgWxk{S);LlGM3dVp$RFj1SVqgQ8KUtL|g+x zAdd{5jUP3)aR#_7@$ITRuxDlKkdJAE@#%&m4fTJYkp~(6<0g_%N_o-XMfIu zoVAaT&Ut{!@uYv26d_NA_yUGvT9(xWTh+=p-UW>Xuf@kR3$JiKBeSSwL9=MofM$_rNZqdYwKI!VoUeshY|%jd+nx0oipWz32Ne`auV13Vl?i6AN$+`j%+P*}lo$3+EQOPNfpAVB96erI7_?rhWYQK*cOwW499tfML!?FkD-RR#EMVmkqFX1b zP9>`RD$x>rW;rnw4@M&qDX5IX@5`2max|va3_)2U8w^@5PgxyIAdtnz)-Jx00Ja-K z&)X=+cInG;Ynua~Yc;Q9_U;;Ho2}LNJ<)VbS&b!^W65-MIz64rA+)uJzBHKAqMFVd zb$@95UE>Y+{Q30e1DE59%3$F6&of*PQz^8pHi7~F#&v{oJ@j&QN1 zUx$9}0C7{-*R_8WT4G($SlVRiXPKo&ZvP%;sgos|JsQy5W*Mm8*6I7}ncFM5p_xC1z2jQnF~z8LCSG2%q>frQM+i*j#S5lLBR@SqdoZ!Z z2MJ<+t%daklu$GxsHtZGhF-nA9ab#siRdE2nj&&Hh-}FjNePZcBa+-nbotYUnwQN} zjEv7}0k4x`B~K_8G=xwj4Mf?pP<)i6sbm$SMZ1*n6xMbq5DA=>^!gae8;gS$=tRW^~oLZP~f)uCwWV|JwhyD{tMgcrtwHWp0K`Zw`LVi^ZAe(~c7> z{K@;yN@C-#bL;#5jJ^D-BW>S;uQpa0ciyPJVNct3ub8^=QlqE2r+J?Z-LmO>8Rk|c zx38PIb;tgSy|8TgI1#TC5g_6W5#xAghnw1q zPju7@Z_yg}Lq#KKTn~Iyx2F8TUfKxt3a!TWkhg2x_{Sa9Mm6e?6kCqs`3@Hq!+-9m Pu?%P__X9+vOq}7rD4!++ delta 1093 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 delta 17410 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 Y#fXh^dl_37^NnsrEX(ZF5ELE$2k7R=Hvj+t 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)