# Plan: Member Cooperative Registration From Cooperative Detail View

**Plan ID:** `11a8aa601d3b45c6aa16ff2b28eb1004`
**Created:** 2026-04-17
**Status:** Pending

---

## Overview

Implement a **Member Cooperative Registration** flow that allows users to register (join) as members of a cooperative **after viewing its details** on the `CooperativeDetail` page. Currently, the system has:

- An `EnrollFarmer` flow (admin-initiated, adds a farmer to a cooperative)
- A `joinCooperative` backend method on `CooperativeController` (self-join, but no frontend)
- A `useUserAdditionalDetails` composable with `joinCooperative` / `leaveCooperative` methods (settings-only sync, no full membership form)

**What's missing:** A dedicated, user-facing registration page where a member can view cooperative details and then register themselves with full membership information (membership type, position, etc.).

---

## Architecture Summary

```
CooperativeDetail.vue
    └── "Register as Member" button
        └── CooperativeMemberRegister.vue
            └── POST /Cooperatives/Member/Register
                └── CooperativeController@registerMember
                    ├── Creates cooperative_members record
                    └── Syncs users.settings.cooperatives
```

---

## Task 1: Backend — New `registerMember` Method in CooperativeController

### Description
Create a new public method `registerMember` on `CooperativeController` that allows an authenticated user to self-register as a member of a cooperative. This differs from the existing `joinCooperative` method (which is minimal) by accepting a full membership form with all `cooperative_members` fields.

### Target File
`app/Http/Controllers/Market/CooperativeController.php`

### Detailed Steps

1. **Add method signature:**
   ```php
   public function registerMember(Request $request)
   ```

2. **Permission check:** Use `UserActions::JoinCooperative` for the permission gate:
   ```php
   if (!UserPermissions::isActionPermitted(Auth::user()->acct_type, UserActions::JoinCooperative)) {
       return ResponseHelper::returnUnauthorized();
   }
   ```

3. **Validate required input:**
   - `cooperative_hash` (required) — hashkey of the target cooperative
   - All other membership fields are optional

4. **Lookup the cooperative:**
   ```php
   $cooperative = Organization::where('hashkey', $cooperativeHash)->first();
   if (!$cooperative) {
       return ResponseHelper::returnError('Cooperative not found', 404);
   }
   ```

5. **Check duplicate membership:**
   ```php
   $existing = CooperativeMember::where('organization_id', $cooperative->id)
       ->where('user_id', $user->id)
       ->first();
   if ($existing) {
       return ResponseHelper::returnError('You are already a member of this cooperative');
   }
   ```

6. **Accept full membership fields from request:**
   ```php
   $memberFields = $request->only([
       'role', 'membership_type', 'membership_level',
       'officer_position', 'officer_level',
       'concurrent_position', 'concurrent_level',
       'cooperative_name_alt', 'cooperative_position', 'year_beginning'
   ]);
   ```

7. **Create the `CooperativeMember` record:**
   ```php
   $member = new CooperativeMember(array_merge($memberFields, [
       'organization_id' => $cooperative->id,
       'user_id' => $user->id,
       'role' => $request->input('role', 'MEMBER'),
       'joined_at' => now(),
       'is_active' => true,
   ]));
   $member->save();
   ```

8. **Sync with `users.settings.cooperatives`:**
   ```php
   $settings = $user->settings ?? [];
   $cooperatives = $settings['cooperatives'] ?? [];
   if (!in_array($cooperativeHash, $cooperatives)) {
       $cooperatives[] = $cooperativeHash;
       $settings['cooperatives'] = $cooperatives;
       $user->settings = $settings;
       $user->save();
   }
   ```

9. **Return success:**
   ```php
   return ResponseHelper::returnSuccessResponse($member, $member->hashkey, 'Successfully registered as a cooperative member');
   ```

### Acceptance Criteria
- [ ] Authenticated users can self-register with full membership fields
- [ ] Duplicate membership is prevented
- [ ] User settings are synced with the cooperative hashkey
- [ ] Proper error handling for missing cooperative or unauthorized access

---

## Task 2: Backend — Register New Route

### Description
Add a new POST route for the member registration endpoint.

### Target File
`routes/web.php`

### Detailed Steps

1. **Add route under the Cooperative Module Routes section** (after line 543):
   ```php
   Route::post('/Cooperatives/Member/Register', [\App\Http\Controllers\Market\CooperativeController::class, 'registerMember'], ['middleware' => 'auth']);
   ```

