Skip to content

Gym Module Documentation

Overview

The Gym Module manages the subscription lifecycle for fitness center members. It generates recurring revenue through time-based "Plans" (Monthly, Yearly) and one-off "Day Passes". It is fully integrated with Accounting (Accrual Revenue) and Tax (VAT Calculation).

Architecture

Domain Layer (app/Domain/Gym)

Models (4 Models)

GymMember (GymMember.php)
  • Table: gym_members
  • Description: The profile. Links to Party.
  • Key Fields:
    • status: ACTIVE | INACTIVE | BANNED.
    • health_conditions: Medical notes.
    • emergency_contact.
GymPlan (GymPlan.php)
  • Table: gym_plans
  • Description: The product definition.
  • Key Fields:
    • name: e.g., "Gold Monthly".
    • duration_days: 30.
    • price: Base price (excluding tax).
    • revenue_account_id: GL Account to credit (e.g. 413).
GymMembership (GymMembership.php)
  • Table: gym_memberships
  • Description: The contract.
  • Key Fields:
    • start_date, end_date: Access window.
    • amount_paid: Total.
    • renewed_from_id: Link to previous membership (Traceability).
GymCheckIn (GymCheckIn.php)
  • Table: gym_check_ins
  • Description: Audit log of access.

Services

GymMembershipService (GymMembershipService.php)

Purpose: Sales and Lifecycle management.

Key Methods:

sellMembership(member, plan, startDate, payments)

Creates a net-new contract.

  • Logic:
    1. Calculates endDate (startDate + plan.duration).
    2. Calculates Tax (TaxEngine::calculate('GYM', price)).
    3. Accounting: Credits Revenue (413), Credits Tax (204), Debits Cash (101).
    4. Updates Member Status -> ACTIVE.
renewMembership(oldMembership, plan)

Extends access.

  • Logic:
    1. Smart Date: If oldMembership is still active, new start date = old_end_date + 1 day. If expired, starts today.
    2. Chain: Sets renewed_from_id to build a history chain.
    3. Expire: Sets oldMembership.status = EXPIRED.
postMembershipRevenue()

Internal helper for GL posting.

  • Checks: Verifies "Sales Discount" (450) and "Tax Payable" (204) accounts exist. Throws exception if missing (Safety).

Audit Findings & Improvements

Strengths

  • Renewal Logic: The "Smart Date" logic ensures members aren't penalized for renewing early (they don't lose the remaining days of their current pass).
  • Traceability: The renewed_from_id linked list allows accurate calculation of "Customer Lifetime Value" and retention rates.

Issues Identified

Major

  • Hardcoded Account Requirements: The service throws an exception if Account 450 (Discounts) is missing, even if no discount is applied. This creates a fragility where a Chart of Accounts change can break the Gym POS.
    • Fix: Make the check conditional on discount_amount > 0.

Minor

  • Constraint Error: Previous debugging session revealed gym_plan_id integrity issues if the Plan is soft-deleted.
  • Date Rigidness: Day passes require valid_date == today. You cannot sell a "Tomorrow Pass".

Module Version

Version: 1.0 Status: Stable