127 lines
5.0 KiB
PHP
127 lines
5.0 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
namespace App\Modules\Dashboard;
|
|
|
|
use App\Core\{Request, Response, Database};
|
|
|
|
final class DashboardController
|
|
{
|
|
public function getStats(Request $request): void
|
|
{
|
|
$tenantId = $request->tenantId;
|
|
$role = $request->user->role ?? 'viewer';
|
|
$assignedCompanyId = $request->user->assigned_company_id ?? null;
|
|
|
|
$cacheKey = "dashboard_stats:{$tenantId}:{$role}:" . ($assignedCompanyId ?? 'all');
|
|
$redis = null;
|
|
try {
|
|
$redis = \App\Core\Redis::getInstance();
|
|
if ($cached = $redis->get($cacheKey)) {
|
|
Response::json(['success' => true, 'data' => json_decode($cached, true)]);
|
|
return;
|
|
}
|
|
} catch (\Throwable $e) {
|
|
// Proceed without cache if Redis fails
|
|
error_log('[DASHBOARD] Redis Cache Miss/Fail: ' . $e->getMessage());
|
|
}
|
|
|
|
$db = Database::getInstance();
|
|
|
|
$companyScope = '';
|
|
$params = [$tenantId];
|
|
if ($role === 'accountant' && $assignedCompanyId) {
|
|
$companyScope = ' AND i.company_id = ?';
|
|
$params[] = $assignedCompanyId;
|
|
}
|
|
|
|
// Invoices this month
|
|
$stmt = $db->prepare("SELECT COUNT(*) FROM invoices i
|
|
WHERE i.tenant_id = ? {$companyScope} AND MONTH(i.created_at) = MONTH(CURDATE()) AND YEAR(i.created_at) = YEAR(CURDATE()) AND i.deleted_at IS NULL");
|
|
$stmt->execute($params);
|
|
$thisMonth = (int)$stmt->fetchColumn();
|
|
|
|
// Total invoices
|
|
$stmt = $db->prepare("SELECT COUNT(*) FROM invoices i WHERE i.tenant_id = ? {$companyScope} AND i.deleted_at IS NULL");
|
|
$stmt->execute($params);
|
|
$total = (int)$stmt->fetchColumn();
|
|
|
|
// Status distribution
|
|
$stmt = $db->prepare("SELECT status, COUNT(*) as count FROM invoices i
|
|
WHERE i.tenant_id = ? {$companyScope} AND i.deleted_at IS NULL GROUP BY status");
|
|
$stmt->execute($params);
|
|
$statusDistribution = $stmt->fetchAll();
|
|
|
|
// Subscription usage
|
|
$stmt = $db->prepare("SELECT max_invoices_per_month, invoices_used_this_month FROM subscriptions WHERE tenant_id = ?");
|
|
$stmt->execute([$tenantId]);
|
|
$sub = $stmt->fetch();
|
|
$usagePct = $sub && $sub['max_invoices_per_month'] > 0
|
|
? round(($sub['invoices_used_this_month'] / $sub['max_invoices_per_month']) * 100)
|
|
: 0;
|
|
|
|
// Recent invoices
|
|
$stmt = $db->prepare("SELECT i.id, i.invoice_number, i.invoice_date, i.grand_total, i.status, i.created_at, c.name as company_name, i.ai_confidence_score
|
|
FROM invoices i
|
|
JOIN companies c ON i.company_id = c.id
|
|
WHERE i.tenant_id = ? {$companyScope} AND i.deleted_at IS NULL
|
|
ORDER BY i.created_at DESC LIMIT 10");
|
|
$stmt->execute($params);
|
|
$recent = $stmt->fetchAll();
|
|
|
|
// Approved count
|
|
$stmt = $db->prepare("SELECT COUNT(*) FROM invoices i WHERE i.tenant_id = ? {$companyScope} AND i.status = 'approved' AND i.deleted_at IS NULL");
|
|
$stmt->execute($params);
|
|
$approved = (int)$stmt->fetchColumn();
|
|
|
|
// Pending extraction (from invoices table)
|
|
$stmt = $db->prepare("SELECT COUNT(*) FROM invoices WHERE tenant_id = ? {$companyScope} AND status IN ('uploaded', 'extracting') AND deleted_at IS NULL");
|
|
$stmt->execute($params);
|
|
$pendingExtraction = (int)$stmt->fetchColumn();
|
|
|
|
// Unresolved risk alerts
|
|
$stmt = $db->prepare("SELECT COUNT(*) FROM risk_scores WHERE tenant_id = ? AND is_resolved = 0");
|
|
$stmt->execute([$tenantId]);
|
|
$riskCount = (int)$stmt->fetchColumn();
|
|
|
|
// Companies count
|
|
$stmt = $db->prepare("SELECT COUNT(*) FROM companies WHERE tenant_id = ? AND is_active = 1 AND deleted_at IS NULL");
|
|
$stmt->execute([$tenantId]);
|
|
$companiesCount = (int)$stmt->fetchColumn();
|
|
|
|
$data = [
|
|
'invoices_this_month' => $thisMonth,
|
|
'subscription_usage_pct' => $usagePct,
|
|
'pending_extraction' => $pendingExtraction,
|
|
'approved_invoices' => $approved,
|
|
'status_distribution' => $statusDistribution,
|
|
'recent_invoices' => $recent,
|
|
'companies_count' => $companiesCount,
|
|
'risk_alerts_count' => $riskCount
|
|
];
|
|
|
|
if ($redis) {
|
|
try {
|
|
$redis->setex($cacheKey, 60, json_encode($data)); // Cache for 60 seconds
|
|
} catch (\Throwable $e) {}
|
|
}
|
|
|
|
Response::json([
|
|
'success' => true,
|
|
'data' => $data
|
|
]);
|
|
}
|
|
|
|
public function getRiskStats(Request $request): void
|
|
{
|
|
$db = Database::getInstance();
|
|
$tenantId = $request->tenantId;
|
|
$stmt = $db->prepare("SELECT risk_type, COUNT(*) AS count FROM risk_scores WHERE tenant_id = ? AND is_resolved = 0 GROUP BY risk_type");
|
|
$stmt->execute([$tenantId]);
|
|
|
|
Response::json([
|
|
'success' => true,
|
|
'data' => $stmt->fetchAll(),
|
|
]);
|
|
}
|
|
}
|