| ← Back to Core Domains | ← Back to Table of Contents |
4.1 Member Lifecycle
Members are tenant-local identities. A member record holds login and profile data, branch affiliation, membership evidence, minor/parent relationships, and the inputs used to determine warrant eligibility. Authentication establishes the identity; CakePHP policies and scopes determine what that identity may do.
Status model
The source of truth is the constants on App\Model\Entity\Member.
| Constant | Stored value | Meaning |
|---|---|---|
STATUS_ACTIVE |
active |
Adult account without verified membership |
STATUS_DEACTIVATED |
deactivated |
Account is disabled |
STATUS_VERIFIED_MEMBERSHIP |
verified |
Adult membership has been verified |
STATUS_UNVERIFIED_MINOR |
unverified minor |
Neither minor membership nor parent is verified |
STATUS_MINOR_MEMBERSHIP_VERIFIED |
< 18 member verified |
Minor membership is verified; parent is not |
STATUS_MINOR_PARENT_VERIFIED |
< 18 parent verified |
Parent relationship is verified; membership is not |
STATUS_VERIFIED_MINOR |
verified < 18 |
Both required minor checks are complete |
Do not reproduce these values in controllers, templates, or plugins. Use the entity constants.
Registration and verification
MemberRegistrationService owns registration support, membership-card validation/storage, and registration notification variables. Uploaded cards are stored through DocumentService; membership_card_document_id is the current association, while legacy file paths are supported only for transition compatibility.
Membership-card uploads accept JPEG/JFIF, PNG, GIF, and WebP raster images. Acceptance is based on server-detected file content and image metadata rather than the browser-provided MIME type or filename extension. Stored files use a canonical extension derived from the verified image type; SVG and other unapproved formats remain rejected.
Verification is an administrative workflow exposed through the member verification queue. Authorization still belongs in the controller policy path; a service call does not bypass it.
When a submitted membership card cannot be read, an authorized verifier can choose Request new upload. The request is bound to the card shown in the open form and stops if the member uploads a replacement before confirmation. Clearing the current card reference and dispatching the active membership-card-reupload-request workflow are one database transaction. The workflow reloads the member and sends the membership-card-reupload-requested email template to the member, with the configured account-verification contact address as the reply-to address. A workflow failure rolls the member record back so the existing card remains available. Stored-file cleanup runs after commit because filesystem deletion cannot be rolled back; a cleanup failure is logged and shown to the verifier without restoring a dangling card reference.
Member expiration imports
The Member Data Import screen updates either membership expiration dates or background-check expiration dates from CSV. The operator selects the date type, then uploads a two-column file containing Member Number and the corresponding expiration-date heading. MemberExpirationImportService validates the entire file—including exact row shape, duplicate membership numbers, and YYYY-MM-DD dates—before applying any updates through MembersTable; unmatched membership numbers are reported without blocking valid rows.
The narrow Can Import Member Data permission is granted to the shared Greater Officer of State role, but MemberPolicy permits that grant only while the member holds the current Kingdom Seneschal office assignment. The former broad member-management mapping does not grant access to this screen.
Automatic reviews
MembersTable::beforeSave() calls both entity reviews:
ageUpReview()clears the parent relationship after the member turns 18 and converts minor states to the corresponding adult state.warrantableReview()recalculateswarrantableandnon_warrantable_reasonsfrom age, verified/unexpired membership, legal name, address, and phone data.
Because these checks occur on save, code that depends on a fresh result must persist the member through MembersTable; it should not update columns directly.
Roles and permissions
Members relate to MemberRoles, including current, upcoming, and previous windows. A role assignment alone is not a substitute for policy authorization: permissions, branch scope, active windows, and any required warrant are evaluated together. Use AuthorizationService, authorizeModel(), applyScope(), and entity policies instead of ad-hoc role-name checks.
Profile and privacy
MemberProfileService validates and replaces profile photos through DocumentService. Member policy and grid-column definitions protect profile and personally identifiable data. New read paths must be scoped before serialization or export; hiding a field in HTML is not an authorization control.
Multi-tenant rules
- Member IDs are meaningful only inside the active tenant database.
- Background operations must bind the target tenant before loading a member.
- Cache identity and permission data through
TenantAwareCacheor existing loaders. - Never resolve a member from the platform database or carry a tenant entity into another tenant context.
Primary code
app/src/Model/Entity/Member.phpapp/src/Model/Table/MembersTable.phpapp/src/Controller/MembersController.phpapp/src/Services/MemberExpirationImportService.phpapp/src/Services/MemberRegistrationService.phpapp/src/Services/MemberProfileService.phpapp/src/Services/MemberSearchService.phpapp/src/KMP/PermissionsLoader.php
For a compact table-layer reference, see MembersTable reference.