feat: build out core helpers — 1 files added
Universal: Auto Version Bump / Version Bump (push) Successful in 9s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 10s

This commit is contained in:
Jonathan Miller
2026-06-21 17:37:52 -05:00
parent dfd64b202d
commit b9fdf33f97
@@ -0,0 +1,109 @@
<?php
namespace Moko\Plugin\System\MokoSuiteBeauty\Helper;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\Database\DatabaseInterface;
/**
* Stylist management — schedules, commissions, performance, chair assignment.
*/
class StylistHelper
{
/**
* Get all active stylists with current stats.
*/
public static function getAll(): array
{
$db = Factory::getContainer()->get(DatabaseInterface::class);
$db->setQuery($db->getQuery(true)
->select('s.id, s.specialties, s.commission_rate, s.chair_id')
->select('cd.name AS stylist_name, cd.telephone')
->select('ch.name AS chair_name')
->select('(SELECT COUNT(*) FROM #__mokosuitebeauty_bookings b WHERE b.stylist_id = s.id AND DATE(b.start_time) = CURDATE() AND b.status != ' . $db->quote('cancelled') . ') AS today_bookings')
->from($db->quoteName('#__mokosuitebeauty_stylists', 's'))
->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = s.contact_id')
->join('LEFT', $db->quoteName('#__mokosuitebeauty_chairs', 'ch') . ' ON ch.id = s.chair_id')
->where($db->quoteName('s.status') . ' = ' . $db->quote('active'))
->order('cd.name ASC'));
return $db->loadObjectList() ?: [];
}
/**
* Get stylist performance for a period.
*/
public static function getPerformance(int $stylistId, string $from = '', string $to = ''): object
{
$from = $from ?: date('Y-m-01');
$to = $to ?: date('Y-m-d');
if (!\DateTime::createFromFormat('Y-m-d', $from) || !\DateTime::createFromFormat('Y-m-d', $to)) {
throw new \InvalidArgumentException('Date parameters must be Y-m-d format.');
}
$db = Factory::getContainer()->get(DatabaseInterface::class);
$db->setQuery($db->getQuery(true)
->select('COUNT(*) AS total_bookings')
->select('SUM(CASE WHEN b.status = ' . $db->quote('completed') . ' THEN 1 ELSE 0 END) AS completed')
->select('SUM(CASE WHEN b.status = ' . $db->quote('no_show') . ' THEN 1 ELSE 0 END) AS no_shows')
->select('COALESCE(SUM(sv.price), 0) AS total_revenue')
->select('COUNT(DISTINCT b.client_contact_id) AS unique_clients')
->from($db->quoteName('#__mokosuitebeauty_bookings', 'b'))
->join('LEFT', $db->quoteName('#__mokosuitebeauty_services', 'sv') . ' ON sv.id = b.service_id')
->where('b.stylist_id = ' . (int) $stylistId)
->where('DATE(b.start_time) BETWEEN ' . $db->quote($from) . ' AND ' . $db->quote($to)));
$stats = $db->loadObject();
$revenue = (float) ($stats->total_revenue ?? 0);
// Get commission rate
$db->setQuery($db->getQuery(true)->select('commission_rate')->from('#__mokosuitebeauty_stylists')->where('id = ' . (int) $stylistId));
$rate = (float) $db->loadResult();
return (object) [
'stylist_id' => $stylistId,
'total_bookings' => (int) ($stats->total_bookings ?? 0),
'completed' => (int) ($stats->completed ?? 0),
'no_shows' => (int) ($stats->no_shows ?? 0),
'unique_clients' => (int) ($stats->unique_clients ?? 0),
'total_revenue' => round($revenue, 2),
'commission_earned'=> round($revenue * ($rate / 100), 2),
'avg_ticket' => (int) ($stats->completed ?? 0) > 0 ? round($revenue / (int) $stats->completed, 2) : 0,
];
}
/**
* Get leaderboard — stylists ranked by revenue.
*/
public static function getLeaderboard(string $from = '', string $to = ''): array
{
$from = $from ?: date('Y-m-01');
$to = $to ?: date('Y-m-d');
if (!\DateTime::createFromFormat('Y-m-d', $from) || !\DateTime::createFromFormat('Y-m-d', $to)) {
throw new \InvalidArgumentException('Date parameters must be Y-m-d format.');
}
$db = Factory::getContainer()->get(DatabaseInterface::class);
$db->setQuery($db->getQuery(true)
->select('s.id AS stylist_id, cd.name AS stylist_name')
->select('COUNT(b.id) AS bookings')
->select('COALESCE(SUM(sv.price), 0) AS revenue')
->from($db->quoteName('#__mokosuitebeauty_bookings', 'b'))
->join('INNER', $db->quoteName('#__mokosuitebeauty_stylists', 's') . ' ON s.id = b.stylist_id')
->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = s.contact_id')
->join('LEFT', $db->quoteName('#__mokosuitebeauty_services', 'sv') . ' ON sv.id = b.service_id')
->where($db->quoteName('b.status') . ' = ' . $db->quote('completed'))
->where('DATE(b.start_time) BETWEEN ' . $db->quote($from) . ' AND ' . $db->quote($to))
->group('s.id, cd.name')
->order('revenue DESC'));
return $db->loadObjectList() ?: [];
}
}