# Memberships (Manage)

**Portal:** Manage · **Routes:** `manage.memberships.*` · **Nav:** Sales & Marketing → **Transaction**, the *Memberships* hub tab (one sidebar entry fronting Memberships / Property Booking / Rental + three unbuilt lines; [Components/SalesTabs.vue](/resources/js/Components/SalesTabs.vue) over the shared [HubTabs](/resources/js/Components/HubTabs.vue))

## What it does
Manages the company's **memberships** and their terms (price, description, T&C, benefits). A membership is
a single **flat, editable record** — there is no versioning and there are no upsells/add-ons; editing a
membership updates it in place. A membership does two things: leads **enrol** into it (becoming members),
and it **grants access to LMS courses**.

Seeded memberships (all admin-editable): **Legacy** (no price, inactive — kept for labelling early members),
**Elite** (RM 2388), **AI Basic** (RM 299), **AI Coaching** (RM 7899).

## How it works
- A **`Membership`** (`Src\Membership\Membership`, key model — uuid + blame + soft delete) holds the stable
  identity (`code`, `name`, `status`, `sort_order`) **and** its terms (`price`, `currency`, `description`,
  `terms`, `benefits` JSON) directly.
- **Status:** `Active` = open for enrolment; `Inactive` = closed (e.g. Legacy). Toggle via activate/deactivate.
- **Benefits** are a JSON array (entered one-per-line in the form, shown as a bulleted list).
- All writes go through `MembershipRepository` inside `DB::transaction`; the controller maps input explicitly
  and returns `Inertia::render`.
- **Members (enrolment):** a lead enrols via a **`MemberSubscription`** (`subscriptions()` 1-to-many on
  `membership_id`) — built in the [Members](/docs/modules_handbook/manage/membership/members/readMe.md)
  module. The Show page's **Members** tab lists them (bulk-add by lead search, CSV import, cancel). Each
  member row also shows its **account manager** — a **read-only** single value read off the lead
  (`subscriptions.lead.assignedAdmin`, emitted as `account_manager` by `transformSubscription`). The
  manager is set in the Leads module, not here.
- **Courses (access):** a membership grants access to LMS courses via **`courses()`** — a many-to-many over
  the **`lms_course_membership`** pivot, the inverse of `Src\Lms\Course::memberships()` (same pivot, editable
  from either side). The Show page's **Courses** tab manages this (`syncCourses`). Only **"Members"-visibility**
  courses are assignable (Public courses are open to everyone; Internal are staff-only); a Members course with
  **no** membership assigned is open to **all** members — see `Course::isAccessibleTo()` in the
  [Courses / LMS](/docs/modules_handbook/manage/lms/readMe.md) handbook.

> **Legacy `TYPE_TIER` / `TYPE_UPSELL` constants** still exist on the model only because the original
> (master-committed) `create_memberships_table` migration references `TYPE_TIER` as a default; the `type`
> column is dropped by `2026_06_16_000003_simplify_membership_schema`. They are not used by the flat model.

## Related files

**Backend — Model**
- [src/Membership/Membership.php](/src/Membership/Membership.php) — identity + terms; STATUSES; `subscriptions()`; **`courses()`** (LMS pivot).

**Backend — Repository**
- [src/Membership/Repositories/MembershipRepository.php](/src/Membership/Repositories/MembershipRepository.php) — create / update (in place), activate/deactivate, **`syncCourses()`**, delete (cascades subscriptions).

**Backend — Controller**
- [app/Http/Controllers/Manage/Membership/MembershipsController.php](/app/Http/Controllers/Manage/Membership/MembershipsController.php) — index/store/show/update/activate/deactivate/**syncCourses**/destroy (+ `assignableCourses` helper).

**Backend — Form Requests**
- [app/Http/Requests/Manage/Membership/StoreRequest.php](/app/Http/Requests/Manage/Membership/StoreRequest.php) · [UpdateRequest.php](/app/Http/Requests/Manage/Membership/UpdateRequest.php) · [MembershipQueryRequest.php](/app/Http/Requests/Manage/Membership/MembershipQueryRequest.php) · [SyncCoursesRequest.php](/app/Http/Requests/Manage/Membership/SyncCoursesRequest.php)

**Frontend (Vue)**
- [resources/js/Pages/Manage/Membership/Index.vue](/resources/js/Pages/Manage/Membership/Index.vue) — membership rows (price, status, actions).
- [resources/js/Pages/Manage/Membership/Show.vue](/resources/js/Pages/Manage/Membership/Show.vue) — identity header + **Members** & **Courses** tabs.
- [resources/js/Pages/Manage/Membership/Partials/MembershipForm.vue](/resources/js/Pages/Manage/Membership/Partials/MembershipForm.vue) + [MembershipFormModal.vue](/resources/js/Pages/Manage/Membership/Partials/MembershipFormModal.vue) — create / edit.
- [resources/js/Pages/Manage/Membership/Partials/Tabs/MembersTab.vue](/resources/js/Pages/Manage/Membership/Partials/Tabs/MembersTab.vue) · [CoursesTab.vue](/resources/js/Pages/Manage/Membership/Partials/Tabs/CoursesTab.vue) (assign courses).
- [resources/js/Layouts/ManageLayout.vue](/resources/js/Layouts/ManageLayout.vue) — sidebar nav entry.

**Migrations**
- [database/migrations/2020_05_25_042763_create_memberships_table.php](/database/migrations/2020_05_25_042763_create_memberships_table.php) — original table.
- [database/migrations/2026_06_16_000003_simplify_membership_schema.php](/database/migrations/2026_06_16_000003_simplify_membership_schema.php) — flatten: terms onto the membership; versions + upsell columns dropped.
- The `lms_course_membership` pivot is created by the LMS migration `2026_06_16_000007_create_lms_course_membership_table.php`.

**Seeder**
- [database/seeds/MembershipsSeeder.php](/database/seeds/MembershipsSeeder.php) — seeds Legacy / Elite / AI Basic / AI Coaching with sample terms.

**Routes**
- [routes/web.php](/routes/web.php) — `manage.memberships.*` (incl. `manage.memberships.courses.sync`).
