diff --git a/hesabixAPI/adapters/api/v1/business_dashboard.py b/hesabixAPI/adapters/api/v1/business_dashboard.py index e71075d..da722d2 100644 --- a/hesabixAPI/adapters/api/v1/business_dashboard.py +++ b/hesabixAPI/adapters/api/v1/business_dashboard.py @@ -264,7 +264,8 @@ def get_business_info_with_permissions( if not ctx.is_superadmin() and not ctx.is_business_owner(business_id): # دریافت دسترسی‌های کسب و کار از business_permissions permission_repo = BusinessPermissionRepository(db) - business_permission = permission_repo.get_by_business_and_user(business_id, ctx.get_user_id()) + # ترتیب آرگومان‌ها: (user_id, business_id) + business_permission = permission_repo.get_by_user_and_business(ctx.get_user_id(), business_id) if business_permission: permissions = business_permission.business_permissions or {} diff --git a/hesabixAPI/adapters/api/v1/business_users.py b/hesabixAPI/adapters/api/v1/business_users.py index 3599c4c..dfc9482 100644 --- a/hesabixAPI/adapters/api/v1/business_users.py +++ b/hesabixAPI/adapters/api/v1/business_users.py @@ -18,6 +18,127 @@ from adapters.db.models.business import Business router = APIRouter(prefix="/business", tags=["business-users"]) +@router.get("/{business_id}/users/{user_id}", + summary="دریافت جزئیات کاربر", + description="دریافت جزئیات کاربر و دسترسی‌هایش در کسب و کار", + responses={ + 200: { + "description": "جزئیات کاربر با موفقیت دریافت شد", + "content": { + "application/json": { + "example": { + "success": True, + "message": "جزئیات کاربر دریافت شد", + "user": { + "id": 1, + "business_id": 1, + "user_id": 2, + "user_name": "علی احمدی", + "user_email": "ali@example.com", + "user_phone": "09123456789", + "role": "member", + "status": "active", + "added_at": "2024-01-01T00:00:00Z", + "last_active": "2024-01-01T12:00:00Z", + "permissions": { + "people": { + "add": True, + "view": True, + "edit": False, + "delete": False + } + } + } + } + } + } + }, + 401: { + "description": "کاربر احراز هویت نشده است" + }, + 403: { + "description": "دسترسی غیرمجاز به کسب و کار" + }, + 404: { + "description": "کاربر یافت نشد" + } + } +) +@require_business_access("business_id") +def get_user_details( + request: Request, + business_id: int, + user_id: int, + ctx: AuthContext = Depends(get_current_user), + db: Session = Depends(get_db) +) -> dict: + """دریافت جزئیات کاربر و دسترسی‌هایش""" + import logging + logger = logging.getLogger(__name__) + + current_user_id = ctx.get_user_id() + logger.info(f"Getting user details for user {user_id} in business {business_id}, current user: {current_user_id}") + + # Check if user is business owner or has permission to manage users + business = db.get(Business, business_id) + if not business: + logger.error(f"Business {business_id} not found") + raise HTTPException(status_code=404, detail="کسب و کار یافت نشد") + + is_owner = business.owner_id == current_user_id + can_manage = ctx.can_manage_business_users() + + logger.info(f"Business owner: {business.owner_id}, is_owner: {is_owner}, can_manage: {can_manage}") + + if not is_owner and not can_manage: + logger.warning(f"User {current_user_id} does not have permission to view user details for business {business_id}") + raise HTTPException(status_code=403, detail="شما مجوز مشاهده جزئیات کاربران ندارید") + + # Get user details + user = db.get(User, user_id) + if not user: + logger.warning(f"User {user_id} not found") + raise HTTPException(status_code=404, detail="کاربر یافت نشد") + + # Get user permissions for this business + permission_repo = BusinessPermissionRepository(db) + permission_obj = permission_repo.get_by_user_and_business(user_id, business_id) + + # Determine role and permissions + if business.owner_id == user_id: + role = "owner" + permissions = {} # Owner has all permissions + else: + role = "member" + permissions = permission_obj.business_permissions if permission_obj else {} + + # Format user data + user_data = { + "id": permission_obj.id if permission_obj else user_id, + "business_id": business_id, + "user_id": user_id, + "user_name": f"{user.first_name or ''} {user.last_name or ''}".strip(), + "user_email": user.email or "", + "user_phone": user.mobile, + "role": role, + "status": "active", + "added_at": permission_obj.created_at if permission_obj else business.created_at, + "last_active": permission_obj.updated_at if permission_obj else business.updated_at, + "permissions": permissions, + } + + logger.info(f"Returning user data: {user_data}") + + # Format datetime fields based on calendar type + formatted_user_data = format_datetime_fields(user_data, request) + + return success_response( + data={"user": formatted_user_data}, + request=request, + message="جزئیات کاربر دریافت شد" + ) + + @router.get("/{business_id}/users", summary="لیست کاربران کسب و کار", description="دریافت لیست کاربران یک کسب و کار", diff --git a/hesabixAPI/adapters/db/repositories/business_permission_repo.py b/hesabixAPI/adapters/db/repositories/business_permission_repo.py index f66cd32..f4ac3e9 100644 --- a/hesabixAPI/adapters/db/repositories/business_permission_repo.py +++ b/hesabixAPI/adapters/db/repositories/business_permission_repo.py @@ -28,15 +28,34 @@ class BusinessPermissionRepository(BaseRepository[BusinessPermission]): existing = self.get_by_user_and_business(user_id, business_id) if existing: - existing.business_permissions = permissions + # Preserve existing permissions and enforce join=True + existing_permissions = existing.business_permissions or {} + + # Always ignore incoming 'join' field from clients + incoming_permissions = dict(permissions or {}) + if 'join' in incoming_permissions: + incoming_permissions.pop('join', None) + + # Merge and enforce join flag + merged_permissions = dict(existing_permissions) + merged_permissions.update(incoming_permissions) + merged_permissions['join'] = True + + existing.business_permissions = merged_permissions self.db.commit() self.db.refresh(existing) return existing else: + # On creation, ensure join=True exists by default + base_permissions = {'join': True} + incoming_permissions = dict(permissions or {}) + if 'join' in incoming_permissions: + incoming_permissions.pop('join', None) + new_permission = BusinessPermission( user_id=user_id, business_id=business_id, - business_permissions=permissions + business_permissions={**base_permissions, **incoming_permissions} ) self.db.add(new_permission) self.db.commit() diff --git a/hesabixUI/hesabix_ui/lib/core/api_client.dart b/hesabixUI/hesabix_ui/lib/core/api_client.dart index bb5f5b3..7da7fd1 100644 --- a/hesabixUI/hesabix_ui/lib/core/api_client.dart +++ b/hesabixUI/hesabix_ui/lib/core/api_client.dart @@ -71,6 +71,29 @@ class ApiClient { if (calendarType != null && calendarType.isNotEmpty) { options.headers['X-Calendar-Type'] = calendarType; } + // Inject X-Business-ID header when path targets a specific business + try { + final uri = options.uri; + final path = uri.path; + // If current business exists, prefer it + final currentBusinessId = _authStore?.currentBusiness?.id; + int? resolvedBusinessId = currentBusinessId; + // Fallback: detect business_id from URL like /api/v1/business/{id}/... + if (resolvedBusinessId == null) { + final match = RegExp(r"/api/v1/business/(\d+)/").firstMatch(path); + if (match != null) { + final idStr = match.group(1); + if (idStr != null) { + resolvedBusinessId = int.tryParse(idStr); + } + } + } + if (resolvedBusinessId != null) { + options.headers['X-Business-ID'] = resolvedBusinessId.toString(); + } + } catch (_) { + // ignore header injection failures + } if (kDebugMode) { // ignore: avoid_print print('[API][REQ] ${options.method} ${options.uri}'); diff --git a/hesabixUI/hesabix_ui/lib/core/auth_store.dart b/hesabixUI/hesabix_ui/lib/core/auth_store.dart index 7a23c7f..1f573a4 100644 --- a/hesabixUI/hesabix_ui/lib/core/auth_store.dart +++ b/hesabixUI/hesabix_ui/lib/core/auth_store.dart @@ -317,15 +317,16 @@ class AuthStore with ChangeNotifier { if (_businessPermissions == null) return false; final sectionPerms = _businessPermissions![section] as Map?; - if (sectionPerms == null) return action == 'view'; // دسترسی خواندن پیش‌فرض + // اگر سکشن در دسترسی‌ها موجود نیست، هیچ دسترسی‌ای وجود ندارد + if (sectionPerms == null) return false; return sectionPerms[action] == true; } // دسترسی‌های کلی bool canReadSection(String section) { - return hasBusinessPermission(section, 'view') || - _businessPermissions?.containsKey(section) == true; + // خواندن فقط زمانی مجاز است که به‌صراحت در سکشن اجازه داده شده باشد + return hasBusinessPermission(section, 'view'); } bool canWriteSection(String section) { diff --git a/hesabixUI/hesabix_ui/lib/l10n/app_en.arb b/hesabixUI/hesabix_ui/lib/l10n/app_en.arb index 6958216..e049df4 100644 --- a/hesabixUI/hesabix_ui/lib/l10n/app_en.arb +++ b/hesabixUI/hesabix_ui/lib/l10n/app_en.arb @@ -837,6 +837,27 @@ "activePersons": "Active Persons", "inactivePersons": "Inactive Persons", "personsByType": "Persons by Type", - "update": "Update" + "update": "Update", + "collect": "Collect", + "transfer": "Transfer", + "charge": "Charge", + "businessSettings": "Business Settings", + "printSettings": "Print Settings", + "eventHistory": "Event History", + "usersAndPermissions": "Users and Permissions", + "storageSpace": "Storage Space", + "deleteFiles": "Files", + "viewSmsHistory": "View SMS History", + "manageSmsTemplates": "Manage SMS Templates", + "viewMarketplace": "View Marketplace", + "buyPlugins": "Buy Plugins", + "viewInvoices": "View Invoices", + "saving": "Saving...", + "userPermissionsTitle": "User Permissions", + "dialogClose": "Close", + "buy": "Buy", + "templates": "Templates", + "history": "History", + "business": "Business" } diff --git a/hesabixUI/hesabix_ui/lib/l10n/app_fa.arb b/hesabixUI/hesabix_ui/lib/l10n/app_fa.arb index 41b635d..96ae54e 100644 --- a/hesabixUI/hesabix_ui/lib/l10n/app_fa.arb +++ b/hesabixUI/hesabix_ui/lib/l10n/app_fa.arb @@ -836,6 +836,27 @@ "activePersons": "اشخاص فعال", "inactivePersons": "اشخاص غیرفعال", "personsByType": "اشخاص بر اساس نوع", - "update": "ویرایش" + "update": "ویرایش", + "collect": "وصول", + "transfer": "انتقال", + "charge": "شارژ", + "businessSettings": "تنظیمات کسب و کار", + "printSettings": "تنظیمات چاپ اسناد", + "eventHistory": "تاریخچه رویدادها", + "usersAndPermissions": "کاربران و دسترسی‌ها", + "storageSpace": "فضای ذخیره‌سازی", + "deleteFiles": "فایل‌ها", + "viewSmsHistory": "مشاهده تاریخچه پیامک‌ها", + "manageSmsTemplates": "مدیریت قالب‌های پیامک", + "viewMarketplace": "مشاهده افزونه‌ها", + "buyPlugins": "خرید افزونه‌ها", + "viewInvoices": "صورت حساب‌ها", + "saving": "در حال ذخیره...", + "userPermissionsTitle": "دسترسی‌های کاربر", + "dialogClose": "بستن", + "buy": "خرید", + "templates": "قالب‌ها", + "history": "تاریخچه", + "business": "کسب و کار" } diff --git a/hesabixUI/hesabix_ui/lib/l10n/app_localizations.dart b/hesabixUI/hesabix_ui/lib/l10n/app_localizations.dart index fe87f90..eae3198 100644 --- a/hesabixUI/hesabix_ui/lib/l10n/app_localizations.dart +++ b/hesabixUI/hesabix_ui/lib/l10n/app_localizations.dart @@ -4037,7 +4037,7 @@ abstract class AppLocalizations { /// No description provided for @deleteFiles. /// /// In en, this message translates to: - /// **'Delete Files'** + /// **'Files'** String get deleteFiles; /// No description provided for @smsPanel. @@ -4567,6 +4567,66 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Update'** String get update; + + /// No description provided for @collect. + /// + /// In en, this message translates to: + /// **'Collect'** + String get collect; + + /// No description provided for @transfer. + /// + /// In en, this message translates to: + /// **'Transfer'** + String get transfer; + + /// No description provided for @charge. + /// + /// In en, this message translates to: + /// **'Charge'** + String get charge; + + /// No description provided for @saving. + /// + /// In en, this message translates to: + /// **'Saving...'** + String get saving; + + /// No description provided for @userPermissionsTitle. + /// + /// In en, this message translates to: + /// **'User Permissions'** + String get userPermissionsTitle; + + /// No description provided for @dialogClose. + /// + /// In en, this message translates to: + /// **'Close'** + String get dialogClose; + + /// No description provided for @buy. + /// + /// In en, this message translates to: + /// **'Buy'** + String get buy; + + /// No description provided for @templates. + /// + /// In en, this message translates to: + /// **'Templates'** + String get templates; + + /// No description provided for @history. + /// + /// In en, this message translates to: + /// **'History'** + String get history; + + /// No description provided for @business. + /// + /// In en, this message translates to: + /// **'Business'** + String get business; } class _AppLocalizationsDelegate diff --git a/hesabixUI/hesabix_ui/lib/l10n/app_localizations_en.dart b/hesabixUI/hesabix_ui/lib/l10n/app_localizations_en.dart index 63d71fc..c29a554 100644 --- a/hesabixUI/hesabix_ui/lib/l10n/app_localizations_en.dart +++ b/hesabixUI/hesabix_ui/lib/l10n/app_localizations_en.dart @@ -2025,7 +2025,7 @@ class AppLocalizationsEn extends AppLocalizations { String get viewStorage => 'View Storage'; @override - String get deleteFiles => 'Delete Files'; + String get deleteFiles => 'Files'; @override String get smsPanel => 'SMS Panel'; @@ -2302,4 +2302,34 @@ class AppLocalizationsEn extends AppLocalizations { @override String get update => 'Update'; + + @override + String get collect => 'Collect'; + + @override + String get transfer => 'Transfer'; + + @override + String get charge => 'Charge'; + + @override + String get saving => 'Saving...'; + + @override + String get userPermissionsTitle => 'User Permissions'; + + @override + String get dialogClose => 'Close'; + + @override + String get buy => 'Buy'; + + @override + String get templates => 'Templates'; + + @override + String get history => 'History'; + + @override + String get business => 'Business'; } diff --git a/hesabixUI/hesabix_ui/lib/l10n/app_localizations_fa.dart b/hesabixUI/hesabix_ui/lib/l10n/app_localizations_fa.dart index 6304e29..8553251 100644 --- a/hesabixUI/hesabix_ui/lib/l10n/app_localizations_fa.dart +++ b/hesabixUI/hesabix_ui/lib/l10n/app_localizations_fa.dart @@ -2013,7 +2013,7 @@ class AppLocalizationsFa extends AppLocalizations { String get viewStorage => 'مشاهده فضای ذخیره‌سازی'; @override - String get deleteFiles => 'حذف فایل‌ها'; + String get deleteFiles => 'فایل‌ها'; @override String get smsPanel => 'پنل پیامک'; @@ -2286,4 +2286,34 @@ class AppLocalizationsFa extends AppLocalizations { @override String get update => 'ویرایش'; + + @override + String get collect => 'وصول'; + + @override + String get transfer => 'انتقال'; + + @override + String get charge => 'شارژ'; + + @override + String get saving => 'در حال ذخیره...'; + + @override + String get userPermissionsTitle => 'دسترسی‌های کاربر'; + + @override + String get dialogClose => 'بستن'; + + @override + String get buy => 'خرید'; + + @override + String get templates => 'قالب‌ها'; + + @override + String get history => 'تاریخچه'; + + @override + String get business => 'کسب و کار'; } diff --git a/hesabixUI/hesabix_ui/lib/pages/business/business_shell.dart b/hesabixUI/hesabix_ui/lib/pages/business/business_shell.dart index 5731d2c..534dc87 100644 --- a/hesabixUI/hesabix_ui/lib/pages/business/business_shell.dart +++ b/hesabixUI/hesabix_ui/lib/pages/business/business_shell.dart @@ -795,48 +795,32 @@ class _BusinessShellState extends State { size: 20, ) else if (item.hasAddButton) - GestureDetector( - onTap: () { - // Navigate to add new item - if (item.label == t.people) { - // Navigate to add person - _showAddPersonDialog(); - } else if (item.label == t.invoice) { - // Navigate to add invoice - } else if (item.label == t.receiptsAndPayments) { - // Navigate to add receipt/payment - } else if (item.label == t.transfers) { - // Navigate to add transfer - } else if (item.label == t.documents) { - // Navigate to add document - } else if (item.label == t.expenseAndIncome) { - // Navigate to add expense/income - } else if (item.label == t.reports) { - // Navigate to add report - } else if (item.label == t.inquiries) { - // Navigate to add inquiry - } else if (item.label == t.storageSpace) { - // Navigate to add storage space - } else if (item.label == t.taxpayers) { - // Navigate to add taxpayer - } else if (item.label == t.pluginMarketplace) { - // Navigate to add plugin - } - }, - child: Container( - width: 24, - height: 24, - decoration: BoxDecoration( - color: sideFg.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(4), + Builder(builder: (ctx) { + final section = _sectionForLabel(item.label, t); + final canAdd = section != null && (widget.authStore.hasBusinessPermission(section, 'add')); + if (!canAdd) return const SizedBox.shrink(); + return GestureDetector( + onTap: () { + if (item.label == t.people) { + _showAddPersonDialog(); + } + // سایر مسیرهای افزودن در آینده متصل می‌شوند + }, + child: Container( + width: 24, + height: 24, + decoration: BoxDecoration( + color: sideFg.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(4), + ), + child: Icon( + Icons.add, + size: 16, + color: sideFg, + ), ), - child: Icon( - Icons.add, - size: 16, - color: sideFg, - ), - ), - ), + ); + }), ], ], ), @@ -901,17 +885,41 @@ class _BusinessShellState extends State { ), ); } else if (item.type == _MenuItemType.simple) { + final section = _sectionForLabel(item.label, t); + final canAdd = section != null && (widget.authStore.hasBusinessPermission(section, 'add')); return ListTile( leading: Icon(item.selectedIcon, color: active ? activeFg : sideFg), title: Text(item.label, style: TextStyle(color: active ? activeFg : sideFg, fontWeight: active ? FontWeight.w600 : FontWeight.w400)), selected: active, selectedTileColor: activeBg, + trailing: (item.hasAddButton && canAdd) + ? GestureDetector( + onTap: () { + context.pop(); + // در حال حاضر فقط اشخاص پشتیبانی می‌شود + if (item.label == t.people) { + _showAddPersonDialog(); + } + }, + child: Container( + width: 24, + height: 24, + decoration: BoxDecoration( + color: sideFg.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(4), + ), + child: Icon(Icons.add, size: 16, color: sideFg), + ), + ) + : null, onTap: () { context.pop(); onSelect(i); }, ); } else if (item.type == _MenuItemType.expandable) { + // فیلتر کردن زیرآیتم‌ها بر اساس دسترسی + final visibleChildren = (item.children ?? []).where((child) => _hasAccessToMenuItem(child)).toList(); return ExpansionTile( leading: Icon(item.icon, color: sideFg), title: Text(item.label, style: TextStyle(color: sideFg)), @@ -924,10 +932,13 @@ class _BusinessShellState extends State { if (item.label == t.warehouseManagement) _isWarehouseManagementExpanded = expanded; }); }, - children: item.children?.map((child) => ListTile( + children: visibleChildren.map((child) { + final childSection = _sectionForLabel(child.label, t); + final childCanAdd = child.hasAddButton && (childSection != null && widget.authStore.hasBusinessPermission(childSection, 'add')); + return ListTile( leading: const SizedBox(width: 24), title: Text(child.label), - trailing: child.hasAddButton ? GestureDetector( + trailing: childCanAdd ? GestureDetector( onTap: () { context.pop(); // Navigate to add new item @@ -977,7 +988,7 @@ class _BusinessShellState extends State { context.pop(); onSelectChild(i, item.children!.indexOf(child)); }, - )).toList() ?? [], + }).toList(), ); } return const SizedBox.shrink(); @@ -1016,39 +1027,12 @@ class _BusinessShellState extends State { } bool _hasAccessToMenuItem(_MenuItem item) { - final sectionMap = { - 'people': 'people', - 'products': 'products', - 'priceLists': 'price_lists', - 'categories': 'categories', - 'productAttributes': 'product_attributes', - 'accounts': 'bank_accounts', - 'pettyCash': 'petty_cash', - 'cashBox': 'cash', - 'wallet': 'wallet', - 'checks': 'checks', - 'invoice': 'invoices', - 'receiptsAndPayments': 'accounting_documents', - 'expenseAndIncome': 'expenses_income', - 'transfers': 'transfers', - 'documents': 'accounting_documents', - 'chartOfAccounts': 'chart_of_accounts', - 'openingBalance': 'opening_balance', - 'yearEndClosing': 'opening_balance', - 'accountingSettings': 'settings', - 'reports': 'reports', - 'warehouses': 'warehouses', - 'shipments': 'warehouse_transfers', - 'inquiries': 'reports', - 'storageSpace': 'storage', - 'taxpayers': 'settings', - 'settings': 'settings', - 'pluginMarketplace': 'marketplace', - }; - - final section = sectionMap[item.label]; - if (section == null) return true; // اگر بخشی تعریف نشده، نمایش داده شود - + final section = _sectionForLabel(item.label, AppLocalizations.of(context)); + // داشبورد همیشه قابل مشاهده است + if (item.path != null && item.path!.endsWith('/dashboard')) return true; + // اگر سکشن تعریف نشده، نمایش داده نشود + if (section == null) return false; + // فقط وقتی اجازه خواندن دارد نمایش بده return widget.authStore.canReadSection(section); } @@ -1058,6 +1042,35 @@ class _BusinessShellState extends State { // اگر حداقل یکی از زیرآیتم‌ها قابل دسترسی باشد، منو نمایش داده شود return item.children!.any((child) => _hasAccessToMenuItem(child)); } + + // تبدیل برچسب محلی‌شده منو به کلید سکشن دسترسی + String? _sectionForLabel(String label, AppLocalizations t) { + if (label == t.people) return 'people'; + if (label == t.products) return 'products'; + if (label == t.priceLists) return 'price_lists'; + if (label == t.categories) return 'categories'; + if (label == t.productAttributes) return 'product_attributes'; + if (label == t.accounts) return 'bank_accounts'; + if (label == t.pettyCash) return 'petty_cash'; + if (label == t.cashBox) return 'cash'; + if (label == t.wallet) return 'wallet'; + if (label == t.checks) return 'checks'; + if (label == t.invoice) return 'invoices'; + if (label == t.receiptsAndPayments) return 'people_transactions'; + if (label == t.expenseAndIncome) return 'expenses_income'; + if (label == t.transfers) return 'transfers'; + if (label == t.documents) return 'accounting_documents'; + if (label == t.chartOfAccounts) return 'chart_of_accounts'; + if (label == t.openingBalance) return 'opening_balance'; + if (label == t.warehouses) return 'warehouses'; + if (label == t.shipments) return 'warehouse_transfers'; + if (label == t.inquiries) return 'reports'; + if (label == t.storageSpace) return 'storage'; + if (label == t.taxpayers) return 'settings'; + if (label == t.settings) return 'settings'; + if (label == t.pluginMarketplace) return 'marketplace'; + return null; + } } enum _MenuItemType { simple, expandable, separator } diff --git a/hesabixUI/hesabix_ui/lib/pages/business/settings_page.dart b/hesabixUI/hesabix_ui/lib/pages/business/settings_page.dart index e863058..71b2d96 100644 --- a/hesabixUI/hesabix_ui/lib/pages/business/settings_page.dart +++ b/hesabixUI/hesabix_ui/lib/pages/business/settings_page.dart @@ -61,7 +61,7 @@ class _SettingsPageState extends State { title: t.usersAndPermissions, subtitle: t.usersAndPermissionsDescription, icon: Icons.people_outline, - onTap: () => _showUsersPermissionsDialog(context), + onTap: () => context.go('/business/${widget.businessId}/users-permissions'), ), _buildSettingItem( context, @@ -240,30 +240,6 @@ class _SettingsPageState extends State { ); } - void _showUsersPermissionsDialog(BuildContext context) { - final t = AppLocalizations.of(context); - showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text(t.usersAndPermissions), - content: Text(t.usersAndPermissionsDialogContent), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(t.close), - ), - FilledButton( - onPressed: () { - Navigator.pop(context); - // Navigate to users permissions page - context.go('/business/${widget.businessId}/users-permissions'); - }, - child: Text(t.manage), - ), - ], - ), - ); - } void _showPrintDocumentsDialog(BuildContext context) { final t = AppLocalizations.of(context); diff --git a/hesabixUI/hesabix_ui/lib/pages/business/users_permissions_page.dart b/hesabixUI/hesabix_ui/lib/pages/business/users_permissions_page.dart index c7144ad..49a6f84 100644 --- a/hesabixUI/hesabix_ui/lib/pages/business/users_permissions_page.dart +++ b/hesabixUI/hesabix_ui/lib/pages/business/users_permissions_page.dart @@ -107,26 +107,6 @@ class _UsersPermissionsPageState extends State { } } - Future _updatePermissions(BusinessUser user, Map newPermissions) async { - try { - final request = UpdatePermissionsRequest( - businessId: int.parse(widget.businessId), - userId: user.userId, - permissions: newPermissions, - ); - - final response = await _userService.updatePermissions(request); - - if (response.success) { - _showSuccessSnackBar(response.message); - _loadUsers(); // Refresh the list - } else { - _showErrorSnackBar(response.message); - } - } catch (e) { - _showErrorSnackBar('${AppLocalizations.of(context).permissionsUpdateFailed}: $e'); - } - } Future _removeUser(BusinessUser user) async { final t = AppLocalizations.of(context); @@ -720,482 +700,652 @@ class _UsersPermissionsPageState extends State { ); } - void _showPermissionsDialog(BusinessUser user) { + void _showPermissionsDialog(BusinessUser user) async { + + // Load fresh user data with permissions + try { + final freshUser = await _userService.getUserDetails(int.parse(widget.businessId), user.userId); + + if (mounted) { + showDialog( + context: context, + builder: (context) => _PermissionsDialog( + user: freshUser, + businessId: widget.businessId, + userService: _userService, + onPermissionsUpdated: () { + // Refresh the users list after permissions are updated + _loadUsers(); + }, + ), + ); + } + } catch (e) { + if (mounted) { + _showErrorSnackBar('خطا در بارگذاری دسترسی‌ها: $e'); + } + } + } + +} + +/// دیالوگ مدیریت دسترسی‌ها +class _PermissionsDialog extends StatefulWidget { + final BusinessUser user; + final String businessId; + final BusinessUserService userService; + final VoidCallback onPermissionsUpdated; + + const _PermissionsDialog({ + required this.user, + required this.businessId, + required this.userService, + required this.onPermissionsUpdated, + }); + + @override + State<_PermissionsDialog> createState() => _PermissionsDialogState(); +} + +class _PermissionsDialogState extends State<_PermissionsDialog> { + late Map _currentPermissions; + bool _isUpdating = false; + + @override + void initState() { + super.initState(); + _currentPermissions = _mergePermissions(widget.user.permissions); + } + + /// ساختار کامل دسترسی‌های سیستم + Map> _getAllPermissions(AppLocalizations t) { + return { + 'people': { + 'add': '${t.add} ${t.people}', + 'view': '${t.view} ${t.people}', + 'edit': '${t.edit} ${t.people}', + 'delete': '${t.delete} ${t.people}', + }, + // Combined: receipts + payments of people as a single unit + 'people_transactions': { + 'add': '${t.add} ${t.receiptsAndPayments} ${t.people}', + 'view': '${t.view} ${t.receiptsAndPayments} ${t.people}', + 'edit': '${t.edit} ${t.receiptsAndPayments} ${t.people}', + 'delete': '${t.delete} ${t.receiptsAndPayments} ${t.people}', + 'draft': '${t.draft} ${t.receiptsAndPayments} ${t.people}', + }, + 'products': { + 'add': '${t.add} ${t.products}', + 'view': '${t.view} ${t.products}', + 'edit': '${t.edit} ${t.products}', + 'delete': '${t.delete} ${t.products}', + }, + 'price_lists': { + 'add': '${t.add} ${t.priceLists}', + 'view': '${t.view} ${t.priceLists}', + 'edit': '${t.edit} ${t.priceLists}', + 'delete': '${t.delete} ${t.priceLists}', + }, + 'categories': { + 'add': '${t.add} ${t.categories}', + 'view': '${t.view} ${t.categories}', + 'edit': '${t.edit} ${t.categories}', + 'delete': '${t.delete} ${t.categories}', + }, + 'product_attributes': { + 'add': '${t.add} ${t.productAttributes}', + 'view': '${t.view} ${t.productAttributes}', + 'edit': '${t.edit} ${t.productAttributes}', + 'delete': '${t.delete} ${t.productAttributes}', + }, + 'bank_accounts': { + 'add': '${t.add} ${t.bankAccounts}', + 'view': '${t.view} ${t.bankAccounts}', + 'edit': '${t.edit} ${t.bankAccounts}', + 'delete': '${t.delete} ${t.bankAccounts}', + }, + 'cash': { + 'add': '${t.add} ${t.cash}', + 'view': '${t.view} ${t.cash}', + 'edit': '${t.edit} ${t.cash}', + 'delete': '${t.delete} ${t.cash}', + }, + 'petty_cash': { + 'add': '${t.add} ${t.pettyCash}', + 'view': '${t.view} ${t.pettyCash}', + 'edit': '${t.edit} ${t.pettyCash}', + 'delete': '${t.delete} ${t.pettyCash}', + }, + 'checks': { + 'add': '${t.add} ${t.checks}', + 'view': '${t.view} ${t.checks}', + 'edit': '${t.edit} ${t.checks}', + 'delete': '${t.delete} ${t.checks}', + 'collect': '${t.collect} ${t.checks}', + 'transfer': '${t.transfer} ${t.checks}', + 'return': 'برگشت ${t.checks}', + }, + 'wallet': { + 'view': '${t.view} ${t.wallet}', + 'charge': '${t.charge} ${t.wallet}', + }, + 'transfers': { + 'add': '${t.add} ${t.transfers}', + 'view': '${t.view} ${t.transfers}', + 'edit': '${t.edit} ${t.transfers}', + 'delete': '${t.delete} ${t.transfers}', + 'draft': '${t.draft} ${t.transfers}', + }, + 'invoices': { + 'add': '${t.add} ${t.invoices}', + 'view': '${t.view} ${t.invoices}', + 'edit': '${t.edit} ${t.invoices}', + 'delete': '${t.delete} ${t.invoices}', + 'draft': '${t.draft} ${t.invoices}', + }, + 'expenses_income': { + 'add': '${t.add} ${t.expensesIncome}', + 'view': '${t.view} ${t.expensesIncome}', + 'edit': '${t.edit} ${t.expensesIncome}', + 'delete': '${t.delete} ${t.expensesIncome}', + 'draft': '${t.draft} ${t.expensesIncome}', + }, + 'accounting_documents': { + 'add': '${t.add} ${t.accountingDocuments}', + 'view': '${t.view} ${t.accountingDocuments}', + 'edit': '${t.edit} ${t.accountingDocuments}', + 'delete': '${t.delete} ${t.accountingDocuments}', + 'draft': '${t.draft} ${t.accountingDocuments}', + }, + 'chart_of_accounts': { + 'add': '${t.add} ${t.chartOfAccounts}', + 'view': '${t.view} ${t.chartOfAccounts}', + 'edit': '${t.edit} ${t.chartOfAccounts}', + 'delete': '${t.delete} ${t.chartOfAccounts}', + }, + 'opening_balance': { + 'view': '${t.view} ${t.openingBalance}', + 'edit': '${t.edit} ${t.openingBalance}', + }, + 'warehouses': { + 'add': '${t.add} ${t.warehouses}', + 'view': '${t.view} ${t.warehouses}', + 'edit': '${t.edit} ${t.warehouses}', + 'delete': '${t.delete} ${t.warehouses}', + }, + 'warehouse_transfers': { + 'add': '${t.add} ${t.warehouseTransfers}', + 'view': '${t.view} ${t.warehouseTransfers}', + 'edit': '${t.edit} ${t.warehouseTransfers}', + 'delete': '${t.delete} ${t.warehouseTransfers}', + 'draft': '${t.draft} ${t.warehouseTransfers}', + }, + 'settings': { + 'business': '${t.businessSettings}', + 'print': '${t.printSettings}', + 'history': '${t.eventHistory}', + 'users': '${t.usersAndPermissions}', + }, + 'storage': { + 'view': '${t.view} ${t.storageSpace}', + 'delete': '${t.delete} ${t.deleteFiles}', + }, + 'sms': { + 'history': '${t.viewSmsHistory}', + 'templates': '${t.manageSmsTemplates}', + }, + 'marketplace': { + 'view': '${t.viewMarketplace}', + 'buy': '${t.buyPlugins}', + 'invoices': '${t.viewInvoices}', + }, + }; + } + + /// ادغام دسترسی‌های دیتابیس با ساختار کامل + Map _mergePermissions(Map dbPermissions) { + final t = AppLocalizations.of(context); + final allPermissions = _getAllPermissions(t); + final mergedPermissions = {}; + + for (final section in allPermissions.keys) { + final sectionPermissions = {}; + + for (final action in allPermissions[section]!.keys) { + // فقط از سکشن جدید استفاده می‌کنیم؛ بدون OR با کلیدهای قدیمی + if (dbPermissions.containsKey(section) && + dbPermissions[section] is Map && + (dbPermissions[section] as Map).containsKey(action)) { + sectionPermissions[action] = dbPermissions[section][action] == true; + } else { + sectionPermissions[action] = false; + } + } + + mergedPermissions[section] = sectionPermissions; + } + + return mergedPermissions; + } + + /// دریافت دسترسی + bool _getPermission(Map permissions, String section, String action) { + if (!permissions.containsKey(section)) return false; + final sectionPerms = permissions[section] as Map?; + if (sectionPerms == null) return false; + return sectionPerms[action] == true; + } + + /// تنظیم دسترسی + void _setPermission(Map permissions, String section, String action, bool value) { + if (!permissions.containsKey(section)) { + permissions[section] = {}; + } + // Enforce dependency: view is prerequisite for other actions + if (action != 'view' && value == true) { + permissions[section]['view'] = true; + } + + // Prevent disabling view while dependent actions are enabled + if (action == 'view' && value == false) { + final sectionPerms = (permissions[section] as Map?) ?? {}; + final hasAnyOtherEnabled = sectionPerms.entries.any((e) => e.key != 'view' && e.value == true); + if (hasAnyOtherEnabled) { + // Keep view enabled; do not change + permissions[section]['view'] = true; + return; + } + } + + permissions[section][action] = value; + + // دیگر mirroring به کلیدهای قدیمی انجام نمی‌شود + if (section == 'people_transactions') { + // no-op: فقط روی people_transactions ذخیره می‌کنیم + } + } + + bool _hasAnyNonViewEnabled(String sectionKey) { + final sectionPerms = (_currentPermissions[sectionKey] as Map?) ?? {}; + for (final entry in sectionPerms.entries) { + if (entry.key != 'view' && entry.value == true) return true; + } + return false; + } + + /// به‌روزرسانی دسترسی‌ها + Future _updatePermissions() async { + if (_isUpdating) return; + + setState(() { + _isUpdating = true; + }); + + try { + final request = UpdatePermissionsRequest( + businessId: int.parse(widget.businessId), + userId: widget.user.userId, + permissions: _currentPermissions, + ); + + final response = await widget.userService.updatePermissions(request); + + if (mounted) { + if (response.success) { + _showSuccessSnackBar(response.message); + widget.onPermissionsUpdated(); + Navigator.of(context).pop(); + } else { + _showErrorSnackBar(response.message); + } + } + } catch (e) { + if (mounted) { + _showErrorSnackBar('خطا در به‌روزرسانی دسترسی‌ها: $e'); + } + } finally { + if (mounted) { + setState(() { + _isUpdating = false; + }); + } + } + } + + void _showErrorSnackBar(String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + backgroundColor: Colors.red, + ), + ); + } + + void _showSuccessSnackBar(String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + backgroundColor: Colors.green, + ), + ); + } + + @override + Widget build(BuildContext context) { final t = AppLocalizations.of(context); final theme = Theme.of(context); final colorScheme = theme.colorScheme; - - showDialog( - context: context, - builder: (context) => StatefulBuilder( - builder: (context, setState) { - Map currentPermissions = Map.from(user.permissions); return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), - child: Container( - width: 800, - height: 700, + child: SizedBox( + width: 800, + height: 700, child: Column( children: [ // Header - Container( - padding: const EdgeInsets.all(24), - decoration: BoxDecoration( - color: colorScheme.primaryContainer, - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Row( + children: [ + Icon(Icons.security, color: colorScheme.primary, size: 18), + const SizedBox(width: 8), + Expanded( + child: Text( + '${t.userPermissions} - ${widget.user.userName}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.w700, + color: colorScheme.onSurface, ), ), - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: colorScheme.primary, - borderRadius: BorderRadius.circular(12), - ), - child: Icon( - Icons.security, - color: colorScheme.onPrimary, - size: 24, - ), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '${t.userPermissions} - ${user.userName}', - style: theme.textTheme.titleLarge?.copyWith( - fontWeight: FontWeight.bold, - color: colorScheme.onPrimaryContainer, - ), - ), - const SizedBox(height: 4), - Text( - t.userPermissions, - style: theme.textTheme.bodyMedium?.copyWith( - color: colorScheme.onPrimaryContainer.withValues(alpha: 0.8), - ), - ), - ], - ), - ), - IconButton( - onPressed: () => context.pop(), - icon: Icon( - Icons.close, - color: colorScheme.onPrimaryContainer, - ), - ), - ], + ), + Tooltip( + message: t.dialogClose, + child: IconButton( + visualDensity: VisualDensity.compact, + padding: const EdgeInsets.all(6), + constraints: const BoxConstraints(minWidth: 32, minHeight: 32), + onPressed: () => Navigator.of(context).pop(), + icon: Icon(Icons.close, color: colorScheme.onSurfaceVariant, size: 18), ), ), + ], + ), + ), // Content Expanded( child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( - children: [ - // اشخاص - _buildPermissionSection( - t.people, - Icons.people, - [ - _buildPermissionGroup( - t.people, - [ - _buildPermissionItem(t.add, t.addPerson, _getPermission(currentPermissions, 'people', 'add'), (value) => _setPermission(currentPermissions, 'people', 'add', value), theme, colorScheme), - _buildPermissionItem(t.view, t.viewPeople, _getPermission(currentPermissions, 'people', 'view'), (value) => _setPermission(currentPermissions, 'people', 'view', value), theme, colorScheme), - _buildPermissionItem(t.edit, t.editPeople, _getPermission(currentPermissions, 'people', 'edit'), (value) => _setPermission(currentPermissions, 'people', 'edit', value), theme, colorScheme), - _buildPermissionItem(t.delete, t.deletePeople, _getPermission(currentPermissions, 'people', 'delete'), (value) => _setPermission(currentPermissions, 'people', 'delete', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - _buildPermissionGroup( - t.peopleReceipts, - [ - _buildPermissionItem(t.add, t.addReceipt, _getPermission(currentPermissions, 'people_receipts', 'add'), (value) => _setPermission(currentPermissions, 'people_receipts', 'add', value), theme, colorScheme), - _buildPermissionItem(t.view, t.viewReceipts, _getPermission(currentPermissions, 'people_receipts', 'view'), (value) => _setPermission(currentPermissions, 'people_receipts', 'view', value), theme, colorScheme), - _buildPermissionItem(t.edit, t.editReceipts, _getPermission(currentPermissions, 'people_receipts', 'edit'), (value) => _setPermission(currentPermissions, 'people_receipts', 'edit', value), theme, colorScheme), - _buildPermissionItem(t.delete, t.deleteReceipts, _getPermission(currentPermissions, 'people_receipts', 'delete'), (value) => _setPermission(currentPermissions, 'people_receipts', 'delete', value), theme, colorScheme), - _buildPermissionItem(t.draft, t.manageReceiptDrafts, _getPermission(currentPermissions, 'people_receipts', 'draft'), (value) => _setPermission(currentPermissions, 'people_receipts', 'draft', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - _buildPermissionGroup( - t.peoplePayments, - [ - _buildPermissionItem(t.add, t.addPayment, _getPermission(currentPermissions, 'people_payments', 'add'), (value) => _setPermission(currentPermissions, 'people_payments', 'add', value), theme, colorScheme), - _buildPermissionItem(t.view, t.viewPayments, _getPermission(currentPermissions, 'people_payments', 'view'), (value) => _setPermission(currentPermissions, 'people_payments', 'view', value), theme, colorScheme), - _buildPermissionItem(t.edit, t.editPayments, _getPermission(currentPermissions, 'people_payments', 'edit'), (value) => _setPermission(currentPermissions, 'people_payments', 'edit', value), theme, colorScheme), - _buildPermissionItem(t.delete, t.deletePayments, _getPermission(currentPermissions, 'people_payments', 'delete'), (value) => _setPermission(currentPermissions, 'people_payments', 'delete', value), theme, colorScheme), - _buildPermissionItem(t.draft, t.managePaymentDrafts, _getPermission(currentPermissions, 'people_payments', 'draft'), (value) => _setPermission(currentPermissions, 'people_payments', 'draft', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - ], - theme, - colorScheme, - ), - - const SizedBox(height: 20), - - // کالا و خدمات - _buildPermissionSection( - t.products, - Icons.inventory, - [ - _buildPermissionGroup( - t.products, - [ - _buildPermissionItem(t.add, t.addProduct, _getPermission(currentPermissions, 'products', 'add'), (value) => _setPermission(currentPermissions, 'products', 'add', value), theme, colorScheme), - _buildPermissionItem(t.view, t.viewProducts, _getPermission(currentPermissions, 'products', 'view'), (value) => _setPermission(currentPermissions, 'products', 'view', value), theme, colorScheme), - _buildPermissionItem(t.edit, t.editProducts, _getPermission(currentPermissions, 'products', 'edit'), (value) => _setPermission(currentPermissions, 'products', 'edit', value), theme, colorScheme), - _buildPermissionItem(t.delete, t.deleteProducts, _getPermission(currentPermissions, 'products', 'delete'), (value) => _setPermission(currentPermissions, 'products', 'delete', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - _buildPermissionGroup( - 'لیست‌های قیمت', - [ - _buildPermissionItem('افزودن', 'افزودن لیست قیمت', _getPermission(currentPermissions, 'price_lists', 'add'), (value) => _setPermission(currentPermissions, 'price_lists', 'add', value), theme, colorScheme), - _buildPermissionItem('مشاهده', 'مشاهده لیست‌های قیمت', _getPermission(currentPermissions, 'price_lists', 'view'), (value) => _setPermission(currentPermissions, 'price_lists', 'view', value), theme, colorScheme), - _buildPermissionItem('ویرایش', 'ویرایش لیست‌های قیمت', _getPermission(currentPermissions, 'price_lists', 'edit'), (value) => _setPermission(currentPermissions, 'price_lists', 'edit', value), theme, colorScheme), - _buildPermissionItem('حذف', 'حذف لیست‌های قیمت', _getPermission(currentPermissions, 'price_lists', 'delete'), (value) => _setPermission(currentPermissions, 'price_lists', 'delete', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - _buildPermissionGroup( - 'دسته‌بندی‌ها', - [ - _buildPermissionItem('افزودن', 'افزودن دسته‌بندی', _getPermission(currentPermissions, 'categories', 'add'), (value) => _setPermission(currentPermissions, 'categories', 'add', value), theme, colorScheme), - _buildPermissionItem('مشاهده', 'مشاهده دسته‌بندی‌ها', _getPermission(currentPermissions, 'categories', 'view'), (value) => _setPermission(currentPermissions, 'categories', 'view', value), theme, colorScheme), - _buildPermissionItem('ویرایش', 'ویرایش دسته‌بندی‌ها', _getPermission(currentPermissions, 'categories', 'edit'), (value) => _setPermission(currentPermissions, 'categories', 'edit', value), theme, colorScheme), - _buildPermissionItem('حذف', 'حذف دسته‌بندی‌ها', _getPermission(currentPermissions, 'categories', 'delete'), (value) => _setPermission(currentPermissions, 'categories', 'delete', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - _buildPermissionGroup( - 'ویژگی‌های کالا و خدمات', - [ - _buildPermissionItem('افزودن', 'افزودن ویژگی', _getPermission(currentPermissions, 'product_attributes', 'add'), (value) => _setPermission(currentPermissions, 'product_attributes', 'add', value), theme, colorScheme), - _buildPermissionItem('مشاهده', 'مشاهده ویژگی‌ها', _getPermission(currentPermissions, 'product_attributes', 'view'), (value) => _setPermission(currentPermissions, 'product_attributes', 'view', value), theme, colorScheme), - _buildPermissionItem('ویرایش', 'ویرایش ویژگی‌ها', _getPermission(currentPermissions, 'product_attributes', 'edit'), (value) => _setPermission(currentPermissions, 'product_attributes', 'edit', value), theme, colorScheme), - _buildPermissionItem('حذف', 'حذف ویژگی‌ها', _getPermission(currentPermissions, 'product_attributes', 'delete'), (value) => _setPermission(currentPermissions, 'product_attributes', 'delete', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - ], - theme, - colorScheme, - ), - - const SizedBox(height: 20), - - // بانکداری - _buildPermissionSection( - 'بانکداری', - Icons.account_balance, - [ - _buildPermissionGroup( - 'حساب‌های بانکی', - [ - _buildPermissionItem('افزودن', 'افزودن حساب بانکی', _getPermission(currentPermissions, 'bank_accounts', 'add'), (value) => _setPermission(currentPermissions, 'bank_accounts', 'add', value), theme, colorScheme), - _buildPermissionItem('مشاهده', 'مشاهده حساب‌های بانکی', _getPermission(currentPermissions, 'bank_accounts', 'view'), (value) => _setPermission(currentPermissions, 'bank_accounts', 'view', value), theme, colorScheme), - _buildPermissionItem('ویرایش', 'ویرایش حساب‌های بانکی', _getPermission(currentPermissions, 'bank_accounts', 'edit'), (value) => _setPermission(currentPermissions, 'bank_accounts', 'edit', value), theme, colorScheme), - _buildPermissionItem('حذف', 'حذف حساب‌های بانکی', _getPermission(currentPermissions, 'bank_accounts', 'delete'), (value) => _setPermission(currentPermissions, 'bank_accounts', 'delete', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - _buildPermissionGroup( - 'صندوق', - [ - _buildPermissionItem('افزودن', 'افزودن صندوق', _getPermission(currentPermissions, 'cash', 'add'), (value) => _setPermission(currentPermissions, 'cash', 'add', value), theme, colorScheme), - _buildPermissionItem('مشاهده', 'مشاهده صندوق‌ها', _getPermission(currentPermissions, 'cash', 'view'), (value) => _setPermission(currentPermissions, 'cash', 'view', value), theme, colorScheme), - _buildPermissionItem('ویرایش', 'ویرایش صندوق‌ها', _getPermission(currentPermissions, 'cash', 'edit'), (value) => _setPermission(currentPermissions, 'cash', 'edit', value), theme, colorScheme), - _buildPermissionItem('حذف', 'حذف صندوق‌ها', _getPermission(currentPermissions, 'cash', 'delete'), (value) => _setPermission(currentPermissions, 'cash', 'delete', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - _buildPermissionGroup( - 'تنخواه گردان', - [ - _buildPermissionItem('افزودن', 'افزودن تنخواه', _getPermission(currentPermissions, 'petty_cash', 'add'), (value) => _setPermission(currentPermissions, 'petty_cash', 'add', value), theme, colorScheme), - _buildPermissionItem('مشاهده', 'مشاهده تنخواه‌ها', _getPermission(currentPermissions, 'petty_cash', 'view'), (value) => _setPermission(currentPermissions, 'petty_cash', 'view', value), theme, colorScheme), - _buildPermissionItem('ویرایش', 'ویرایش تنخواه‌ها', _getPermission(currentPermissions, 'petty_cash', 'edit'), (value) => _setPermission(currentPermissions, 'petty_cash', 'edit', value), theme, colorScheme), - _buildPermissionItem('حذف', 'حذف تنخواه‌ها', _getPermission(currentPermissions, 'petty_cash', 'delete'), (value) => _setPermission(currentPermissions, 'petty_cash', 'delete', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - _buildPermissionGroup( - 'چک', - [ - _buildPermissionItem('افزودن', 'افزودن چک', _getPermission(currentPermissions, 'checks', 'add'), (value) => _setPermission(currentPermissions, 'checks', 'add', value), theme, colorScheme), - _buildPermissionItem('مشاهده', 'مشاهده چک‌ها', _getPermission(currentPermissions, 'checks', 'view'), (value) => _setPermission(currentPermissions, 'checks', 'view', value), theme, colorScheme), - _buildPermissionItem('ویرایش', 'ویرایش چک‌ها', _getPermission(currentPermissions, 'checks', 'edit'), (value) => _setPermission(currentPermissions, 'checks', 'edit', value), theme, colorScheme), - _buildPermissionItem('حذف', 'حذف چک‌ها', _getPermission(currentPermissions, 'checks', 'delete'), (value) => _setPermission(currentPermissions, 'checks', 'delete', value), theme, colorScheme), - _buildPermissionItem('وصول', 'وصول چک‌ها', _getPermission(currentPermissions, 'checks', 'collect'), (value) => _setPermission(currentPermissions, 'checks', 'collect', value), theme, colorScheme), - _buildPermissionItem('انتقال', 'انتقال چک‌ها', _getPermission(currentPermissions, 'checks', 'transfer'), (value) => _setPermission(currentPermissions, 'checks', 'transfer', value), theme, colorScheme), - _buildPermissionItem('برگشت', 'برگشت چک‌ها', _getPermission(currentPermissions, 'checks', 'return'), (value) => _setPermission(currentPermissions, 'checks', 'return', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - _buildPermissionGroup( - 'کیف پول', - [ - _buildPermissionItem('مشاهده', 'مشاهده کیف پول', _getPermission(currentPermissions, 'wallet', 'view'), (value) => _setPermission(currentPermissions, 'wallet', 'view', value), theme, colorScheme), - _buildPermissionItem('شارژ', 'شارژ کیف پول', _getPermission(currentPermissions, 'wallet', 'charge'), (value) => _setPermission(currentPermissions, 'wallet', 'charge', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - _buildPermissionGroup( - 'انتقال', - [ - _buildPermissionItem('افزودن', 'افزودن انتقال', _getPermission(currentPermissions, 'transfers', 'add'), (value) => _setPermission(currentPermissions, 'transfers', 'add', value), theme, colorScheme), - _buildPermissionItem('مشاهده', 'مشاهده انتقال‌ها', _getPermission(currentPermissions, 'transfers', 'view'), (value) => _setPermission(currentPermissions, 'transfers', 'view', value), theme, colorScheme), - _buildPermissionItem('ویرایش', 'ویرایش انتقال‌ها', _getPermission(currentPermissions, 'transfers', 'edit'), (value) => _setPermission(currentPermissions, 'transfers', 'edit', value), theme, colorScheme), - _buildPermissionItem('حذف', 'حذف انتقال‌ها', _getPermission(currentPermissions, 'transfers', 'delete'), (value) => _setPermission(currentPermissions, 'transfers', 'delete', value), theme, colorScheme), - _buildPermissionItem('مدیریت پیش‌نویس‌ها', 'مدیریت پیش‌نویس‌های انتقال', _getPermission(currentPermissions, 'transfers', 'draft'), (value) => _setPermission(currentPermissions, 'transfers', 'draft', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - ], - theme, - colorScheme, - ), - - const SizedBox(height: 20), - - // فاکتورها و هزینه‌ها - _buildPermissionSection( - 'فاکتورها و هزینه‌ها', - Icons.receipt, - [ - _buildPermissionGroup( - 'فاکتورها', - [ - _buildPermissionItem('افزودن', 'افزودن فاکتور', _getPermission(currentPermissions, 'invoices', 'add'), (value) => _setPermission(currentPermissions, 'invoices', 'add', value), theme, colorScheme), - _buildPermissionItem('مشاهده', 'مشاهده فاکتورها', _getPermission(currentPermissions, 'invoices', 'view'), (value) => _setPermission(currentPermissions, 'invoices', 'view', value), theme, colorScheme), - _buildPermissionItem('ویرایش', 'ویرایش فاکتورها', _getPermission(currentPermissions, 'invoices', 'edit'), (value) => _setPermission(currentPermissions, 'invoices', 'edit', value), theme, colorScheme), - _buildPermissionItem('حذف', 'حذف فاکتورها', _getPermission(currentPermissions, 'invoices', 'delete'), (value) => _setPermission(currentPermissions, 'invoices', 'delete', value), theme, colorScheme), - _buildPermissionItem('مدیریت پیش‌نویس‌ها', 'مدیریت پیش‌نویس‌های فاکتور', _getPermission(currentPermissions, 'invoices', 'draft'), (value) => _setPermission(currentPermissions, 'invoices', 'draft', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - _buildPermissionGroup( - 'هزینه و درآمد', - [ - _buildPermissionItem('افزودن', 'افزودن هزینه یا درآمد', _getPermission(currentPermissions, 'expenses_income', 'add'), (value) => _setPermission(currentPermissions, 'expenses_income', 'add', value), theme, colorScheme), - _buildPermissionItem('مشاهده', 'مشاهده هزینه‌ها و درآمدها', _getPermission(currentPermissions, 'expenses_income', 'view'), (value) => _setPermission(currentPermissions, 'expenses_income', 'view', value), theme, colorScheme), - _buildPermissionItem('ویرایش', 'ویرایش هزینه‌ها و درآمدها', _getPermission(currentPermissions, 'expenses_income', 'edit'), (value) => _setPermission(currentPermissions, 'expenses_income', 'edit', value), theme, colorScheme), - _buildPermissionItem('حذف', 'حذف هزینه‌ها و درآمدها', _getPermission(currentPermissions, 'expenses_income', 'delete'), (value) => _setPermission(currentPermissions, 'expenses_income', 'delete', value), theme, colorScheme), - _buildPermissionItem('مدیریت پیش‌نویس‌ها', 'مدیریت پیش‌نویس‌های هزینه و درآمد', _getPermission(currentPermissions, 'expenses_income', 'draft'), (value) => _setPermission(currentPermissions, 'expenses_income', 'draft', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - ], - theme, - colorScheme, - ), - - const SizedBox(height: 20), - - // حسابداری - _buildPermissionSection( - 'حسابداری', - Icons.calculate, - [ - _buildPermissionGroup( - 'اسناد حسابداری', - [ - _buildPermissionItem('افزودن', 'افزودن سند حسابداری', _getPermission(currentPermissions, 'accounting_documents', 'add'), (value) => _setPermission(currentPermissions, 'accounting_documents', 'add', value), theme, colorScheme), - _buildPermissionItem('مشاهده', 'مشاهده اسناد حسابداری', _getPermission(currentPermissions, 'accounting_documents', 'view'), (value) => _setPermission(currentPermissions, 'accounting_documents', 'view', value), theme, colorScheme), - _buildPermissionItem('ویرایش', 'ویرایش اسناد حسابداری', _getPermission(currentPermissions, 'accounting_documents', 'edit'), (value) => _setPermission(currentPermissions, 'accounting_documents', 'edit', value), theme, colorScheme), - _buildPermissionItem('حذف', 'حذف اسناد حسابداری', _getPermission(currentPermissions, 'accounting_documents', 'delete'), (value) => _setPermission(currentPermissions, 'accounting_documents', 'delete', value), theme, colorScheme), - _buildPermissionItem('مدیریت پیش‌نویس‌ها', 'مدیریت پیش‌نویس‌های اسناد', _getPermission(currentPermissions, 'accounting_documents', 'draft'), (value) => _setPermission(currentPermissions, 'accounting_documents', 'draft', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - _buildPermissionGroup( - 'جدول حساب‌ها', - [ - _buildPermissionItem('افزودن', 'افزودن حساب', _getPermission(currentPermissions, 'chart_of_accounts', 'add'), (value) => _setPermission(currentPermissions, 'chart_of_accounts', 'add', value), theme, colorScheme), - _buildPermissionItem('مشاهده', 'مشاهده جدول حساب‌ها', _getPermission(currentPermissions, 'chart_of_accounts', 'view'), (value) => _setPermission(currentPermissions, 'chart_of_accounts', 'view', value), theme, colorScheme), - _buildPermissionItem('ویرایش', 'ویرایش جدول حساب‌ها', _getPermission(currentPermissions, 'chart_of_accounts', 'edit'), (value) => _setPermission(currentPermissions, 'chart_of_accounts', 'edit', value), theme, colorScheme), - _buildPermissionItem('حذف', 'حذف حساب‌ها', _getPermission(currentPermissions, 'chart_of_accounts', 'delete'), (value) => _setPermission(currentPermissions, 'chart_of_accounts', 'delete', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - _buildPermissionGroup( - 'تراز افتتاحیه', - [ - _buildPermissionItem('مشاهده', 'مشاهده تراز افتتاحیه', _getPermission(currentPermissions, 'opening_balance', 'view'), (value) => _setPermission(currentPermissions, 'opening_balance', 'view', value), theme, colorScheme), - _buildPermissionItem('ویرایش', 'ویرایش تراز افتتاحیه', _getPermission(currentPermissions, 'opening_balance', 'edit'), (value) => _setPermission(currentPermissions, 'opening_balance', 'edit', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - ], - theme, - colorScheme, - ), - - const SizedBox(height: 20), - - // انبارداری - _buildPermissionSection( - 'انبارداری', - Icons.warehouse, - [ - _buildPermissionGroup( - 'مدیریت انبارها', - [ - _buildPermissionItem('افزودن', 'افزودن انبار', _getPermission(currentPermissions, 'warehouses', 'add'), (value) => _setPermission(currentPermissions, 'warehouses', 'add', value), theme, colorScheme), - _buildPermissionItem('مشاهده', 'مشاهده انبارها', _getPermission(currentPermissions, 'warehouses', 'view'), (value) => _setPermission(currentPermissions, 'warehouses', 'view', value), theme, colorScheme), - _buildPermissionItem('ویرایش', 'ویرایش انبارها', _getPermission(currentPermissions, 'warehouses', 'edit'), (value) => _setPermission(currentPermissions, 'warehouses', 'edit', value), theme, colorScheme), - _buildPermissionItem('حذف', 'حذف انبارها', _getPermission(currentPermissions, 'warehouses', 'delete'), (value) => _setPermission(currentPermissions, 'warehouses', 'delete', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - _buildPermissionGroup( - 'صدور حواله', - [ - _buildPermissionItem('افزودن', 'افزودن حواله', _getPermission(currentPermissions, 'warehouse_transfers', 'add'), (value) => _setPermission(currentPermissions, 'warehouse_transfers', 'add', value), theme, colorScheme), - _buildPermissionItem('مشاهده', 'مشاهده حواله‌ها', _getPermission(currentPermissions, 'warehouse_transfers', 'view'), (value) => _setPermission(currentPermissions, 'warehouse_transfers', 'view', value), theme, colorScheme), - _buildPermissionItem('ویرایش', 'ویرایش حواله‌ها', _getPermission(currentPermissions, 'warehouse_transfers', 'edit'), (value) => _setPermission(currentPermissions, 'warehouse_transfers', 'edit', value), theme, colorScheme), - _buildPermissionItem('حذف', 'حذف حواله‌ها', _getPermission(currentPermissions, 'warehouse_transfers', 'delete'), (value) => _setPermission(currentPermissions, 'warehouse_transfers', 'delete', value), theme, colorScheme), - _buildPermissionItem('مدیریت پیش‌نویس‌ها', 'مدیریت پیش‌نویس‌های حواله', _getPermission(currentPermissions, 'warehouse_transfers', 'draft'), (value) => _setPermission(currentPermissions, 'warehouse_transfers', 'draft', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - ], - theme, - colorScheme, - ), - - const SizedBox(height: 20), - - // تنظیمات - _buildPermissionSection( - 'تنظیمات', - Icons.settings, - [ - _buildPermissionGroup( - 'تنظیمات', - [ - _buildPermissionItem('تنظیمات کسب و کار', 'مدیریت تنظیمات کسب و کار', _getPermission(currentPermissions, 'settings', 'business'), (value) => _setPermission(currentPermissions, 'settings', 'business', value), theme, colorScheme), - _buildPermissionItem('تنظیمات چاپ اسناد', 'مدیریت تنظیمات چاپ', _getPermission(currentPermissions, 'settings', 'print'), (value) => _setPermission(currentPermissions, 'settings', 'print', value), theme, colorScheme), - _buildPermissionItem('تاریخچه رویدادها', 'مشاهده تاریخچه رویدادها', _getPermission(currentPermissions, 'settings', 'history'), (value) => _setPermission(currentPermissions, 'settings', 'history', value), theme, colorScheme), - _buildPermissionItem('کاربران و دسترسی‌ها', 'مدیریت کاربران و دسترسی‌ها', _getPermission(currentPermissions, 'settings', 'users'), (value) => _setPermission(currentPermissions, 'settings', 'users', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - _buildPermissionGroup( - 'فضای ذخیره‌سازی', - [ - _buildPermissionItem('مشاهده', 'مشاهده فضای ذخیره‌سازی', _getPermission(currentPermissions, 'storage', 'view'), (value) => _setPermission(currentPermissions, 'storage', 'view', value), theme, colorScheme), - _buildPermissionItem('حذف', 'حذف فایل‌ها', _getPermission(currentPermissions, 'storage', 'delete'), (value) => _setPermission(currentPermissions, 'storage', 'delete', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - _buildPermissionGroup( - 'پنل پیامک', - [ - _buildPermissionItem('مشاهده تاریخچه', 'مشاهده تاریخچه پیامک‌ها', _getPermission(currentPermissions, 'sms', 'history'), (value) => _setPermission(currentPermissions, 'sms', 'history', value), theme, colorScheme), - _buildPermissionItem('مدیریت قالب‌ها', 'مدیریت قالب‌های پیامک', _getPermission(currentPermissions, 'sms', 'templates'), (value) => _setPermission(currentPermissions, 'sms', 'templates', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - _buildPermissionGroup( - 'بازار افزونه‌ها', - [ - _buildPermissionItem('مشاهده', 'مشاهده افزونه‌ها', _getPermission(currentPermissions, 'marketplace', 'view'), (value) => _setPermission(currentPermissions, 'marketplace', 'view', value), theme, colorScheme), - _buildPermissionItem('خرید', 'خرید افزونه‌ها', _getPermission(currentPermissions, 'marketplace', 'buy'), (value) => _setPermission(currentPermissions, 'marketplace', 'buy', value), theme, colorScheme), - _buildPermissionItem('صورت حساب‌ها', 'مشاهده صورت حساب‌ها', _getPermission(currentPermissions, 'marketplace', 'invoices'), (value) => _setPermission(currentPermissions, 'marketplace', 'invoices', value), theme, colorScheme), - ], - theme, - colorScheme, - ), - ], - theme, - colorScheme, - ), - ], - ), + children: _buildAllPermissionSections(t, theme, colorScheme), + ), ), ), // Actions - Container( - padding: const EdgeInsets.all(24), - decoration: BoxDecoration( - color: colorScheme.surfaceContainerHighest, - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(20), - bottomRight: Radius.circular(20), - ), + Padding( + padding: const EdgeInsets.fromLTRB(12, 8, 12, 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + visualDensity: VisualDensity.compact, ), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () => context.pop(), - child: Text(t.cancel), - ), - const SizedBox(width: 12), - ElevatedButton.icon( - onPressed: () { - _updatePermissions(user, currentPermissions); - context.pop(); - }, - icon: const Icon(Icons.save, size: 18), - label: Text(t.savePermissions), - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - ), - ], + onPressed: _isUpdating ? null : () => Navigator.of(context).pop(), + child: Text(t.cancel), + ), + const SizedBox(width: 8), + ElevatedButton.icon( + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), + visualDensity: VisualDensity.compact, + minimumSize: const Size(0, 36), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), + ), + onPressed: _isUpdating ? null : _updatePermissions, + icon: _isUpdating + ? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2)) + : const Icon(Icons.save, size: 16), + label: Text( + _isUpdating ? t.saving : t.savePermissions, + style: theme.textTheme.labelLarge, ), ), ], ), + ), + ], + ), ), ); - }, - ), - ); + } + + /// ساخت تمام بخش‌های دسترسی به صورت پویا + List _buildAllPermissionSections( + AppLocalizations t, + ThemeData theme, + ColorScheme colorScheme, + ) { + final sections = []; + + // تعریف بخش‌ها و آیکون‌هایشان + final sectionConfigs = [ + { + 'title': t.people, + 'icon': Icons.people, + 'sections': ['people', 'people_transactions'], + }, + { + 'title': t.products, + 'icon': Icons.inventory, + 'sections': ['products', 'price_lists', 'categories', 'product_attributes'], + }, + { + 'title': 'بانکداری', + 'icon': Icons.account_balance, + 'sections': ['bank_accounts', 'cash', 'petty_cash', 'checks', 'wallet', 'transfers'], + }, + { + 'title': 'فاکتورها و هزینه‌ها', + 'icon': Icons.receipt, + 'sections': ['invoices', 'expenses_income'], + }, + { + 'title': 'حسابداری', + 'icon': Icons.calculate, + 'sections': ['accounting_documents', 'chart_of_accounts', 'opening_balance'], + }, + { + 'title': 'انبارداری', + 'icon': Icons.warehouse, + 'sections': ['warehouses', 'warehouse_transfers'], + }, + { + 'title': 'تنظیمات', + 'icon': Icons.settings, + 'sections': ['settings', 'storage', 'sms', 'marketplace'], + }, + ]; + + for (int i = 0; i < sectionConfigs.length; i++) { + final config = sectionConfigs[i]; + final sectionWidgets = []; + + // ساخت گروه‌های دسترسی برای هر بخش + for (final sectionKey in config['sections'] as List) { + final allPermissions = _getAllPermissions(t); + if (allPermissions.containsKey(sectionKey)) { + final sectionPermissions = allPermissions[sectionKey]!; + final permissionItems = []; + + for (final action in sectionPermissions.keys) { + permissionItems.add( + _buildPermissionItem( + _localizeAction(action), + sectionPermissions[action]!, + _getPermission(_currentPermissions, sectionKey, action), + (value) { + _setPermission(_currentPermissions, sectionKey, action, value); + setState(() {}); + }, + theme, + colorScheme, + ), + ); + } + + sectionWidgets.add( + _buildPermissionGroup( + _getSectionTitle(sectionKey), + permissionItems, + theme, + colorScheme, + ), + ); + } + } + + sections.add( + _buildPermissionSection( + config['title'] as String, + config['icon'] as IconData, + sectionWidgets, + theme, + colorScheme, + ), + ); + + // اضافه کردن فاصله بین بخش‌ها + if (i < sectionConfigs.length - 1) { + sections.add(const SizedBox(height: 20)); + } + } + + return sections; + } + + // سعی می‌کند کلید بخش فعلی را برای آیتم تعیین کند (تقریبی) + String _inferCurrentSectionKey(String title, String description) { + // جستجو بر اساس کلمات کلیدی ساده + final pairs = >{ + 'people': ['${AppLocalizations.of(context).people}'], + 'people_transactions': [ + '${AppLocalizations.of(context).receiptsAndPayments}', + '${AppLocalizations.of(context).receipts}', + '${AppLocalizations.of(context).payments}', + ], + 'products': ['${AppLocalizations.of(context).products}'], + 'price_lists': ['${AppLocalizations.of(context).priceLists}'], + 'categories': ['${AppLocalizations.of(context).categories}'], + 'product_attributes': ['${AppLocalizations.of(context).productAttributes}'], + 'bank_accounts': ['${AppLocalizations.of(context).bankAccounts}'], + 'cash': ['${AppLocalizations.of(context).cash}'], + 'petty_cash': ['${AppLocalizations.of(context).pettyCash}'], + 'checks': ['${AppLocalizations.of(context).checks}'], + 'wallet': ['${AppLocalizations.of(context).wallet}'], + 'transfers': ['${AppLocalizations.of(context).transfers}'], + 'invoices': ['${AppLocalizations.of(context).invoices}'], + 'expenses_income': ['${AppLocalizations.of(context).expensesIncome}'], + 'accounting_documents': ['${AppLocalizations.of(context).accountingDocuments}'], + 'chart_of_accounts': ['${AppLocalizations.of(context).chartOfAccounts}'], + 'opening_balance': ['${AppLocalizations.of(context).openingBalance}'], + 'warehouses': ['${AppLocalizations.of(context).warehouses}'], + 'warehouse_transfers': ['${AppLocalizations.of(context).warehouseTransfers}'], + 'settings': ['${AppLocalizations.of(context).businessSettings}'], + 'storage': ['${AppLocalizations.of(context).storageSpace}'], + 'sms': ['${AppLocalizations.of(context).smsPanel}'], + 'marketplace': ['${AppLocalizations.of(context).marketplace}'], + }; + + final hay = (title + ' ' + description).toLowerCase(); + for (final entry in pairs.entries) { + for (final token in entry.value) { + if (hay.contains(token.toLowerCase())) { + return entry.key; + } + } + } + return 'people'; + } + + /// دریافت عنوان بخش بر اساس کلید + String _getSectionTitle(String sectionKey) { + final titles = { + 'people': 'اشخاص', + 'people_receipts': 'دریافت از اشخاص', + 'people_payments': 'پرداخت‌های اشخاص', + 'people_transactions': '${AppLocalizations.of(context).receiptsAndPayments} ${AppLocalizations.of(context).people}', + 'products': 'کالا و خدمات', + 'price_lists': 'لیست‌های قیمت', + 'categories': 'دسته‌بندی‌ها', + 'product_attributes': 'ویژگی‌های کالا و خدمات', + 'bank_accounts': 'حساب‌های بانکی', + 'cash': 'صندوق', + 'petty_cash': 'تنخواه گردان', + 'checks': 'چک', + 'wallet': 'کیف پول', + 'transfers': 'انتقال', + 'invoices': 'فاکتورها', + 'expenses_income': 'هزینه و درآمد', + 'accounting_documents': 'اسناد حسابداری', + 'chart_of_accounts': 'جدول حساب‌ها', + 'opening_balance': 'تراز افتتاحیه', + 'warehouses': 'مدیریت انبارها', + 'warehouse_transfers': 'صدور حواله', + 'settings': 'تنظیمات', + 'storage': 'فضای ذخیره‌سازی', + 'sms': 'پنل پیامک', + 'marketplace': 'بازار افزونه‌ها', + }; + + return titles[sectionKey] ?? sectionKey; + } + + String _localizeAction(String action) { + final t = AppLocalizations.of(context); + switch (action) { + case 'add': + return t.add; + case 'view': + return t.view; + case 'edit': + return t.edit; + case 'delete': + return t.delete; + case 'draft': + return t.draft; + case 'buy': + return t.buy; + case 'invoices': + return t.invoices; + case 'templates': + return t.templates; + case 'history': + return t.history; + case 'print': + return t.print; + case 'users': + return t.users; + case 'business': + return t.business; + case 'collect': + return t.collect; + case 'transfer': + return t.transfer; + case 'charge': + return t.charge; + case 'return': + return 'برگشت'; + default: + return action; + } } Widget _buildPermissionSection( @@ -1297,14 +1447,17 @@ class _UsersPermissionsPageState extends State { ThemeData theme, ColorScheme colorScheme, ) { + final isViewItem = title == 'view' || description.contains('مشاهده') || description.toLowerCase().contains('view'); + final sectionKey = _inferCurrentSectionKey(title, description); + final mustKeepViewEnabled = isViewItem && _hasAnyNonViewEnabled(sectionKey); return Container( margin: const EdgeInsets.only(bottom: 8), child: Row( children: [ Switch( value: value, - onChanged: onChanged, - activeColor: colorScheme.primary, + onChanged: mustKeepViewEnabled ? null : onChanged, + activeThumbColor: colorScheme.primary, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, ), const SizedBox(width: 12), @@ -1334,20 +1487,4 @@ class _UsersPermissionsPageState extends State { ), ); } - - - bool _getPermission(Map permissions, String section, String action) { - if (!permissions.containsKey(section)) return false; - final sectionPerms = permissions[section] as Map?; - if (sectionPerms == null) return false; - return sectionPerms[action] == true; - } - - void _setPermission(Map permissions, String section, String action, bool value) { - if (!permissions.containsKey(section)) { - permissions[section] = {}; - } - permissions[section][action] = value; - } - -} \ No newline at end of file +}