Files
BarangaySystem/.claude/plans/e8e0be1ce348a4e47641f59cdf136890-complete.md
2026-06-06 18:43:00 +08:00

470 lines
27 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
task: Cooperative officer/member homepages, chapter org chart, member search, create user, assign officer to child chapter, create chapter, share registration link
cycles: 5
context: true
private: false
started: 2026-05-29T00:00:00Z
finished: 2026-05-29T00:01:00Z
---
## prerequisite
This plan DEPENDS on plan `4fc3b455fb62b15dfb06790a1f421c96` (COOP_MEMBER/COOP_OFFICER types + migrations). Execute that plan first. Assumes the following already exist after that plan runs:
- `users.acct_type` values `'coop officer'` and `'coop member'` (UserTypes enum)
- `chapters.cooperative_id` column + `chapters.level` enum includes `'municipal'`
- `chapter_members.role` column
- `UserActions::ViewChapterOrgChart`, `ManageChapterMembers`, `ViewScopedMemberReports`, `AssignChapterOfficer`
- `UserTypes::COOP_OFFICER` and `UserTypes::COOP_MEMBER` in UserPermissions::roles()
- `isCoopOfficer` and `isCoopMember` in `useAuth.js`
- `Home.vue` fragments wired for COOP_OFFICER and COOP_MEMBER
## files
- `app/Http/Controllers/Support/ChapterController.php` — add orgChart(), officerAssign(), createChapter(), memberSearch() methods
- `app/Http/Controllers/Market/CooperativeController.php` [line 410] — extend publicRegisterMember to accept chapter_hash; auto-assign to chapter
- `routes/web.php` [lines 761-770] — register new chapter endpoints + public chapter route
- `app/Http/Controllers/Support/VueRouteMap.php` [lines 278-311] — register new SPA pages; add coop officer/member to existing coop allowedUserTypes
- `resources/js/Pages/Fragments/Home/HomeCoopOfficer.vue` — NEW: level-aware officer dashboard
- `resources/js/Pages/Fragments/Home/HomeCoopMember.vue` — NEW: read-only member card
- `resources/js/Pages/ChapterOrgChart.vue` — NEW: org chart with child chapter toggle
- `resources/js/Pages/CoopMemberSearch.vue` — NEW: name-only member search for officers
- `resources/js/Pages/CreateCoopUser.vue` — NEW: full user creation with coop+chapter auto-assign
- `resources/js/Pages/AssignChapterOfficer.vue` — NEW: move member to child chapter as officer
- `resources/js/Pages/CreateChapter.vue` — NEW: create chapter one level below current officer's chapter
- `resources/js/composables/useChapters.js` — add fetchOfficerScope(), fetchOrgChart(), searchMembers(), assignOfficer(), createChapter()
- `routes/web.php` [line ~754] — add public chapter info + registration endpoints
## level hierarchy constants
```
LEVEL_ORDER = ['national','region','province','city','municipal','barangay']
CHILD_LEVELS = {
'national' => ['region'],
'region' => ['province'],
'province' => ['city','municipal'],
'city' => ['barangay'],
'municipal' => ['barangay'],
'barangay' => [],
}
```
All PHP code using level must use `in_array($level, ['city','municipal'])` never equality check.
## steps
### 1. ChapterController — getOrgChart()
Add `public function getOrgChart(Request $request)` to `ChapterController.php`:
- Auth: caller must be logged in
- Determine caller's chapter: `ChapterMember::with('chapter')->where('user_id', Auth::id())->where('is_active', true)->orderByRaw("FIELD(chapters.level, 'region','province','city','municipal','barangay')")->join('chapters','chapters.id','chapter_members.chapter_id')->first()`
- For COOP_OFFICER: load own chapter + its officers (chapter_members WHERE role IS NOT NULL AND role != 'MEMBER') + direct child chapters (WHERE parent_id = own chapter id AND cooperative_id = own cooperative_id) each with `active_members_count` and their own officers (collapsed by default)
- For COOP_MEMBER: load own chapter name + officers only (names + roles) — do NOT return member list
- For Big3/COORDINATOR: accept optional `cooperative_id` param, return full coop chapter tree
- Response shape:
```json
{
"own_chapter": { "id", "hashkey", "name", "level", "location_key", "officers": [{"name","role","position"}] },
"children": [{ "id", "hashkey", "name", "level", "member_count": 42, "officers": [] }]
}
```
Children `officers` array starts empty (populated by frontend on toggle via second call to same endpoint with `chapter_id` param pointing to that child).
### 2. ChapterController — assignOfficer()
Add `public function assignOfficer(Request $request)`:
- Validate: `member_user_hashkey` (string, required), `child_chapter_id` (integer, required), `role` (string, required: PRESIDENT/VICE_PRESIDENT/SECRETARY/TREASURER/AUDITOR/BOARD_MEMBER)
- Permission check: caller must be COOP_OFFICER (or Big3/COORDINATOR)
- Eligibility check A — the selected user: must have a chapter_members row in caller's OWN chapter (`is_active = true`), AND role IS NULL OR role = 'MEMBER' (not already an officer there)
- Eligibility check B — child_chapter_id: must have `parent_id = caller's own chapter id` AND `cooperative_id = caller's cooperative_id`
- Effect:
1. Delete the member's existing chapter_members row in the parent chapter
2. Insert/update chapter_members row: `{user_id, chapter_id: child_chapter_id, role, position: role label, is_manual_override: true, assigned_by: Auth::id(), is_active: true, created_by, updated_by, hashkey}`
3. Upgrade `users.acct_type = 'coop officer'` if currently `'coop member'`
- Return `{ success: true, message: '...' }`
### 3. ChapterController — createChapter()
Add `public function createChapter(Request $request)`:
- Validate: `name` (string, required), `location_key` (string, required), `lat` (nullable decimal), `lng` (nullable decimal)
- Permission check: caller must be COOP_OFFICER (or Big3/COORDINATOR)
- Determine caller's chapter level → compute allowed child level using CHILD_LEVELS map
- If caller is at BARANGAY level, return 422 "Cannot create sub-chapters at barangay level"
- Determine caller's cooperative_id from their chapter's cooperative_id
- Create Chapter: `{ name, level: childLevel, parent_id: callerChapterId, cooperative_id: callerCoopId, location_key: strtolower(trim()), lat, lng, is_active: true, created_by, updated_by, hashkey }`
- Return `{ success: true, chapter: { hashkey, name, level } }`
### 4. ChapterController — memberSearch()
Add `public function memberSearch(Request $request)`:
- Validate: `query` (string, required, min 2)
- Permission check: caller must have `ManageChapterMembers` action (COOP_OFFICER, COORDINATOR, Big3)
- Scope: collect all chapter IDs in caller's subtree (own chapter + all descendants recursively via `parent_id` chain, scoped to same `cooperative_id`)
- Query: `User::join('chapter_members','users.id','chapter_members.user_id')->whereIn('chapter_members.chapter_id', $scopeIds)->where('chapter_members.is_active', true)->where('users.name', 'LIKE', "%{$query}%")->select('users.name', 'chapter_members.role', 'chapters.name as chapter_name')->join('chapters','chapters.id','chapter_members.chapter_id')->distinct()->limit(30)->get()`
- Return ONLY: `[{ name, role, chapter_name }]` — NO hashkey, NO mobile_number, NO username, NO other fields
### 5. ChapterController — getOfficerScope()
Add `public function getOfficerScope(Request $request)`:
- Returns caller's own chapter info + list of direct child chapters (for dropdowns in AssignChapterOfficer and CreateChapter pages)
- Response: `{ own_chapter: {id, hashkey, name, level}, child_chapters: [{id, hashkey, name, level, active_members_count}], cooperative: {hashkey, name}, eligible_members: [{user_hashkey, name, role}] }`
- `eligible_members`: chapter_members WHERE chapter_id = own chapter AND is_active = true AND (role IS NULL OR role = 'MEMBER') → join users → return {user_hashkey: users.hashkey, name: users.name OR users.fullname, role}
### 6. Public chapter registration endpoint
In `routes/web.php` add after existing public coop routes (~line 755):
```php
Route::get('/api/public/chapter/{hkey}', [\App\Http\Controllers\Support\ChapterController::class, 'publicGetChapter']);
Route::post('/api/public/chapter/register', [\App\Http\Controllers\Support\ChapterController::class, 'publicRegisterToChapter']);
```
Add `publicGetChapter(Request $request, string $hkey)` to ChapterController:
- No auth required
- Return chapter name, level, cooperative name — nothing else
- Response: `{ success: true, chapter: { name, level }, cooperative: { name } }`
Add `publicRegisterToChapter(Request $request)` to ChapterController:
- No auth required
- Params: `chapter_hash` (string), `name`, `username`, `mobile_number`, `password`
- Validate same as publicRegisterMember
- Load chapter by hashkey; load cooperative from chapter.cooperative_id
- Create user: acct_type = 'coop member', parentuid = chapter.created_by (fallback: first COORDINATOR)
- Insert cooperative_members row linking user to cooperative
- Insert chapter_members row: `{user_id, chapter_id: chapter.id, is_manual_override: false, is_active: true, created_by: user.id, updated_by: user.id, hashkey}`
- Update user.settings.cooperatives = [cooperative.hashkey]
- Return `{ success: true, user_hashkey, message }`
### 7. routes/web.php — register new chapter endpoints
In the Chapter routes block (lines 761770), add:
```php
Route::post('/Chapters/OrgChart', [\App\Http\Controllers\Support\ChapterController::class, 'getOrgChart'], ['middleware' => 'auth']);
Route::post('/Chapters/Officer/Assign', [\App\Http\Controllers\Support\ChapterController::class, 'assignOfficer'], ['middleware' => 'auth']);
Route::post('/Chapters/Create', [\App\Http\Controllers\Support\ChapterController::class, 'createChapter'], ['middleware' => 'auth']);
Route::post('/Chapters/Members/Search', [\App\Http\Controllers\Support\ChapterController::class, 'memberSearch'], ['middleware' => 'auth']);
Route::post('/Chapters/Officer/Scope', [\App\Http\Controllers\Support\ChapterController::class, 'getOfficerScope'], ['middleware' => 'auth']);
```
### 8. home-data — COOP_OFFICER and COOP_MEMBER branches
In the `/home-data` closure in `routes/web.php` (after the COORDINATOR block, ~line 153):
```php
if ($acctType === \App\Enums\UserTypes::COOP_OFFICER && $user) {
$myChapterMember = \App\Models\ChapterMember::join('chapters','chapters.id','chapter_members.chapter_id')
->where('chapter_members.user_id', $user->id)->where('chapter_members.is_active', true)
->orderByRaw("FIELD(chapters.level,'region','province','city','municipal','barangay')")
->select('chapter_members.*','chapters.name as chapter_name','chapters.level as chapter_level','chapters.hashkey as chapter_hashkey','chapters.cooperative_id')
->first();
if ($myChapterMember) {
$myChapterId = $myChapterMember->chapter_id;
$chapterMemberCount = \App\Models\ChapterMember::where('chapter_id', $myChapterId)->where('is_active', true)->count();
$childChapterCount = \App\Models\Chapter::where('parent_id', $myChapterId)->where('is_active', true)->count();
$newMembersCount = \App\Models\ChapterMember::where('chapter_id', $myChapterId)->where('is_active', true)->where('created_at', '>=', now()->subDays(7))->count();
$cooperativeHash = \App\Models\Market\Organization::where('id', $myChapterMember->cooperative_id)->value('hashkey');
$props['props']['chapter_info'] = [
'chapter_name' => $myChapterMember->chapter_name,
'chapter_level' => $myChapterMember->chapter_level,
'chapter_hashkey' => $myChapterMember->chapter_hashkey,
'cooperative_hash' => $cooperativeHash,
];
$props['props']['stats']['chapter_member_count'] = $chapterMemberCount;
$props['props']['stats']['child_chapter_count'] = $childChapterCount;
$props['props']['stats']['new_members_7d'] = $newMembersCount;
}
}
if ($acctType === \App\Enums\UserTypes::COOP_MEMBER && $user) {
$myChapterMember = \App\Models\ChapterMember::join('chapters','chapters.id','chapter_members.chapter_id')
->where('chapter_members.user_id', $user->id)->where('chapter_members.is_active', true)
->orderByRaw("FIELD(chapters.level,'barangay','city','municipal','province','region','national')")
->select('chapter_members.*','chapters.name as chapter_name','chapters.level as chapter_level','chapters.id as chapter_id','chapters.cooperative_id')
->first();
if ($myChapterMember) {
$chapterMemberCount = \App\Models\ChapterMember::where('chapter_id', $myChapterMember->chapter_id)->where('is_active', true)->count();
$cooperativeName = \App\Models\Market\Organization::where('id', $myChapterMember->cooperative_id)->value('name');
$props['props']['chapter_info'] = [
'chapter_name' => $myChapterMember->chapter_name,
'chapter_level' => $myChapterMember->chapter_level,
'cooperative_name' => $cooperativeName,
'member_count' => $chapterMemberCount,
];
}
}
```
### 9. VueRouteMap.php — register new pages + update existing allowedUserTypes
Add to the routes array (near the cooperatives block, ~line 308):
```php
'/chapter-org-chart' => [
'component' => 'ChapterOrgChart',
'loginRequired' => true,
'allowedUserTypes'=> ['ult','super operator','operator','coordinator','coop officer','coop member'],
'module' => 'cooperatives',
],
'/coop-member-search' => [
'component' => 'CoopMemberSearch',
'loginRequired' => true,
'allowedUserTypes'=> ['ult','super operator','operator','coordinator','coop officer'],
'module' => 'cooperatives',
],
'/create-coop-user' => [
'component' => 'CreateCoopUser',
'loginRequired' => true,
'allowedUserTypes'=> ['ult','super operator','operator','coordinator','coop officer'],
'module' => 'cooperatives',
],
'/assign-chapter-officer' => [
'component' => 'AssignChapterOfficer',
'loginRequired' => true,
'allowedUserTypes'=> ['ult','super operator','operator','coordinator','coop officer'],
'module' => 'cooperatives',
],
'/create-chapter' => [
'component' => 'CreateChapter',
'loginRequired' => true,
'allowedUserTypes'=> ['ult','super operator','operator','coordinator','coop officer'],
'module' => 'cooperatives',
],
'/register-chapter' => [
'component' => 'RegisterChapter',
'loginRequired' => false,
'allowedUserTypes'=> ['*'],
'module' => 'cooperatives',
],
```
Also update existing `/cooperative-list`, `/cooperative-detail`, `/cooperative-member-register` entries to include `'coop officer'` and `'coop member'` in their `allowedUserTypes`.
### 10. useChapters.js — add new methods
Append to the `useChapters()` composable return object:
```js
const fetchOrgChart = async ({ chapterId = null } = {}) => { /* POST /Chapters/OrgChart */ }
const fetchOfficerScope = async () => { /* POST /Chapters/Officer/Scope */ }
const searchMembers = async (query) => { /* POST /Chapters/Members/Search */ }
const assignOfficer = async ({ memberUserHashkey, childChapterId, role }) => { /* POST /Chapters/Officer/Assign */ }
const createChapter = async ({ name, locationKey, lat = null, lng = null }) => { /* POST /Chapters/Create */ }
```
Follow same loading/error pattern as existing methods.
### 11. HomeCoopOfficer.vue — NEW
`resources/js/Pages/Fragments/Home/HomeCoopOfficer.vue`
Pattern: mirror `HomeCooperative.vue` structure (imports: usePageData, useNavigate, useAuth, useModal, BalanceBox, ServiceButtonGrid, SideTextButtonList, HomeSkeleton).
`onMounted`: call `fetchPageData('/home-data')`. Extract `data.value?.chapter_info` for chapter name/level/hashkey/cooperative_hash. Extract `data.value?.stats` for member count, child chapters, new members 7d.
Stats cards:
```js
[
{ title: 'Members', number: chapter_member_count, unit: 'In Chapter' },
{ title: 'Sub-Chapters', number: child_chapter_count, unit: 'Direct' },
{ title: 'New (7d)', number: new_members_7d, unit: 'Members' },
]
```
Chapter badge: show chapter level (capitalize) + chapter name. e.g. "REGIONAL — Bukidnon Chapter".
Services grid (icon: use FontAwesome `fas fa-*`, NOT micons for new components):
```js
[
{ icon: 'fas fa-sitemap', title: 'Org Chart', pagename: 'ChapterOrgChart' },
{ icon: 'fas fa-search', title: 'Search Members', pagename: 'CoopMemberSearch' },
{ icon: 'fas fa-user-plus', title: 'Create User', pagename: 'CreateCoopUser' },
{ icon: 'fas fa-user-tie', title: 'Assign Officer', pagename: 'AssignChapterOfficer' },
{ icon: 'fas fa-map-marker-alt', title: 'Create Chapter', pagename: 'CreateChapter' },
{ icon: 'fas fa-share-alt', title: 'Share Invite', action: 'shareChapterLink' },
]
```
SideTextButtonList:
```js
[
{ text: 'My Cooperative', action: 'viewMyCoop' },
{ text: 'My Profile', pagename: 'UserInfoEdit' },
{ text: 'Member Ledger', pagename: 'AccountingDashboard' },
]
```
`shareChapterLink()` handler:
```js
const shareChapterLink = () => {
const coopHash = chapterInfo.value?.cooperative_hash;
const chapterHash = chapterInfo.value?.chapter_hashkey;
if (!coopHash || !chapterHash) return;
const encoded = btoa(JSON.stringify({ coop_hash: coopHash, chapter_hash: chapterHash }));
const url = `${window.location.origin}/register-chapter--e:${encoded}`;
if (navigator.share) {
navigator.share({ title: 'Join our cooperative chapter', url }).catch(() => {});
} else {
navigator.clipboard?.writeText(url);
modal.quickDismiss({ title: 'Link Copied', body: 'Registration link copied to clipboard.' });
}
};
```
No `bg-white`, `bg-light`, or `text-dark` hardcoded. Use `var(--bg-card)`, `var(--text-primary)`. Sticky header: `top: 66px; z-index: 1020;` if used.
### 12. HomeCoopMember.vue — NEW
`resources/js/Pages/Fragments/Home/HomeCoopMember.vue`
Fetch `/home-data`. Extract `chapter_info` (chapter_name, chapter_level, cooperative_name, member_count).
Display:
- Chapter name card: chapter_level (capitalized) + chapter_name + cooperative_name
- Member count badge: "X members in this chapter" — count only, NO names list
- Org chart strip: fetch `/Chapters/OrgChart` (own chapter's officers) → display name + role, e.g. "Pres. Juan dela Cruz". Do NOT display member names.
Services grid:
```js
[
{ icon: 'fas fa-sitemap', title: 'Org Chart', pagename: 'ChapterOrgChart' },
{ icon: 'fas fa-handshake', title: 'My Cooperative', action: 'viewMyCoop' },
{ icon: 'fas fa-user-circle', title: 'My Profile', pagename: 'UserInfoEdit' },
{ icon: 'fas fa-wallet', title: 'My Wallet', pagename: 'MyWallet' },
]
```
No search button, no create user, no assign officer.
### 13. ChapterOrgChart.vue — NEW
Route: `/chapter-org-chart` (no hash needed — scoped server-side to caller).
`onMounted`: call `POST /Chapters/OrgChart` → get `{ own_chapter, children }`.
Render:
1. **Header card**: own_chapter.name + level badge
2. **Officers section** (own chapter): flat list of `officer.name + officer.role` badges. If no officers: "No officers assigned yet."
3. **Child chapters list**: for each child in `children`:
- Row: `[level badge] Chapter Name ——— X members [▶ toggle icon]`
- Toggle (`showOfficers[child.id]` ref): on click, if `child.officers` is empty, call `POST /Chapters/OrgChart` with `{ chapter_id: child.id }` to fetch and populate `child.officers`. Show officer names + roles below the row.
4. COOP_MEMBER: show only own_chapter.name + officer list. No children drill-down. No member names anywhere.
No `bg-white`/`bg-light`. Use theme variables.
### 14. CoopMemberSearch.vue — NEW
Route: `/coop-member-search`
Simple search page:
- Input: text field with `v-model="query"`, debounced 400ms, min 2 chars
- On each debounced change: call `POST /Chapters/Members/Search { query }``[{ name, role, chapter_name }]`
- Results: list of cards showing `name`, `chapter_name`, role badge (if officer). NO mobile, NO hashkey link, NO other fields.
- Empty state if < 2 chars: "Type at least 2 characters to search."
- No results: "No members found matching '{{query}}'."
### 15. CreateCoopUser.vue — NEW
Route: `/create-coop-user`
`onMounted`: call `POST /Chapters/Officer/Scope` to get `{ own_chapter, cooperative, cooperative_list (if multiple) }`.
If officer has multiple cooperatives show cooperative picker dropdown first.
Form fields (full user creation):
- `name` (display name), `username`, `mobile_number` (format 09XXXXXXXXX), `password`, `fullname`, `firstname`, `middlename`, `lastname` (optional extras for UserInfo)
- Real-time duplicate check via existing `/admin/check/mobile` and `/admin/check/username` endpoints
- Chapter assignment: auto-set to officer's own chapter (read-only display: "Will be added to: [chapter_name]")
- Cooperative: auto-set (read-only if single, picker if multiple)
On submit: `POST /api/public/chapter/register` with `{ chapter_hash, name, username, mobile_number, password }`. On success show confirmation.
Cooperative picker: use `user.value?.settings?.cooperatives` array resolve names via `POST /Cooperatives/Get` for each hashkey show select modal. Selected cooperative must have a chapter that matches.
### 16. AssignChapterOfficer.vue — NEW
Route: `/assign-chapter-officer`
`onMounted`: call `POST /Chapters/Officer/Scope` get `eligible_members` (members of own chapter, not yet officers) + `child_chapters`.
Step 1 pick member: searchable list of `eligible_members` (name only). Tap to select.
Step 2 pick child chapter: list of `child_chapters` from scope. Tap to select.
Step 3 pick role: dropdown `[PRESIDENT, VICE_PRESIDENT, SECRETARY, TREASURER, AUDITOR, BOARD_MEMBER]`
Step 4 confirm: "Assign [name] as [role] to [child_chapter_name]? This will MOVE them from [own_chapter_name] to [child_chapter_name]."
On confirm: `POST /Chapters/Officer/Assign { member_user_hashkey, child_chapter_id, role }`.
On success: navigate back to home.
### 17. CreateChapter.vue — NEW
Route: `/create-chapter`
`onMounted`: call `POST /Chapters/Officer/Scope` get own_chapter (name, level). Compute `childLevel` from CHILD_LEVELS map. If own_chapter.level === 'barangay' show error "Cannot create sub-chapters at barangay level."
Form:
- `name` (string, required)
- `location_key` (auto-lowercase from name, editable)
- `lat`, `lng` (optional)
- Read-only: "Level: [childLevel]", "Parent: [own_chapter.name]", "Cooperative: [cooperative.name]"
On submit: `POST /Chapters/Create { name, location_key, lat, lng }`.
On success: show confirmation with new chapter name + level badge.
### 18. RegisterChapter.vue — NEW (public page)
Route: `/register-chapter--e:<encoded>` (no auth required `loginRequired: false`)
`onMounted`:
1. Decode `target` prop (base64 JSON: `{ coop_hash, chapter_hash }`)
2. Call `GET /api/public/chapter/{chapter_hash}` show chapter name, level, cooperative name
Form:
- `name`, `username`, `mobile_number`, `password`
- Real-time duplicate checks via existing public endpoints
- On submit: `POST /api/public/chapter/register { chapter_hash, name, username, mobile_number, password }`
- On success: show "Welcome to [cooperative name] [chapter name]! Your account has been created. Please login." with Login button.
No auth required. No sidebar/header full-page public layout (mirror `RegisterCoop.vue` structure).
## context
### home-data closure location (routes/web.php lines 101-199)
COORDINATOR branch at line 147. Insert COOP_OFFICER and COOP_MEMBER blocks after line 153 (after end of coordinator if block).
`$acctType` already resolved at line 117. Use `\App\Enums\UserTypes::COOP_OFFICER` and `\App\Enums\UserTypes::COOP_MEMBER`.
### ChapterController existing methods (lines 27, 75, 108, 141, 194)
- `hierarchy()` line 27 GET chapters by parent
- `mapData()` line 75 lat/lng dots
- `members()` line 108 members for chapter_id (returns full user info DO NOT reuse for search, too much data)
- `assignMember()` line 141 generic assign (does NOT move, does NOT upgrade acct_type new `assignOfficer()` must handle move + upgrade)
- `removeMember()` line 194 remove by hashkey
### CooperativeController::publicRegisterMember (lines 410-460)
Creates user with `acct_type = 'user'`, no chapter assignment. The new `publicRegisterToChapter` in ChapterController is the chapter-aware equivalent.
### useChapters.js existing methods (lines 8-127)
Returns: fetchHierarchy, fetchMapData, fetchOrgHierarchy, fetchOrgMapData, fetchMembers, assignMember, removeMember, fetchPositions, syncAutoAssignments.
ADD: fetchOrgChart (POST /Chapters/OrgChart), fetchOfficerScope (POST /Chapters/Officer/Scope), searchMembers (POST /Chapters/Members/Search), assignOfficer (POST /Chapters/Officer/Assign), createChapter (POST /Chapters/Create).
### RegisterCoop.vue (existing public page — mirror for RegisterChapter.vue)
File: `resources/js/Pages/RegisterCoop.vue`. Route: `/register-coop--h:HASHKEY`. No auth. Calls `GET /api/public/cooperative/{hkey}` then `POST /api/public/cooperative/register`. Mirror this structure for RegisterChapter.vue.
### Share link encoding
Payload: `btoa(JSON.stringify({ coop_hash, chapter_hash }))`. Frontend decodes with `JSON.parse(atob(target))` where `target` is the prop injected by VueRouteMap from the `--e:` URL segment. This follows the existing encoded payload pattern already used in VueRouteMap.
### Web Share API pattern (already in CooperativeDetail.vue)
```js
if (navigator.share) {
navigator.share({ title, url }).catch(() => {});
} else {
navigator.clipboard?.writeText(url);
// show copied notice
}
```
### LEVEL_ORDER for PHP childLevel lookup
```php
const CHILD_LEVELS = [
'national' => ['region'],
'region' => ['province'],
'province' => ['city', 'municipal'],
'city' => ['barangay'],
'municipal' => ['barangay'],
'barangay' => [],
];
```
Helper: `$childLevels = CHILD_LEVELS[$chapter->level] ?? []; if (empty($childLevels)) { return 422; }`
### Hypervel migration imports (for any new migration if needed)
```php
use Hyperf\Database\Schema\Blueprint;
use Hypervel\Database\Migrations\Migration;
use Hypervel\Support\Facades\Schema;
```
NO Illuminate classes.
### UserActions in $RoleswithNoTargetUser
`ViewChapterOrgChart`, `ManageChapterMembers`, `ViewScopedMemberReports`, `AssignChapterOfficer` must be in `UserPermissions::$RoleswithNoTargetUser` (added by prerequisite plan). Verify before adding routes.
## notes
- dictionary: ai-docs/dictionary.md
- linters: none detected
- constraints:
- PREREQUISITE: execute plan 4fc3b455fb62b15dfb06790a1f421c96 first (adds COOP_OFFICER/COOP_MEMBER types, migrations, permissions, Home.vue fragments)
- All chapter level checks use in_array(level, ['city','municipal']) not equality city and municipal are peers
- memberSearch NEVER returns mobile_number, hashkey, username or any field other than name/role/chapter_name
- HomeCoopMember NEVER shows fellow member names member count only; officer names ARE shown in org chart strip
- assignOfficer MOVES the member (delete parent row, insert child row) not a copy
- createChapter links to officer's cooperative via cooperative_id chapters are coop-scoped
- share link uses encoded payload (btoa JSON) not hashkey decode in RegisterChapter.vue onMounted
- No bg-white, bg-light, text-dark in any new Vue component use var(--bg-card), var(--text-primary)
- Sticky headers must use top: 66px; z-index: 1020
- Raw DB queries must include created_by/updated_by (Auth::id()) and timestamps (now())
- All new routes use Hypervel-style Route::post(..., ..., ['middleware' => 'auth'])