From 5fe27b614dd933bb8d3fb525cbacfbdde70de3fe Mon Sep 17 00:00:00 2001
From: Jonathan Miller <1+jmiller@noreply.git.mokoconsulting.tech>
Date: Sat, 27 Jun 2026 20:18:36 +0000
Subject: [PATCH] feat: initial scaffold
---
.gitignore | 5 +
CHANGELOG.md | 20 ++
CLAUDE.md | 30 +++
.../com_mokosuitegym/admin/access.xml | 11 ++
.../com_mokosuitegym/admin/config.xml | 6 +
.../admin/services/provider.php | 30 +++
.../com_mokosuitegym/mokosuitegym.xml | 32 ++++
.../en-GB/plg_system_mokosuitegym.ini | 3 +
.../en-GB/plg_system_mokosuitegym.sys.ini | 3 +
.../plg_system_mokosuitegym/mokosuitegym.xml | 43 +++++
.../services/provider.php | 31 +++
.../sql/install.mysql.sql | 181 ++++++++++++++++++
.../sql/uninstall.mysql.sql | 9 +
.../src/Extension/Gym.php | 20 ++
source/pkg_mokosuitegym.xml | 21 ++
15 files changed, 445 insertions(+)
create mode 100644 .gitignore
create mode 100644 CHANGELOG.md
create mode 100644 CLAUDE.md
create mode 100644 source/packages/com_mokosuitegym/admin/access.xml
create mode 100644 source/packages/com_mokosuitegym/admin/config.xml
create mode 100644 source/packages/com_mokosuitegym/admin/services/provider.php
create mode 100644 source/packages/com_mokosuitegym/mokosuitegym.xml
create mode 100644 source/packages/plg_system_mokosuitegym/language/en-GB/plg_system_mokosuitegym.ini
create mode 100644 source/packages/plg_system_mokosuitegym/language/en-GB/plg_system_mokosuitegym.sys.ini
create mode 100644 source/packages/plg_system_mokosuitegym/mokosuitegym.xml
create mode 100644 source/packages/plg_system_mokosuitegym/services/provider.php
create mode 100644 source/packages/plg_system_mokosuitegym/sql/install.mysql.sql
create mode 100644 source/packages/plg_system_mokosuitegym/sql/uninstall.mysql.sql
create mode 100644 source/packages/plg_system_mokosuitegym/src/Extension/Gym.php
create mode 100644 source/pkg_mokosuitegym.xml
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b919835
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+.claude/
+.mcp.json
+TODO.md
+*.min.css
+*.min.js
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..bfcc00a
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,20 @@
+
+
+# Changelog
+
+## [Unreleased]
+
+### Added
+- **Repository** -- initial repo creation with scaffolding
+- **System Plugin** -- Extension class, service provider
+- **SQL Schema** -- 9 tables
+- **Admin Component** -- 6 views: Dashboard, Members, Memberships, Classes, Trainers, Equipment
+- **Webservices Plugin** -- 6 API routes
+- **Configuration** -- basic settings fieldset
+- **Access Control** -- core permissions
+- **Language Files** -- en-GB translations
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..69ba425
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,30 @@
+# MokoSuiteGym
+
+Layer 2 — Fitness center management, memberships, class scheduling, trainer management, equipment tracking
+
+## Quick Reference
+
+| Field | Value |
+|---|---|
+| **Package** | `pkg_mokosuitegym` |
+| **Layer** | 2 (requires: Client, CRM) |
+| **Language** | PHP 8.3+ |
+| **Branch** | develop on `dev`, merge to `main` (protected) |
+
+## Architecture
+
+Joomla **package** -- Layer 2 add-on. CRM contacts as gym members and trainers, membership tiers, class scheduling with enrollment, and equipment maintenance.
+
+## Rules
+
+- **Never commit** `.claude/`, `.mcp.json`, `TODO.md`, `*.min.css`/`*.min.js`
+- **Attribution**: `Authored-by: Moko Consulting`
+- **Workflow directory**: `.mokogitea/`
+- **Standards**: [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoCLI/wiki)
+- **Changelog**: `[Unreleased]` only -- release system assigns versions
+
+## Coding Standards
+
+- PHP 8.3+ / Joomla 6 patterns
+- `$this->getDatabase()` in models, `Factory::getContainer()->get(DatabaseInterface::class)` in helpers
+- `Factory::getApplication()->getIdentity()` for user
diff --git a/source/packages/com_mokosuitegym/admin/access.xml b/source/packages/com_mokosuitegym/admin/access.xml
new file mode 100644
index 0000000..5485090
--- /dev/null
+++ b/source/packages/com_mokosuitegym/admin/access.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/source/packages/com_mokosuitegym/admin/config.xml b/source/packages/com_mokosuitegym/admin/config.xml
new file mode 100644
index 0000000..6c7aef9
--- /dev/null
+++ b/source/packages/com_mokosuitegym/admin/config.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/source/packages/com_mokosuitegym/admin/services/provider.php b/source/packages/com_mokosuitegym/admin/services/provider.php
new file mode 100644
index 0000000..a7e02a6
--- /dev/null
+++ b/source/packages/com_mokosuitegym/admin/services/provider.php
@@ -0,0 +1,30 @@
+set(
+ ComponentInterface::class,
+ function (Container $container) {
+ $component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));
+ $component->setMVCFactory($container->get(MVCFactoryInterface::class));
+ return $component;
+ }
+ );
+ }
+};
diff --git a/source/packages/com_mokosuitegym/mokosuitegym.xml b/source/packages/com_mokosuitegym/mokosuitegym.xml
new file mode 100644
index 0000000..60a0804
--- /dev/null
+++ b/source/packages/com_mokosuitegym/mokosuitegym.xml
@@ -0,0 +1,32 @@
+
+
+ com_mokosuitegym
+ 0.0.0
+ 2026-06
+ Moko Consulting
+ hello@mokoconsulting.tech
+ https://mokoconsulting.tech
+ (C) 2026 Moko Consulting
+ GPL-3.0-or-later
+ Layer 2 — Fitness center management, memberships, class scheduling, trainer management, equipment tracking
+ MokoConsulting\Component\MokoSuiteGym
+
+
+ services
+ src
+ tmpl
+ language
+ access.xml
+ config.xml
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/source/packages/plg_system_mokosuitegym/language/en-GB/plg_system_mokosuitegym.ini b/source/packages/plg_system_mokosuitegym/language/en-GB/plg_system_mokosuitegym.ini
new file mode 100644
index 0000000..1059f2d
--- /dev/null
+++ b/source/packages/plg_system_mokosuitegym/language/en-GB/plg_system_mokosuitegym.ini
@@ -0,0 +1,3 @@
+PLG_SYSTEM_MOKOSUITEGYM="Gym"
+PLG_SYSTEM_MOKOSUITEGYM_DESCRIPTION="MokoSuiteGym system plugin"
+PLG_SYSTEM_MOKOSUITEGYM_ENABLED="Enable MokoSuiteGym"
diff --git a/source/packages/plg_system_mokosuitegym/language/en-GB/plg_system_mokosuitegym.sys.ini b/source/packages/plg_system_mokosuitegym/language/en-GB/plg_system_mokosuitegym.sys.ini
new file mode 100644
index 0000000..1059f2d
--- /dev/null
+++ b/source/packages/plg_system_mokosuitegym/language/en-GB/plg_system_mokosuitegym.sys.ini
@@ -0,0 +1,3 @@
+PLG_SYSTEM_MOKOSUITEGYM="Gym"
+PLG_SYSTEM_MOKOSUITEGYM_DESCRIPTION="MokoSuiteGym system plugin"
+PLG_SYSTEM_MOKOSUITEGYM_ENABLED="Enable MokoSuiteGym"
diff --git a/source/packages/plg_system_mokosuitegym/mokosuitegym.xml b/source/packages/plg_system_mokosuitegym/mokosuitegym.xml
new file mode 100644
index 0000000..3d90a96
--- /dev/null
+++ b/source/packages/plg_system_mokosuitegym/mokosuitegym.xml
@@ -0,0 +1,43 @@
+
+
+ plg_system_mokosuitegym
+ 0.0.0
+ 2026-06
+ Moko Consulting
+ hello@mokoconsulting.tech
+ https://mokoconsulting.tech
+ (C) 2026 Moko Consulting
+ GPL-3.0-or-later
+ MokoSuiteGym system plugin -- schema owner and bootstrap
+ MokoConsulting\Plugin\System\MokoSuiteGym
+
+ src
+ services
+ sql
+ language
+
+
+
+ sql/install.mysql.sql
+
+
+
+
+ sql/uninstall.mysql.sql
+
+
+
+ en-GB/plg_system_mokosuitegym.ini
+ en-GB/plg_system_mokosuitegym.sys.ini
+
+
+
+
+
+
+
diff --git a/source/packages/plg_system_mokosuitegym/services/provider.php b/source/packages/plg_system_mokosuitegym/services/provider.php
new file mode 100644
index 0000000..80dfbf4
--- /dev/null
+++ b/source/packages/plg_system_mokosuitegym/services/provider.php
@@ -0,0 +1,31 @@
+set(
+ PluginInterface::class,
+ function (Container $container) {
+ $dispatcher = $container->get(DispatcherInterface::class);
+ $plugin = new Gym($dispatcher, (array) PluginHelper::getPlugin('system', 'mokosuitegym'));
+ $plugin->setApplication(Factory::getApplication());
+ return $plugin;
+ }
+ );
+ }
+};
diff --git a/source/packages/plg_system_mokosuitegym/sql/install.mysql.sql b/source/packages/plg_system_mokosuitegym/sql/install.mysql.sql
new file mode 100644
index 0000000..4ccb845
--- /dev/null
+++ b/source/packages/plg_system_mokosuitegym/sql/install.mysql.sql
@@ -0,0 +1,181 @@
+-- MokoSuiteGym Schema
+-- Copyright (C) 2026 Moko Consulting
+-- SPDX-License-Identifier: GPL-3.0-or-later
+
+CREATE TABLE IF NOT EXISTS `#__mokosuitegym_memberships` (
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ `name` VARCHAR(255) NOT NULL,
+ `description` TEXT NULL,
+ `tier` ENUM('basic','standard','premium','vip','student','senior') NOT NULL DEFAULT 'basic',
+ `duration_months` INT UNSIGNED NOT NULL DEFAULT 1,
+ `price` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
+ `enrollment_fee` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
+ `guest_passes` INT UNSIGNED NOT NULL DEFAULT 0,
+ `class_access` TINYINT(1) NOT NULL DEFAULT 0,
+ `trainer_sessions` INT UNSIGNED NOT NULL DEFAULT 0,
+ `status` ENUM('active','inactive','archived') NOT NULL DEFAULT 'active',
+ `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `modified` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ KEY `idx_tier` (`tier`),
+ KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+CREATE TABLE IF NOT EXISTS `#__mokosuitegym_members` (
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ `contact_id` INT UNSIGNED NULL COMMENT 'FK to CRM contacts',
+ `member_number` VARCHAR(50) NOT NULL,
+ `first_name` VARCHAR(255) NOT NULL,
+ `last_name` VARCHAR(255) NOT NULL,
+ `date_of_birth` DATE NULL,
+ `gender` ENUM('male','female','other') NULL,
+ `phone` VARCHAR(50) NULL,
+ `email` VARCHAR(255) NULL,
+ `emergency_contact_name` VARCHAR(255) NULL,
+ `emergency_contact_phone` VARCHAR(50) NULL,
+ `photo` VARCHAR(500) NULL,
+ `join_date` DATE NOT NULL,
+ `status` ENUM('active','inactive','frozen','cancelled') NOT NULL DEFAULT 'active',
+ `notes` TEXT NULL,
+ `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `modified` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `idx_member_number` (`member_number`),
+ KEY `idx_contact` (`contact_id`),
+ KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+CREATE TABLE IF NOT EXISTS `#__mokosuitegym_member_memberships` (
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ `member_id` INT UNSIGNED NOT NULL,
+ `membership_id` INT UNSIGNED NOT NULL,
+ `start_date` DATE NOT NULL,
+ `end_date` DATE NULL,
+ `auto_renew` TINYINT(1) NOT NULL DEFAULT 1,
+ `payment_method` ENUM('cash','card','bank_transfer','online') NULL,
+ `amount_paid` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
+ `status` ENUM('active','expired','cancelled','frozen') NOT NULL DEFAULT 'active',
+ `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `modified` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ KEY `idx_member` (`member_id`),
+ KEY `idx_membership` (`membership_id`),
+ KEY `idx_status` (`status`),
+ KEY `idx_end_date` (`end_date`),
+ CONSTRAINT `fk_membermembership_member` FOREIGN KEY (`member_id`) REFERENCES `#__mokosuitegym_members`(`id`) ON DELETE CASCADE,
+ CONSTRAINT `fk_membermembership_membership` FOREIGN KEY (`membership_id`) REFERENCES `#__mokosuitegym_memberships`(`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+CREATE TABLE IF NOT EXISTS `#__mokosuitegym_trainers` (
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ `contact_id` INT UNSIGNED NULL COMMENT 'FK to CRM contacts',
+ `first_name` VARCHAR(255) NOT NULL,
+ `last_name` VARCHAR(255) NOT NULL,
+ `specializations` JSON NULL,
+ `certifications` JSON NULL,
+ `bio` TEXT NULL,
+ `phone` VARCHAR(50) NULL,
+ `email` VARCHAR(255) NULL,
+ `photo` VARCHAR(500) NULL,
+ `hourly_rate` DECIMAL(10,2) NULL,
+ `hire_date` DATE NULL,
+ `status` ENUM('active','inactive','on_leave') NOT NULL DEFAULT 'active',
+ `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `modified` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ KEY `idx_contact` (`contact_id`),
+ KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+CREATE TABLE IF NOT EXISTS `#__mokosuitegym_classes` (
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ `name` VARCHAR(255) NOT NULL,
+ `description` TEXT NULL,
+ `class_type` ENUM('yoga','pilates','spinning','hiit','crossfit','zumba','boxing','swimming','strength','cardio','other') NOT NULL DEFAULT 'other',
+ `trainer_id` INT UNSIGNED NULL,
+ `duration_minutes` INT UNSIGNED NOT NULL DEFAULT 60,
+ `max_capacity` INT UNSIGNED NULL,
+ `difficulty` ENUM('beginner','intermediate','advanced','all_levels') NOT NULL DEFAULT 'all_levels',
+ `room` VARCHAR(100) NULL,
+ `status` ENUM('active','inactive','archived') NOT NULL DEFAULT 'active',
+ `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `modified` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ KEY `idx_trainer` (`trainer_id`),
+ KEY `idx_class_type` (`class_type`),
+ KEY `idx_status` (`status`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+CREATE TABLE IF NOT EXISTS `#__mokosuitegym_class_schedules` (
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ `class_id` INT UNSIGNED NOT NULL,
+ `trainer_id` INT UNSIGNED NULL,
+ `day_of_week` ENUM('monday','tuesday','wednesday','thursday','friday','saturday','sunday') NOT NULL,
+ `start_time` TIME NOT NULL,
+ `end_time` TIME NOT NULL,
+ `room` VARCHAR(100) NULL,
+ `effective_from` DATE NULL,
+ `effective_until` DATE NULL,
+ `status` ENUM('active','cancelled','suspended') NOT NULL DEFAULT 'active',
+ `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `modified` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ KEY `idx_class` (`class_id`),
+ KEY `idx_trainer` (`trainer_id`),
+ KEY `idx_day` (`day_of_week`),
+ KEY `idx_status` (`status`),
+ CONSTRAINT `fk_schedule_class` FOREIGN KEY (`class_id`) REFERENCES `#__mokosuitegym_classes`(`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+CREATE TABLE IF NOT EXISTS `#__mokosuitegym_class_enrollments` (
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ `class_schedule_id` INT UNSIGNED NOT NULL,
+ `member_id` INT UNSIGNED NOT NULL,
+ `enrollment_date` DATE NOT NULL,
+ `session_date` DATE NOT NULL,
+ `status` ENUM('enrolled','attended','no_show','cancelled') NOT NULL DEFAULT 'enrolled',
+ `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ KEY `idx_schedule` (`class_schedule_id`),
+ KEY `idx_member` (`member_id`),
+ KEY `idx_session_date` (`session_date`),
+ KEY `idx_status` (`status`),
+ CONSTRAINT `fk_enrollment_schedule` FOREIGN KEY (`class_schedule_id`) REFERENCES `#__mokosuitegym_class_schedules`(`id`) ON DELETE CASCADE,
+ CONSTRAINT `fk_enrollment_member` FOREIGN KEY (`member_id`) REFERENCES `#__mokosuitegym_members`(`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+CREATE TABLE IF NOT EXISTS `#__mokosuitegym_equipment` (
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ `name` VARCHAR(255) NOT NULL,
+ `category` ENUM('cardio','strength','free_weights','machines','accessories','other') NOT NULL DEFAULT 'other',
+ `brand` VARCHAR(255) NULL,
+ `model` VARCHAR(255) NULL,
+ `serial_number` VARCHAR(255) NULL,
+ `purchase_date` DATE NULL,
+ `purchase_cost` DECIMAL(10,2) NULL,
+ `warranty_expiry` DATE NULL,
+ `location` VARCHAR(100) NULL,
+ `last_maintenance` DATE NULL,
+ `next_maintenance` DATE NULL,
+ `status` ENUM('operational','maintenance','out_of_order','retired') NOT NULL DEFAULT 'operational',
+ `notes` TEXT NULL,
+ `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `modified` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ KEY `idx_category` (`category`),
+ KEY `idx_status` (`status`),
+ KEY `idx_next_maintenance` (`next_maintenance`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+
+CREATE TABLE IF NOT EXISTS `#__mokosuitegym_check_ins` (
+ `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+ `member_id` INT UNSIGNED NOT NULL,
+ `check_in_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `check_out_time` DATETIME NULL,
+ `location` VARCHAR(100) NULL COMMENT 'e.g. main entrance, pool, spa',
+ `created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ PRIMARY KEY (`id`),
+ KEY `idx_member` (`member_id`),
+ KEY `idx_check_in_time` (`check_in_time`),
+ CONSTRAINT `fk_checkin_member` FOREIGN KEY (`member_id`) REFERENCES `#__mokosuitegym_members`(`id`) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
diff --git a/source/packages/plg_system_mokosuitegym/sql/uninstall.mysql.sql b/source/packages/plg_system_mokosuitegym/sql/uninstall.mysql.sql
new file mode 100644
index 0000000..1745298
--- /dev/null
+++ b/source/packages/plg_system_mokosuitegym/sql/uninstall.mysql.sql
@@ -0,0 +1,9 @@
+DROP TABLE IF EXISTS `#__mokosuitegym_check_ins`;
+DROP TABLE IF EXISTS `#__mokosuitegym_equipment`;
+DROP TABLE IF EXISTS `#__mokosuitegym_class_enrollments`;
+DROP TABLE IF EXISTS `#__mokosuitegym_class_schedules`;
+DROP TABLE IF EXISTS `#__mokosuitegym_classes`;
+DROP TABLE IF EXISTS `#__mokosuitegym_trainers`;
+DROP TABLE IF EXISTS `#__mokosuitegym_member_memberships`;
+DROP TABLE IF EXISTS `#__mokosuitegym_members`;
+DROP TABLE IF EXISTS `#__mokosuitegym_memberships`;
diff --git a/source/packages/plg_system_mokosuitegym/src/Extension/Gym.php b/source/packages/plg_system_mokosuitegym/src/Extension/Gym.php
new file mode 100644
index 0000000..dc8f148
--- /dev/null
+++ b/source/packages/plg_system_mokosuitegym/src/Extension/Gym.php
@@ -0,0 +1,20 @@
+
+
+ MokoSuiteGym
+ mokosuitegym
+ 0.0.0
+ 2026-06
+ Moko Consulting
+ hello@mokoconsulting.tech
+ https://mokoconsulting.tech
+ (C) 2026 Moko Consulting
+ GPL-3.0-or-later
+ Layer 2 — Fitness center management, memberships, class scheduling, trainer management, equipment tracking
+
+ plg_system_mokosuitegym.zip
+ com_mokosuitegym.zip
+ plg_webservices_mokosuitegym.zip
+
+
+ https://git.mokoconsulting.tech/api/packages/MokoConsulting/generic/updates/mokosuitegym/updates.xml
+
+