### Acceptance Criteria
- [ ] Route is registered and protected by `auth` middleware
- [ ] Route follows existing naming convention (`/Cooperatives/Member/Register`)

---

## Task 3: Frontend — Create `CooperativeMemberRegister.vue` Page

### Description
Build a new Vue 3 Composition API page that presents a multi-step registration form for a user to join a cooperative. This page is navigated to from `CooperativeDetail.vue` and receives the cooperative `hashkey` as a `target` prop.

### Target File
`resources/js/Pages/CooperativeMemberRegister.vue`

### Detailed Steps

#### 3.1 — Script Setup
```javascript
import { ref, onMounted, computed } from 'vue';
import axios from 'axios';
import { usePageTitle } from '../composables/Core/usePageTitle';
import { useNavigate } from '../composables/Core/useNavigate';
import { useModal } from '../composables/Core/useModal';
import CardSimple from '../Components/Core/CardSimple.vue';

const props = defineProps({ target: String });
usePageTitle('Register as Cooperative Member');
const { navigate } = useNavigate();
const modal = useModal();
```

#### 3.2 — State Variables
```javascript
const cooperative = ref(null);
const loadingCoop = ref(true);
const isSaving = ref(false);
const alreadyMember = ref(false);

const form = ref({
    membership_type: '',
    membership_level: '',
    officer_position: '',
    officer_level: '',
    concurrent_position: '',
    concurrent_level: '',
    cooperative_name_alt: '',
    cooperative_position: '',
    year_beginning: '',
});
```

#### 3.3 — Fetch Cooperative Details on Mount
- Call `POST /Cooperatives/Get` with `{ hashkey: props.target }`
- Populate `cooperative.value`
- Check `response.data.is_member` to set `alreadyMember` (depends on Task 7)

#### 3.4 — Cooperative Info Header
Display a read-only header card showing:
- Cooperative name (bold, primary color)
- Address
- Registration number / CIN / Type
- Member count badge
- Use the same premium header style from `CooperativeDetail.vue` (bg-primary card with white text)

#### 3.5 — Registration Form (using `CardSimple` component)
Present the following fields organized in logical sections:

**Section: Membership Information**
| Field | Type | Placeholder | Notes |
|---|---|---|---|
| `membership_type` | `<select>` | Select Type | Options: REGULAR, ASSOCIATE, HONORARY, LABORATORY |
| `membership_level` | `<select>` | Select Level | Options: PRIMARY, SECONDARY, TERTIARY |
| `year_beginning` | `<input type="number">` | e.g. 2024 | Year membership begins |

**Section: Position Details (Optional)**
| Field | Type | Placeholder | Notes |
|---|---|---|---|
| `officer_position` | `<input type="text">` | e.g. Board Member | Optional officer role |
| `officer_level` | `<select>` | Select Level | Options: PRIMARY, SECONDARY, TERTIARY |
| `concurrent_position` | `<input type="text">` | e.g. Treasurer | Additional concurrent role |
| `concurrent_level` | `<select>` | Select Level | Same as officer_level |
| `cooperative_position` | `<input type="text">` | e.g. Chairperson | Position within the cooperative |

**Section: Alternative Cooperative Name (Optional)**
| Field | Type | Placeholder | Notes |
|---|---|---|---|
| `cooperative_name_alt` | `<input type="text">` | Alternative name | If the cooperative is known by another name |

#### 3.6 — Submit Button
- Full width, `btn-premium-launch` style (reuse from `CreateCooperative.vue`)
- Disabled when `isSaving` or `alreadyMember`
- If `alreadyMember`, show a badge/alert: "You are already a member of this cooperative"

#### 3.7 — Submit Handler
```javascript
const handleRegister = async () => {
    isSaving.value = true;
    try {
        const response = await axios.post('/Cooperatives/Member/Register', {
            cooperative_hash: props.target,
            ...form.value,
        });
        if (response.data.success) {
            modal.open({
                title: 'Registration Successful',
                body: 'You have been registered as a member of this cooperative!',
                onClose: () => navigate({ page: 'CooperativeDetail', props: { target: props.target } })
            });
        } else {
            modal.open({ title: 'Error', body: response.data.message || 'Registration failed.' });
        }
    } catch (error) {
        modal.open({
            title: 'Error',
            body: error.response?.data?.message || 'Failed to register. Please try again.'
        });
    } finally {
        isSaving.value = false;
    }
};
```

#### 3.8 — Navigation Back
- Include a "Back to Cooperative" link at the top using the same pattern from `EnrollFarmer.vue`:
  ```html
  <button @click="navigate({ page: 'CooperativeDetail', target: target })" class="btn btn-link ...">
      <i class="fas fa-arrow-left me-1"></i> Back to Cooperative
  </button>
  ```

