progress in permissions
This commit is contained in:
parent
898e0fb993
commit
2e0c68967b
|
|
@ -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 {}
|
||||
|
||||
|
|
|
|||
|
|
@ -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="دریافت لیست کاربران یک کسب و کار",
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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}');
|
||||
|
|
|
|||
|
|
@ -317,15 +317,16 @@ class AuthStore with ChangeNotifier {
|
|||
if (_businessPermissions == null) return false;
|
||||
|
||||
final sectionPerms = _businessPermissions![section] as Map<String, dynamic>?;
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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": "کسب و کار"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 => 'کسب و کار';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -795,33 +795,16 @@ class _BusinessShellState extends State<BusinessShell> {
|
|||
size: 20,
|
||||
)
|
||||
else if (item.hasAddButton)
|
||||
GestureDetector(
|
||||
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: () {
|
||||
// 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,
|
||||
|
|
@ -836,7 +819,8 @@ class _BusinessShellState extends State<BusinessShell> {
|
|||
color: sideFg,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
],
|
||||
),
|
||||
|
|
@ -901,17 +885,41 @@ class _BusinessShellState extends State<BusinessShell> {
|
|||
),
|
||||
);
|
||||
} 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<BusinessShell> {
|
|||
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<BusinessShell> {
|
|||
context.pop();
|
||||
onSelectChild(i, item.children!.indexOf(child));
|
||||
},
|
||||
)).toList() ?? [],
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
|
|
@ -1016,39 +1027,12 @@ class _BusinessShellState extends State<BusinessShell> {
|
|||
}
|
||||
|
||||
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<BusinessShell> {
|
|||
// اگر حداقل یکی از زیرآیتمها قابل دسترسی باشد، منو نمایش داده شود
|
||||
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 }
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ class _SettingsPageState extends State<SettingsPage> {
|
|||
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<SettingsPage> {
|
|||
);
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue