Files
BarangaySystem/plans/11a8aa601d3b45c6aa16ff2b28eb1004.md
2026-06-06 18:43:00 +08:00

18 KiB

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:

    public function registerMember(Request $request)
    
  2. Permission check: Use UserActions::JoinCooperative for the permission gate:

    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:

    $cooperative = Organization::where('hashkey', $cooperativeHash)->first();
    if (!$cooperative) {
        return ResponseHelper::returnError('Cooperative not found', 404);
    }
    
  5. Check duplicate membership:

    $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:

    $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:

    $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:

    $settings = $user->settings ?? [];
    $cooperatives = $settings['cooperatives'] ?? [];
    if (!in_array($cooperativeHash, $cooperatives)) {
        $cooperatives[] = $cooperativeHash;
        $settings['cooperatives'] = $cooperatives;
        $user->settings = $settings;
        $user->save();
    }
    
  9. Return success:

    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):
    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

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

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

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:
    <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):

    <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):

    '/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:
    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:

    $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:

## 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