#### 3.9 — Styles
- Reuse premium input styles from `CreateCooperative.vue` (`.premium-input`, `.premium-input-group`, etc.)
- Include dark mode scoped overrides using `:global(.dark-mode)` pattern
- Use `rounded-20` cards, smooth animations (`animate-fade-in`)

### Acceptance Criteria
- [ ] Cooperative header displays correctly with all key info
- [ ] All membership fields are editable
- [ ] Form submits to `/Cooperatives/Member/Register`
- [ ] Success modal with navigation back to detail page
- [ ] Already-member state is detected and prevents duplicate registration
- [ ] Dark mode compatible
- [ ] Mobile responsive

---

## Task 4: Frontend — Add "Register as Member" Button to `CooperativeDetail.vue`

### Description
Add a prominent call-to-action button on the `CooperativeDetail.vue` page that navigates to the new `CooperativeMemberRegister` page.

### Target File
`resources/js/Pages/CooperativeDetail.vue`

### Detailed Steps

1. **Add the button** in the existing action bar (near the "Enroll New Farmer" button, around line 85-89):
   ```html
   <div class="mb-4 d-flex justify-content-end gap-2 flex-wrap">
       <button @click="navigate({ page: 'CooperativeMemberRegister', props: { target: props.target } })" 
               class="btn btn-success rounded-pill px-4 py-2 shadow-sm">
           <i class="fas fa-id-card me-2"></i> Register as Member
       </button>
       <button @click="navigate({ page: 'EnrollFarmer', target: props.target })" 
               class="btn btn-primary rounded-pill px-4 py-2 shadow-sm">
           <i class="fas fa-user-plus me-2"></i> Enroll New Farmer
       </button>
   </div>
   ```

2. **Conditional visibility** (optional enhancement): Hide the "Register as Member" button if the current user is already a member of this cooperative. This requires checking `cooperative.members` for the current user's ID (available from the Pinia user store or from the page props).

### Acceptance Criteria
- [ ] Button is visible and styled consistently with existing buttons
- [ ] Button navigates to `CooperativeMemberRegister` with the cooperative hashkey
- [ ] Optional: Button is hidden/disabled if user is already a member

---

## Task 5: Register Route in VueRouteMap

### Description
Register the new `CooperativeMemberRegister` page in the VueRouteMap so it can be accessed via direct URL and through the SPA router.

### Target File
`app/Http/Controllers/Support/VueRouteMap.php`

### Detailed Steps

1. **Add entry** in the `$routes` array (after the `/cooperative-detail` entry, around line 229):
   ```php
   '/cooperative-member-register' => [
       'component'       => 'CooperativeMemberRegister',
       'loginRequired'   => true,
       'allowedUserTypes' => ['ult', 'super operator', 'operator', 'coordinator', 'store owner', 'store manager', 'supplier', 'user'],
   ],
   ```

2. **Note on access:** This page should be accessible to **most authenticated users** since any user should be able to register as a cooperative member. The `allowedUserTypes` list is kept broad. The actual permission check (`JoinCooperative`) happens on the backend.

### Acceptance Criteria
- [ ] Page is accessible via `/cooperative-member-register--h:HASHKEY` URL
- [ ] Login is required
- [ ] Route is accessible to all standard authenticated user types

---

## Task 6: RBAC — Verify JoinCooperative Permission Mapping

### Description
Ensure the `UserActions::JoinCooperative` permission is properly mapped in `UserPermissions::roles()` for all user types that should be allowed to self-register as cooperative members.

### Target File
`app/Http/Controllers/Helpers/Permissions/UserPermissions.php`

### Detailed Steps

1. **Check current mapping:** Search for `JoinCooperative` in the `roles()` method.
2. **Ensure it is included for these user types at minimum:**
   - `ULTIMATE` (automatic — gets all actions)
   - `SUPER_OPERATOR`
   - `OPERATOR`
   - `COORDINATOR`
   - `STORE_OWNER`
   - `STORE_MANAGER`
   - `SUPPLIER`
   - `USER` (standard user — should be able to join cooperatives)
3. **Add to `$RoleswithNoTargetUser`** if not already there, since `JoinCooperative` doesn't target another user:
   ```php
   UserActions::JoinCooperative,
   ```

### Acceptance Criteria
- [ ] `JoinCooperative` is permitted for all relevant user types
- [ ] `JoinCooperative` is listed in `$RoleswithNoTargetUser`

