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

27 KiB
Raw Permalink Blame History

task, cycles, context, private, started, finished
task cycles context private started finished
Cooperative officer/member homepages, chapter org chart, member search, create user, assign officer to child chapter, create chapter, share registration link 5 true false 2026-05-29T00:00:00Z 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:
{
  "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):

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:

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

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

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

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:

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

[
  { 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:

[
  { text: 'My Cooperative', action: 'viewMyCoop' },
  { text: 'My Profile', pagename: 'UserInfoEdit' },
  { text: 'Member Ledger', pagename: 'AccountingDashboard' },
]

shareChapterLink() handler:

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:

[
  { 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.

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)

if (navigator.share) {
  navigator.share({ title, url }).catch(() => {});
} else {
  navigator.clipboard?.writeText(url);
  // show copied notice
}

LEVEL_ORDER for PHP childLevel lookup

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)

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'])