Compare commits

...

1 Commits

Author SHA1 Message Date
kelly
4fc95ee712 feat: add visibility guards and Shared badge for child division menu items
- Add shared_from_parent flag to division alias menu items:
  - requisitions (Purchasing section)
  - division_vendors (Accounting section)
  - division_ar_snapshot (Accounting section)
  - division_ap_snapshot (Accounting section)

- Add permission guard for requisitions menu item via userCanSubmitRequisitions()
  - Checks for can_submit_requisition, can_view_requisitions, can_manage_requisitions
  - Business owners and admins always have access

- Update resolveMenuItems() to pass through shared_from_parent flag

- Update seller-sidebar-suites.blade.php to render "Shared" badge
  - Small uppercase badge appears next to shared menu items
  - Uses DaisyUI-style pill: bg-base-200, text-base-content/60

Guard logic ensures:
- Standalone SaaS businesses (no parent_id) never see division alias items
- Child businesses see aliases only when user has appropriate permissions
- Shared items are visually marked with badge
2025-12-07 11:39:15 -07:00
2 changed files with 67 additions and 4 deletions

View File

@@ -165,6 +165,7 @@ class SuiteMenuResolver
// ═══════════════════════════════════════════════════════════════
// PURCHASING (for child businesses / divisions)
// Staff submit requisitions, parent company approves & creates POs
// Only shown when business has parent_id (is a division)
// ═══════════════════════════════════════════════════════════════
'requisitions' => [
'label' => 'Requisitions',
@@ -173,6 +174,7 @@ class SuiteMenuResolver
'section' => 'Purchasing',
'order' => 260,
'requires_route' => true,
'shared_from_parent' => true,
],
// ═══════════════════════════════════════════════════════════════
@@ -187,6 +189,7 @@ class SuiteMenuResolver
'section' => 'Accounting',
'order' => 270,
'requires_route' => true,
'shared_from_parent' => true,
],
'division_ar_snapshot' => [
'label' => 'AR Snapshot',
@@ -195,6 +198,7 @@ class SuiteMenuResolver
'section' => 'Accounting',
'order' => 280,
'requires_route' => true,
'shared_from_parent' => true,
],
'division_ap_snapshot' => [
'label' => 'AP Snapshot',
@@ -203,6 +207,7 @@ class SuiteMenuResolver
'section' => 'Accounting',
'order' => 290,
'requires_route' => true,
'shared_from_parent' => true,
],
// ═══════════════════════════════════════════════════════════════
@@ -905,8 +910,10 @@ class SuiteMenuResolver
{
$keys = [];
// Requisitions - always available to divisions
$keys[] = 'requisitions';
// Requisitions - only if user has requisition permission
if ($user === null || $this->userCanSubmitRequisitions($user, $business)) {
$keys[] = 'requisitions';
}
// Accounting aliases - only if user has accounting view permission
if ($user === null || $this->userCanViewAccountingAliases($user, $business)) {
@@ -918,6 +925,50 @@ class SuiteMenuResolver
return $keys;
}
/**
* Check if a user can submit requisitions in a child business.
*
* Returns true if user has requisition permission or is the business owner.
*/
protected function userCanSubmitRequisitions(User $user, Business $business): bool
{
// Business owners always can submit
if ($this->isBusinessOwner($user, $business)) {
return true;
}
// Admins always can submit
if ($user->user_type === 'admin') {
return true;
}
// Check for requisition-related permissions in pivot
$pivot = $user->businesses()
->where('businesses.id', $business->id)
->first()
?->pivot;
if (! $pivot) {
return false;
}
$permissions = $pivot->permissions ?? [];
$requisitionPermissions = [
'can_submit_requisition',
'can_view_requisitions',
'can_manage_requisitions',
'*',
];
foreach ($requisitionPermissions as $perm) {
if (in_array($perm, $permissions)) {
return true;
}
}
return false;
}
/**
* Filter Management Suite menu keys for Canopy parent based on visibility rules.
*
@@ -1286,7 +1337,7 @@ class SuiteMenuResolver
continue;
}
$items[] = [
$item = [
'key' => $key,
'label' => $definition['label'],
'icon' => $definition['icon'],
@@ -1295,6 +1346,13 @@ class SuiteMenuResolver
'order' => $definition['order'],
'url' => $url,
];
// Pass through shared_from_parent flag for division alias items
if (! empty($definition['shared_from_parent'])) {
$item['shared_from_parent'] = true;
}
$items[] = $item;
}
return $items;

View File

@@ -124,7 +124,12 @@
@foreach($items as $item)
<a class="sidebar-item {{ request()->routeIs($item['route']) || request()->routeIs($item['route'] . '.*') ? 'active' : '' }}"
href="{{ $item['url'] }}">
{{ $item['label'] }}
<span class="flex items-center justify-between w-full">
<span>{{ $item['label'] }}</span>
@if(!empty($item['shared_from_parent']))
<span class="ml-1 text-[9px] px-1 py-0.5 rounded bg-base-200 text-base-content/60 uppercase tracking-wide">Shared</span>
@endif
</span>
</a>
@endforeach
</div>