From b9fdf33f97e65d19d04a5fb4dd67758aba959aef Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sun, 21 Jun 2026 17:37:52 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20build=20out=20core=20helpers=20?= =?UTF-8?q?=E2=80=94=201=20files=20added?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Helper/StylistHelper.php | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 source/packages/plg_system_mokosuitebeauty/src/Helper/StylistHelper.php diff --git a/source/packages/plg_system_mokosuitebeauty/src/Helper/StylistHelper.php b/source/packages/plg_system_mokosuitebeauty/src/Helper/StylistHelper.php new file mode 100644 index 0000000..7f6f653 --- /dev/null +++ b/source/packages/plg_system_mokosuitebeauty/src/Helper/StylistHelper.php @@ -0,0 +1,109 @@ +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() ?: []; + } +}