initial: bootstrap from BukidBountyApp base
This commit is contained in:
110
.claude/plans/1321cb985147e2ce3a341d045288e5f5-complete.md
Normal file
110
.claude/plans/1321cb985147e2ce3a341d045288e5f5-complete.md
Normal file
@@ -0,0 +1,110 @@
|
||||
---
|
||||
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
|
||||
Reference in New Issue
Block a user