27 KiB
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_typevalues'coop officer'and'coop member'(UserTypes enum)chapters.cooperative_idcolumn +chapters.levelenum includes'municipal'chapter_members.rolecolumnUserActions::ViewChapterOrgChart,ManageChapterMembers,ViewScopedMemberReports,AssignChapterOfficerUserTypes::COOP_OFFICERandUserTypes::COOP_MEMBERin UserPermissions::roles()isCoopOfficerandisCoopMemberinuseAuth.jsHome.vuefragments wired for COOP_OFFICER and COOP_MEMBER
files
app/Http/Controllers/Support/ChapterController.php— add orgChart(), officerAssign(), createChapter(), memberSearch() methodsapp/Http/Controllers/Market/CooperativeController.php[line 410] — extend publicRegisterMember to accept chapter_hash; auto-assign to chapterroutes/web.php[lines 761-770] — register new chapter endpoints + public chapter routeapp/Http/Controllers/Support/VueRouteMap.php[lines 278-311] — register new SPA pages; add coop officer/member to existing coop allowedUserTypesresources/js/Pages/Fragments/Home/HomeCoopOfficer.vue— NEW: level-aware officer dashboardresources/js/Pages/Fragments/Home/HomeCoopMember.vue— NEW: read-only member cardresources/js/Pages/ChapterOrgChart.vue— NEW: org chart with child chapter toggleresources/js/Pages/CoopMemberSearch.vue— NEW: name-only member search for officersresources/js/Pages/CreateCoopUser.vue— NEW: full user creation with coop+chapter auto-assignresources/js/Pages/AssignChapterOfficer.vue— NEW: move member to child chapter as officerresources/js/Pages/CreateChapter.vue— NEW: create chapter one level below current officer's chapterresources/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_countand 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_idparam, 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 idANDcooperative_id = caller's cooperative_id - Effect:
- Delete the member's existing chapter_members row in the parent chapter
- 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} - 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
ManageChapterMembersaction (COOP_OFFICER, COORDINATOR, Big3) - Scope: collect all chapter IDs in caller's subtree (own chapter + all descendants recursively via
parent_idchain, scoped to samecooperative_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 761–770), 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:
- Header card: own_chapter.name + level badge
- Officers section (own chapter): flat list of
officer.name + officer.rolebadges. If no officers: "No officers assigned yet." - Child chapters list: for each child in
children:- Row:
[level badge] Chapter Name ——— X members [▶ toggle icon] - Toggle (
showOfficers[child.id]ref): on click, ifchild.officersis empty, callPOST /Chapters/OrgChartwith{ chapter_id: child.id }to fetch and populatechild.officers. Show officer names + roles below the row.
- Row:
- 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/mobileand/admin/check/usernameendpoints - 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:
- Decode
targetprop (base64 JSON:{ coop_hash, chapter_hash }) - 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 parentmapData()line 75 — lat/lng dotsmembers()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 — newassignOfficer()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)
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'])