docs: add comprehensive testing and implementation guide for membership linking feature
This commit is contained in:
249
TEST_MEMBERSHIP_LINKING.md
Normal file
249
TEST_MEMBERSHIP_LINKING.md
Normal file
@@ -0,0 +1,249 @@
|
|||||||
|
# Membership Linking Feature - Test & Verification Checklist
|
||||||
|
|
||||||
|
## Feature Overview
|
||||||
|
This document outlines the membership linking feature that allows multiple users (e.g., married couples, family members) to share a single membership account.
|
||||||
|
|
||||||
|
## Database Schema
|
||||||
|
|
||||||
|
### Tables Created
|
||||||
|
1. **membership_links** - Tracks relationships between primary and secondary users
|
||||||
|
- link_id (auto-increment)
|
||||||
|
- primary_user_id - User who owns/manages the membership
|
||||||
|
- secondary_user_id - User gaining access to membership
|
||||||
|
- status (ACTIVE/INACTIVE)
|
||||||
|
- created_date
|
||||||
|
- expires_date (optional)
|
||||||
|
|
||||||
|
2. **membership_permissions** - Granular permission control
|
||||||
|
- permission_id (auto-increment)
|
||||||
|
- link_id - Foreign key to membership_links
|
||||||
|
- permission_name (e.g., access_member_areas, member_pricing, etc.)
|
||||||
|
- granted_date
|
||||||
|
|
||||||
|
## Core Functions (in src/config/functions.php)
|
||||||
|
|
||||||
|
### New Functions Added
|
||||||
|
1. **linkSecondaryUserToMembership($primary_user_id, $secondary_user_id, $permissions = [])**
|
||||||
|
- Creates link and assigns default permissions
|
||||||
|
- Validates primary user has active membership
|
||||||
|
- Validates secondary user exists and doesn't already link
|
||||||
|
- Returns success/error response
|
||||||
|
|
||||||
|
2. **getUserMembershipLink($user_id)**
|
||||||
|
- Checks if user is linked as secondary to another membership
|
||||||
|
- Returns link details if active
|
||||||
|
- Returns false if no active link
|
||||||
|
|
||||||
|
3. **getLinkedSecondaryUsers($primary_user_id)**
|
||||||
|
- Returns array of all secondary users linked to primary
|
||||||
|
- Includes link creation date and status
|
||||||
|
- Used for UI display on membership_details page
|
||||||
|
|
||||||
|
4. **unlinkSecondaryUser($primary_user_id, $secondary_user_id)**
|
||||||
|
- Removes link and associated permissions
|
||||||
|
- Returns success/error response
|
||||||
|
|
||||||
|
### Modified Functions
|
||||||
|
1. **getUserMemberStatus($user_id)**
|
||||||
|
- NOW checks linked memberships at ALL failure points:
|
||||||
|
* If user has no application → check if linked to active membership
|
||||||
|
* If user hasn't accepted indemnity → check if linked
|
||||||
|
* If user has no payment record → check if linked
|
||||||
|
* If user's direct membership expired → check if linked
|
||||||
|
- Returns true for linked members even if direct membership check fails
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
### POST /src/processors/link_membership_user.php
|
||||||
|
- **Purpose**: AJAX endpoint for creating membership links
|
||||||
|
- **Parameters**:
|
||||||
|
- csrf_token (validated)
|
||||||
|
- secondary_user_email (validated)
|
||||||
|
- **Returns**: JSON response with success/error
|
||||||
|
- **Security**: CSRF token validation, database injection prevention
|
||||||
|
|
||||||
|
### POST /src/processors/unlink_membership_user.php
|
||||||
|
- **Purpose**: AJAX endpoint for removing membership links
|
||||||
|
- **Parameters**:
|
||||||
|
- csrf_token (validated)
|
||||||
|
- secondary_user_id (validated)
|
||||||
|
- **Returns**: JSON response with success/error
|
||||||
|
- **Security**: CSRF token validation, only primary user can unlink
|
||||||
|
|
||||||
|
## UI Implementation
|
||||||
|
|
||||||
|
### Membership Details Page (src/pages/membership_details.php)
|
||||||
|
- Added "Linked Accounts" section OUTSIDE main info form
|
||||||
|
- Displays list of currently linked secondary users
|
||||||
|
- Form to add new linked user by email
|
||||||
|
- Unlink buttons for each linked user
|
||||||
|
- IMPORTANT FIX: Form moved outside infoForm to prevent form submission conflicts
|
||||||
|
|
||||||
|
### Header Navigation (src/pages/header.php)
|
||||||
|
- "Members Area" dropdown shown for users with direct OR linked membership
|
||||||
|
- Uses getUserMemberStatus() to determine visibility
|
||||||
|
- Shows: Campsites & Gallery links
|
||||||
|
|
||||||
|
## Booking Pages & Pricing
|
||||||
|
|
||||||
|
### Protected Member Pages
|
||||||
|
- `src/pages/bookings/campsites.php` - Redirects non-members
|
||||||
|
- `src/pages/gallery/gallery.php` - Redirects non-members
|
||||||
|
- `src/pages/gallery/view_album.php` - Redirects non-members
|
||||||
|
- `src/pages/gallery/create_album.php` - Redirects non-members
|
||||||
|
|
||||||
|
### Open Booking Pages (All Users Welcome)
|
||||||
|
1. **Trip Details** (`src/pages/bookings/trip-details.php`)
|
||||||
|
- Shows member & non-member rates
|
||||||
|
- Linked members get member pricing
|
||||||
|
- Correct calculateTotal() logic with adults/children/pensioners
|
||||||
|
|
||||||
|
2. **Driver Training** (`src/pages/bookings/driver_training.php`)
|
||||||
|
- Pricing: Members vs Non-members
|
||||||
|
- Form fields adjusted for non-members
|
||||||
|
- FIXED: calculateTotal() now correctly:
|
||||||
|
* Members: (self + additional_members at member rate) + additional_nonmembers
|
||||||
|
* Non-members: (self + additional participants at non-member rate)
|
||||||
|
|
||||||
|
3. **Bush Mechanics** (`src/pages/other/bush_mechanics.php`)
|
||||||
|
- FIXED: calculateTotal() pricing logic corrected
|
||||||
|
- Members: (self at member rate) + additional members + additional non-members
|
||||||
|
- Non-members: (self + additional participants at non-member rate)
|
||||||
|
|
||||||
|
4. **Rescue & Recovery** (`src/pages/other/rescue_recovery.php`)
|
||||||
|
- FIXED: calculateTotal() pricing logic corrected
|
||||||
|
- Members: (self at member rate) + additional members + additional non-members
|
||||||
|
- Non-members: (self + additional participants at non-member rate)
|
||||||
|
|
||||||
|
5. **Course Details** (`src/pages/bookings/course_details.php`)
|
||||||
|
- Shows member & non-member rates
|
||||||
|
- Open to all users (members and non-members)
|
||||||
|
|
||||||
|
6. **Campsite Booking** (`src/pages/bookings/campsite_booking.php`)
|
||||||
|
- Pricing: Members stay FREE, Non-members R200/night
|
||||||
|
- Calculates based on getUserMemberStatus()
|
||||||
|
|
||||||
|
### Booking Processors
|
||||||
|
1. **process_trip_booking.php** - ✅ Allows non-members, applies pricing correctly
|
||||||
|
2. **process_course_booking.php** - ✅ Allows non-members, applies pricing correctly
|
||||||
|
3. **process_camp_booking.php** - ✅ Allows non-members, applies pricing correctly
|
||||||
|
|
||||||
|
## Testing Checklist
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
- [ ] Link secondary user to primary user membership
|
||||||
|
- [ ] Verify linked user appears in getLinkedSecondaryUsers()
|
||||||
|
- [ ] Verify linked user gets member pricing on bookings
|
||||||
|
- [ ] Verify linked user can access member-only areas
|
||||||
|
- [ ] Unlink secondary user from primary membership
|
||||||
|
- [ ] Verify unlinked user loses member benefits
|
||||||
|
- [ ] Test with invalid secondary user email
|
||||||
|
- [ ] Test with secondary user who already has direct membership
|
||||||
|
|
||||||
|
### Integration Tests
|
||||||
|
- [ ] Member books trip - should use member pricing
|
||||||
|
- [ ] Member books course - should use member pricing
|
||||||
|
- [ ] Member books campsite - should stay FREE
|
||||||
|
- [ ] Linked member books trip - should use member pricing
|
||||||
|
- [ ] Linked member books course - should use member pricing
|
||||||
|
- [ ] Linked member books campsite - should stay FREE
|
||||||
|
- [ ] Non-member books trip - should use non-member pricing
|
||||||
|
- [ ] Non-member books course - should use non-member pricing
|
||||||
|
- [ ] Non-member books campsite - should pay R200/night
|
||||||
|
- [ ] Linked member can view members gallery
|
||||||
|
- [ ] Non-member cannot access members gallery
|
||||||
|
- [ ] Linked member dropdown link shows in header
|
||||||
|
- [ ] Payment processing for non-member bookings
|
||||||
|
|
||||||
|
### UI/UX Tests
|
||||||
|
- [ ] Linking form displays properly on membership details
|
||||||
|
- [ ] Unlink buttons work correctly
|
||||||
|
- [ ] "You will be added at non-member rate" message shows for non-members
|
||||||
|
- [ ] Pricing calculations update correctly as form fields change
|
||||||
|
- [ ] Member/Non-member rate display is clear
|
||||||
|
|
||||||
|
## Known Issues & Fixes Applied
|
||||||
|
|
||||||
|
### Issue 1: Form Submission Conflicts
|
||||||
|
- **Problem**: linkUserForm nested inside infoForm - submit triggered parent
|
||||||
|
- **Fix**: Moved linkUserForm outside infoForm closes
|
||||||
|
- **Commit**: c5112e1c
|
||||||
|
|
||||||
|
### Issue 2: Linked Members Not Recognized
|
||||||
|
- **Problem**: getUserMemberStatus() only checked linked if no application existed
|
||||||
|
- **Fix**: Added linked checks at all failure points in function
|
||||||
|
- **Commit**: e63bd806
|
||||||
|
|
||||||
|
### Issue 3: JavaScript Pricing Calculations Wrong
|
||||||
|
- **Problem**: calculateTotal() in driver_training, bush_mechanics, rescue_recovery incorrectly calculated non-member totals
|
||||||
|
- **Fix**: Corrected variable names and logic to properly handle:
|
||||||
|
- Members: count themselves + additional members/non-members
|
||||||
|
- Non-members: count themselves only + additional participants
|
||||||
|
- **Commits**:
|
||||||
|
- driver_training: inline with member label UI improvement
|
||||||
|
- bush_mechanics & rescue_recovery: 646a3ecb
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
### Database Queries
|
||||||
|
- getUserMembershipLink() - Single query with index on secondary_user_id
|
||||||
|
- getLinkedSecondaryUsers() - Single join query with index on primary_user_id
|
||||||
|
- getUserMemberStatus() - Multiple queries but cached in session after first call
|
||||||
|
|
||||||
|
### Recommended Indexes
|
||||||
|
- membership_links(secondary_user_id)
|
||||||
|
- membership_links(primary_user_id)
|
||||||
|
- membership_links(status)
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
### Access Control
|
||||||
|
- Only primary user can link/unlink accounts
|
||||||
|
- Secondary user cannot manage their own link (primary must unlink)
|
||||||
|
- CSRF tokens validated on all membership operations
|
||||||
|
|
||||||
|
### Input Validation
|
||||||
|
- User emails validated before linking
|
||||||
|
- User IDs validated as integers
|
||||||
|
- Links can only be created between valid users
|
||||||
|
|
||||||
|
### Audit Trail
|
||||||
|
- All linking operations logged via auditLog()
|
||||||
|
- Timestamps recorded for all changes
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
1. **Secondary user control**
|
||||||
|
- Allow secondary users to decline/accept links
|
||||||
|
- Option for secondary user to self-unlink
|
||||||
|
|
||||||
|
2. **Permissions system**
|
||||||
|
- Granular control over which permissions secondary users receive
|
||||||
|
- Ability to revoke specific permissions without unlinking
|
||||||
|
|
||||||
|
3. **Multiple primary accounts**
|
||||||
|
- Allow one user to be secondary to multiple primaries
|
||||||
|
- Flexible family/group structure support
|
||||||
|
|
||||||
|
4. **Member linking UI**
|
||||||
|
- Search for existing members to link
|
||||||
|
- Batch link multiple users
|
||||||
|
- Link management dashboard
|
||||||
|
|
||||||
|
5. **Expiration dates**
|
||||||
|
- Time-limited links (e.g., seasonal guests)
|
||||||
|
- Auto-renewal options
|
||||||
|
- Expiration notifications
|
||||||
|
|
||||||
|
## Rollback Plan
|
||||||
|
|
||||||
|
If issues arise, revert to previous commit:
|
||||||
|
```bash
|
||||||
|
git revert <commit-hash>
|
||||||
|
```
|
||||||
|
|
||||||
|
Key commits to know:
|
||||||
|
- 646a3ecb - Latest fixes (pricing calculations)
|
||||||
|
- e63bd806 - Improved getUserMemberStatus
|
||||||
|
- c5112e1c - Fixed form nesting issue
|
||||||
|
- bd20fc0f - Initial feature implementation
|
||||||
Reference in New Issue
Block a user