---

## Task 7: Backend — Enhance `getCooperative` Response

### Description
Enhance the `getCooperative` method to include additional context that the registration page needs — specifically whether the current user is already a member.

### Target File
`app/Http/Controllers/Market/CooperativeController.php`

### Detailed Steps

1. **Verify current response:** The existing `getCooperative` already loads `members.user.userInfo` — confirm it returns all organization fields (registration_number, cin, cooperative_type, etc.)

2. **Add current user membership check** to the response:
   ```php
   $currentUserMembership = null;
   $user = Auth::user();
   if ($user) {
       $currentUserMembership = CooperativeMember::where('organization_id', $cooperative->id)
           ->where('user_id', $user->id)
           ->first();
   }
   
   return response()->json([
       'success' => true,
       'data' => $cooperative,
       'is_member' => $currentUserMembership !== null,
       'membership' => $currentUserMembership,
   ]);
   ```

3. This allows the frontend to:
   - Show/hide the "Register as Member" button
   - Pre-populate the registration form if the user wants to update details
   - Display "Already a Member" badges

### Acceptance Criteria
- [ ] `getCooperative` returns all organization fields
- [ ] Response includes `is_member` boolean and `membership` data for current user
- [ ] No breaking changes to existing `CooperativeDetail.vue` usage

---

## Task 8: Update Dictionary

### Description
Update `ai-docs/dictionary.md` with the new module information.

### Target File
`ai-docs/dictionary.md`

### Detailed Steps

Add the following under the **Cooperative & User Profile** section:

```markdown
## Cooperative Member Registration
- **Page**: `resources/js/Pages/CooperativeMemberRegister.vue`
- **Flow**: User views `CooperativeDetail` → clicks "Register as Member" → fills membership form → POST to `/Cooperatives/Member/Register`
- **Backend**: `CooperativeController@registerMember`
- **Route**: `/cooperative-member-register--h:COOPERATIVE_HASHKEY`
- **Permission**: `UserActions::JoinCooperative`
- **Key Fields**: `membership_type`, `membership_level`, `officer_position`, `officer_level`, `concurrent_position`, `concurrent_level`, `cooperative_name_alt`, `cooperative_position`, `year_beginning`
- **Membership Types**: REGULAR, ASSOCIATE, HONORARY, LABORATORY
- **Membership Levels**: PRIMARY, SECONDARY, TERTIARY
```

### Acceptance Criteria
- [ ] Dictionary is updated with new module documentation
- [ ] Commit and push the dictionary update

---

## Implementation Order

| # | Task | Dependencies | Estimated Complexity |
|---|------|-------------|---------------------|
| 1 | Backend `registerMember` method | None | Medium |
| 2 | Register POST route | Task 1 | Low |
| 3 | Create `CooperativeMemberRegister.vue` | Task 1, 2 | High |
| 4 | Add button to `CooperativeDetail.vue` | Task 3, 5 | Low |
| 5 | Register in `VueRouteMap.php` | Task 3 | Low |
| 6 | Verify RBAC permissions | Task 1 | Low |
| 7 | Enhance `getCooperative` response | None | Low |
| 8 | Update dictionary | All tasks | Low |

**Recommended execution order:** 7 → 1 → 2 → 6 → 3 → 5 → 4 → 8

---

## Files Modified (Summary)

| File | Action | Description |
|------|--------|-------------|
| `app/Http/Controllers/Market/CooperativeController.php` | **Modify** | Add `registerMember()`, enhance `getCooperative()` |
| `routes/web.php` | **Modify** | Add POST route for `/Cooperatives/Member/Register` |
| `resources/js/Pages/CooperativeMemberRegister.vue` | **Create** | New registration page |
| `resources/js/Pages/CooperativeDetail.vue` | **Modify** | Add "Register as Member" button |
| `app/Http/Controllers/Support/VueRouteMap.php` | **Modify** | Register new page route |
| `app/Http/Controllers/Helpers/Permissions/UserPermissions.php` | **Modify** | Verify/add `JoinCooperative` permission mapping |
| `ai-docs/dictionary.md` | **Modify** | Document new module |

---

## Post-Implementation Checklist

- [ ] Run `npm run build` to compile frontend assets
- [ ] Run `docker restart bukidbountyapp` to apply backend changes
- [ ] Test registration flow end-to-end
- [ ] Verify dark mode compatibility
- [ ] Verify mobile responsiveness
- [ ] Verify duplicate membership prevention
- [ ] Commit and push all changes
