--- task: enable store owner to delete and edit stores he owns in manage stores admin page cycles: 5 context: true private: false started: 2026-05-16T00:00:00Z finished: 2026-05-16T00:01:00Z --- ## files - resources/js/Pages/ManageStoresAdmin.vue — frontend page; already has canModifyStore checks and edit/delete buttons conditioned on `store.user_can_manage`; no changes needed unless navigation from service grid is added - resources/js/Pages/Fragments/Home/HomeStoreOwner.vue [lines 34-60] — store owner home page services grid; has "My Stores" in `balanceFooterItems` → ManageStoresAdmin but NOT in the services grid - app/Http/Controllers/Market/StoreController.php [lines 626-732] — `update()` method; allows edit if `$store->owner_id === $user->id` but does NOT check store_managers pivot table, causing a mismatch with `user_can_manage` flag - app/Http/Controllers/Market/StoreController.php [lines 1403-1435] — `deleteStore_Admin()` method; allows owner to delete but uses `catch (Exception $e)` without backslash (namespace bug) and does NOT check store_managers pivot - app/Http/Controllers/Market/StoreController.php [lines 1437-1475] — `toggleStoreStatus_Admin()` method; same `catch (Exception $e)` bug - app/Http/Controllers/Market/StoreController.php [lines 1297-1353] — `listStores_Admin()`: sets `user_can_manage = true` when user is in `allowedUserIds` (includes owner, manager_id, and managers pivot) — but backend edit/delete endpoints don't fully mirror this logic ## steps 1. In `StoreController.php` `deleteStore_Admin()` (line ~1425): fix `catch (Exception $e)` → `catch (\Exception $e)` (namespace issue; unqualified `Exception` in a namespace resolves to `App\Http\Controllers\Market\Exception` which doesn't exist) 2. In `StoreController.php` `toggleStoreStatus_Admin()` (line ~1468): same fix — `catch (Exception $e)` → `catch (\Exception $e)` 3. In `StoreController.php` `deleteStore_Admin()` (line ~1423): after `$allowedIds` is defined, add a check for managers pivot so any user listed in `store_managers` table can also delete (currently only checks `owner_id` and `manager_id` columns). Add: ```php $isManagerViaPivot = $store->managers()->whereIn('user_id', $allowedIds)->exists(); if (!in_array($store->owner_id, $allowedIds) && !in_array($store->manager_id, $allowedIds) && !$isManagerViaPivot) { return ResponseHelper::returnUnauthorized(); } ``` 4. In `StoreController.php` `update()` (line ~648): the permission check only allows: isBig3, isParentOfOwner, or `$store->owner_id === $user->id`. Add store managers pivot check so managers assigned via pivot can also edit: ```php $isStoreOwner = $acctType === UserTypes::STORE_OWNER; $isManagerViaPivot = $store->managers()->where('user_id', $user->id)->exists(); $isDirectManager = $store->manager_id === $user->id; if (!$isBig3 && !$isParentOfOwner && $store->owner_id !== $user->id && !$isDirectManager && !$isManagerViaPivot) { return response()->json(['error' => 'Unauthorized to modify this store'], 403); } ``` Also guard `owner_id`/`manager_id` changes for STORE_OWNER in update — they should not be able to reassign ownership away from themselves (mirror the restriction in `store()` create method): ```php if ($isStoreOwner) { $ownerId = $store->owner_id; // lock owner to self } ``` 5. In `HomeStoreOwner.vue` `services` computed (line ~34): add a "Manage Stores" service button so store owners can navigate directly from their service grid (currently only accessible via balance box footer): ```js { icon: 'https://cdn.jsdelivr.net/gh/telemagnadon/obj-vault-3a@v2026.05.14-vendor-2/a/85605eacd4c8.bin', title: 'Manage Stores', pagename: 'ManageStoresAdmin', }, ``` ## context ### StoreController.php — update() permission block (lines 644-651) ```php $acctType = $user->acct_type instanceof UserTypes ? $user->acct_type : UserTypes::tryFrom($user->acct_type); $isBig3 = in_array($acctType, [UserTypes::ULTIMATE, UserTypes::SUPER_OPERATOR, UserTypes::OPERATOR]); // Permission check: Big 3 or Parent of Owner/Manager $isParentOfOwner = UserPermissions::isDescendantOfCurrentUser($store->owner_id); if (!$isBig3 && !$isParentOfOwner && $store->owner_id !== $user->id) { return response()->json(['error' => 'Unauthorized to modify this store'], 403); } ``` ### StoreController.php — deleteStore_Admin() (lines 1403-1435) ```php public function deleteStore_Admin(Request $request) { ... $isUltimate = $user->acct_type === UserTypes::ULTIMATE; if (!$isUltimate) { $descendants = $user->getAllDescendants(); $descendantIds = $descendants->pluck('id')->toArray(); $allowedIds = array_merge([$user->id], $descendantIds); if (!in_array($store->owner_id, $allowedIds) && !in_array($store->manager_id, $allowedIds)) { return ResponseHelper::returnUnauthorized(); } } $store->delete(); ... } catch (Exception $e) { // BUG: missing backslash ``` ### StoreController.php — listStores_Admin() user_can_manage logic (lines 1330-1334) ```php ->each(function ($s) use ($allowedUserIds) { $s->user_can_manage = in_array($s->owner_id, $allowedUserIds) || in_array($s->manager_id, $allowedUserIds) || $s->managers->pluck('user_id')->intersect($allowedUserIds)->isNotEmpty(); unset($s->managers); }); ``` The `user_can_manage` flag includes managers-pivot check, but `update()` and `deleteStore_Admin()` do not — this is the root mismatch that causes store managers to see buttons but get 403. ### HomeStoreOwner.vue — services array (lines 34-60) Currently has: Create Store, Import Products, New Product, My Products, POS Keys. "My Stores" exists only in `balanceFooterItems`. Adding it to `services` makes it more discoverable. ### Store model relations `$store->managers()` returns `StoreManager` records (pivot model at `app/Models/Market/StoreManager.php`). `$store->managerUsers()` returns `User` models via the pivot. ## notes - dictionary: /home/josh/development/personal/BukidBountyApp/ai-docs/dictionary.md - linters: none detected - constraints: Hypervel (not Laravel) — use `Hypervel\Support\Facades\*` not `Illuminate\*`; table name for stores is `str` (abbreviated); `declare(strict_types=1)` is in effect