feat: implement staff management and subscription system

- Added StaffController for managing clinic staff, including listing, creating, updating, and toggling staff status.
- Created ClinicStaff entity and repository for staff data handling.
- Developed SubscriptionController to manage subscription plans and periods, including trial subscriptions.
- Introduced SubscriptionPlan, SubscriptionPeriod, and ClinicSubscription entities for subscription management.
- Implemented SubscriptionService for handling subscription logic, including trial activation and subscription creation from payments.
- Added necessary repositories for subscription entities to facilitate data access and manipulation.
This commit is contained in:
hamed
2026-06-14 22:10:28 +03:30
parent dcd631f503
commit b0244f28f5
53 changed files with 4434 additions and 40 deletions
+39 -20
View File
@@ -1687,34 +1687,37 @@ class AdminApiController extends BaseController
// ── Dashboard Charts ──────────────────────────────────────────────────────
#[Route('/api/v1/admin/dashboard/charts', methods: ['GET'])]
public function dashboardCharts(): JsonResponse
public function dashboardCharts(Request $request): JsonResponse
{
$conn = $this->em->getConnection();
$days = 30;
$now = time();
$start = $now - ($days * 86400);
$conn = $this->em->getConnection();
$now = time();
$from = $request->query->get('from') ? (int) $request->query->get('from') : $now - (30 * 86400);
$to = $request->query->get('to') ? (int) $request->query->get('to') : $now;
$apptRows = $conn->fetchAllAssociative(
'SELECT DATE(FROM_UNIXTIME(slot_start)) AS d, COUNT(*) AS cnt
FROM appointments WHERE slot_start >= :s GROUP BY d ORDER BY d',
['s' => $start]
FROM appointments WHERE slot_start BETWEEN :s AND :e GROUP BY d ORDER BY d',
['s' => $from, 'e' => $to]
);
$apptMap = array_column($apptRows, 'cnt', 'd');
$revRows = $conn->fetchAllAssociative(
'SELECT DATE(FROM_UNIXTIME(created_at)) AS d, COALESCE(SUM(amount_rials), 0) AS total
FROM payments WHERE status = :st AND created_at >= :s GROUP BY d ORDER BY d',
['st' => 'received', 's' => $start]
FROM payments WHERE status = :st AND created_at BETWEEN :s AND :e GROUP BY d ORDER BY d',
['st' => 'received', 's' => $from, 'e' => $to]
);
$revMap = array_column($revRows, 'total', 'd');
$appt30d = [];
$rev30d = [];
for ($i = $days - 1; $i >= 0; $i--) {
$date = date('Y-m-d', $now - $i * 86400);
$shortDate = date('m/d', $now - $i * 86400);
$appt30d[] = ['date' => $shortDate, 'count' => (int)($apptMap[$date] ?? 0)];
$rev30d[] = ['date' => $shortDate, 'amount' => (int)($revMap[$date] ?? 0)];
$days = max(1, (int) ceil(($to - $from) / 86400));
$apptByDay = [];
$revByDay = [];
for ($i = 0; $i < $days; $i++) {
$ts = $from + $i * 86400;
$date = date('Y-m-d', $ts);
$shortDate = date('m/d', $ts);
$apptByDay[] = ['date' => $shortDate, 'count' => (int)($apptMap[$date] ?? 0)];
$revByDay[] = ['date' => $shortDate, 'amount' => (int)($revMap[$date] ?? 0)];
}
$statusRows = $conn->fetchAllAssociative(
@@ -1729,11 +1732,27 @@ class AdminApiController extends BaseController
GROUP BY s.id, s.name ORDER BY cnt DESC LIMIT 8'
);
$subRows = $conn->fetchAllAssociative(
'SELECT sp.name AS plan_name, COUNT(cs.id) AS cnt, COALESCE(SUM(p.amount_rials), 0) AS revenue
FROM clinic_subscriptions cs
JOIN subscription_plans sp ON sp.id = cs.plan_id
LEFT JOIN payments p ON p.id = cs.payment_id AND p.status = :st
WHERE cs.created_at BETWEEN :s AND :e
GROUP BY sp.id, sp.name ORDER BY cnt DESC',
['st' => 'received', 's' => $from, 'e' => $to]
);
return $this->success([
'appointments_30d' => $appt30d,
'revenue_30d' => $rev30d,
'appointment_status' => array_map(fn($r) => ['status' => $r['status'], 'count' => (int) $r['cnt']], $statusRows),
'top_specialties' => array_map(fn($r) => ['name' => $r['name'], 'count' => (int) $r['cnt']], $specRows),
'appointments_by_day' => $apptByDay,
'revenue_by_day' => $revByDay,
'appointment_status' => array_map(fn($r) => ['status' => $r['status'], 'count' => (int) $r['cnt']], $statusRows),
'top_specialties' => array_map(fn($r) => ['name' => $r['name'], 'count' => (int) $r['cnt']], $specRows),
'subscription_sales_by_plan' => array_map(fn($r) => [
'plan' => $r['plan_name'],
'count' => (int) $r['cnt'],
'revenue' => (int) $r['revenue'],
], $subRows),
'period' => ['from' => $from, 'to' => $to],
]);
}
}