Update: 2026-05-08 04:58:23
This commit is contained in:
@@ -123,8 +123,21 @@ try {
|
||||
'api_success' => $apiResponse['success'],
|
||||
], $decoded);
|
||||
|
||||
// Smart Notifications
|
||||
\App\Services\SmartNotifications::invoiceApproved(
|
||||
$invoice['tenant_id'], $invoice['uploaded_by'] ?? $decoded['user_id'],
|
||||
$id, $invoice['invoice_number'] ?? $id
|
||||
);
|
||||
\App\Services\SmartNotifications::checkQuotaWarning($invoice['tenant_id']);
|
||||
|
||||
// Gamification
|
||||
\App\Services\GamificationService::award($decoded['user_id'], $invoice['tenant_id'], 'invoice_approved');
|
||||
if ($apiResponse['success'] ?? false) {
|
||||
\App\Services\GamificationService::award($decoded['user_id'], $invoice['tenant_id'], 'jofotara_submitted');
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
if ($db->inTransaction()) $db->rollBack();
|
||||
error_log("JoFotara Approve Error: " . $e->getMessage());
|
||||
json_error('خطأ غير متوقع: ' . $e->getMessage(), 500);
|
||||
safe_error($e, 'invoices/approve');
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoices List Endpoint (Role-Based & Tenant-Aware)
|
||||
* Invoices List Endpoint (Role-Based, Tenant-Aware, Paginated)
|
||||
*/
|
||||
|
||||
use App\Core\Database;
|
||||
@@ -16,26 +16,17 @@ $userId = $decoded['user_id'];
|
||||
$role = $decoded['role'];
|
||||
|
||||
try {
|
||||
// 2. Build Query based on Role
|
||||
$pagination = paginate_params(25, 100);
|
||||
|
||||
// 2. Build WHERE clause based on Role
|
||||
$where = '';
|
||||
$params = [];
|
||||
|
||||
if ($role === 'super_admin') {
|
||||
// Super Admin sees ALL invoices
|
||||
$stmt = $db->query("
|
||||
SELECT i.*, t.name as tenant_name, c.name as company_name
|
||||
FROM invoices i
|
||||
LEFT JOIN tenants t ON i.tenant_id = t.id
|
||||
LEFT JOIN companies c ON i.company_id = c.id
|
||||
ORDER BY i.created_at DESC
|
||||
");
|
||||
$where = '1=1';
|
||||
} elseif ($role === 'admin') {
|
||||
// Admin sees all invoices in THEIR tenant
|
||||
$stmt = $db->prepare("
|
||||
SELECT i.*, c.name as company_name
|
||||
FROM invoices i
|
||||
LEFT JOIN companies c ON i.company_id = c.id
|
||||
WHERE i.tenant_id = ?
|
||||
ORDER BY i.created_at DESC
|
||||
");
|
||||
$stmt->execute([$tenantId]);
|
||||
$where = 'i.tenant_id = ?';
|
||||
$params = [$tenantId];
|
||||
} else {
|
||||
// Accountant/Viewer: Filter by assigned companies
|
||||
$stmtUser = $db->prepare("SELECT company_id FROM user_company_assignments WHERE user_id = ? AND is_active = 1");
|
||||
@@ -43,26 +34,58 @@ try {
|
||||
$assignedCompanyIds = $stmtUser->fetchAll(PDO::FETCH_COLUMN);
|
||||
|
||||
if (empty($assignedCompanyIds)) {
|
||||
json_success([]);
|
||||
json_paginated([], 0, $pagination);
|
||||
}
|
||||
|
||||
$placeholders = implode(',', array_fill(0, count($assignedCompanyIds), '?'));
|
||||
$stmt = $db->prepare("
|
||||
SELECT i.*, c.name as company_name
|
||||
FROM invoices i
|
||||
LEFT JOIN companies c ON i.company_id = c.id
|
||||
WHERE i.company_id IN ($placeholders)
|
||||
ORDER BY i.created_at DESC
|
||||
");
|
||||
$stmt->execute($assignedCompanyIds);
|
||||
$where = "i.company_id IN ($placeholders)";
|
||||
$params = $assignedCompanyIds;
|
||||
}
|
||||
|
||||
// Optional filters from query string
|
||||
$companyFilter = $_GET['company_id'] ?? null;
|
||||
$statusFilter = $_GET['status'] ?? null;
|
||||
$searchFilter = $_GET['search'] ?? null;
|
||||
|
||||
if ($companyFilter) {
|
||||
$where .= ' AND i.company_id = ?';
|
||||
$params[] = $companyFilter;
|
||||
}
|
||||
if ($statusFilter) {
|
||||
$where .= ' AND i.status = ?';
|
||||
$params[] = $statusFilter;
|
||||
}
|
||||
if ($searchFilter) {
|
||||
$where .= ' AND (i.invoice_number LIKE ? OR i.supplier_name LIKE ?)';
|
||||
$params[] = "%$searchFilter%";
|
||||
$params[] = "%$searchFilter%";
|
||||
}
|
||||
|
||||
// 3. Count total
|
||||
$countStmt = $db->prepare("SELECT COUNT(*) FROM invoices i WHERE $where");
|
||||
$countStmt->execute($params);
|
||||
$total = (int)$countStmt->fetchColumn();
|
||||
|
||||
// 4. Fetch page
|
||||
$joinTenant = ($role === 'super_admin') ? 'LEFT JOIN tenants t ON i.tenant_id = t.id' : '';
|
||||
$selectTenant = ($role === 'super_admin') ? ', t.name as tenant_name' : '';
|
||||
|
||||
$stmt = $db->prepare("
|
||||
SELECT i.*{$selectTenant}, c.name as company_name
|
||||
FROM invoices i
|
||||
LEFT JOIN companies c ON i.company_id = c.id
|
||||
{$joinTenant}
|
||||
WHERE {$where}
|
||||
ORDER BY i.created_at DESC
|
||||
LIMIT {$pagination['limit']} OFFSET {$pagination['offset']}
|
||||
");
|
||||
$stmt->execute($params);
|
||||
$invoices = $stmt->fetchAll();
|
||||
|
||||
// 3. Decrypt sensitive fields for display (Robustly)
|
||||
// 5. Decrypt sensitive fields
|
||||
$dec = function($val) {
|
||||
if (empty($val)) return '';
|
||||
$result = \App\Core\Encryption::decrypt((string)$val);
|
||||
$result = Encryption::decrypt((string)$val);
|
||||
return ($result !== false && $result !== null) ? $result : (string)$val;
|
||||
};
|
||||
|
||||
@@ -79,12 +102,8 @@ try {
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($invoices)) {
|
||||
error_log("INVOICES LIST: No invoices found for role: $role, tenant_id: $tenantId");
|
||||
}
|
||||
|
||||
json_success($invoices);
|
||||
json_paginated($invoices, $total, $pagination);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
json_error('SQL Error in Invoices List: ' . $e->getMessage(), 500);
|
||||
safe_error($e, 'invoices/index');
|
||||
}
|
||||
|
||||
@@ -148,6 +148,8 @@ if ($result['success']) {
|
||||
'jofotara_uuid' => $result['uuid'],
|
||||
], $decoded);
|
||||
|
||||
\App\Services\SmartNotifications::jofotaraSuccess($tenantId, $userId, $invoiceId, $result['uuid']);
|
||||
|
||||
json_success([
|
||||
'uuid' => $result['uuid'],
|
||||
'qr_code' => $qrBase64,
|
||||
@@ -158,5 +160,7 @@ if ($result['success']) {
|
||||
'error' => $result['error'] ?? 'Unknown',
|
||||
], $decoded);
|
||||
|
||||
\App\Services\SmartNotifications::jofotaraRejected($tenantId, $userId, $invoiceId, $result['error'] ?? 'خطأ غير محدد');
|
||||
|
||||
json_error('رُفضت الفاتورة من جوفتورة: ' . ($result['error'] ?? 'خطأ غير محدد'), 422);
|
||||
}
|
||||
|
||||
@@ -112,5 +112,5 @@ try {
|
||||
} catch (\Exception $e) {
|
||||
$db->rollBack();
|
||||
error_log("Invoice Update Error: " . $e->getMessage());
|
||||
json_error('فشل تحديث الفاتورة: ' . $e->getMessage(), 500);
|
||||
safe_error($e, 'invoices/update', 'فشل تحديث الفاتورة.');
|
||||
}
|
||||
|
||||
@@ -62,11 +62,12 @@ try {
|
||||
|
||||
foreach ([$tenantDir, $companyDir, $uploadDir] as $dir) {
|
||||
if (!is_dir($dir)) {
|
||||
if (!mkdir($dir, 0777, true)) {
|
||||
json_error('فشل في إنشاء مجلد التخزين: ' . $dir, 500);
|
||||
if (!mkdir($dir, 0755, true)) {
|
||||
error_log('Failed to create storage directory: ' . $dir);
|
||||
json_error('فشل في تجهيز مساحة التخزين', 500);
|
||||
exit;
|
||||
}
|
||||
chmod($dir, 0777);
|
||||
chmod($dir, 0755);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,6 +199,8 @@ try {
|
||||
|
||||
// --- INCREMENT QUOTA ---
|
||||
QuotaMiddleware::incrementInvoiceUsage($tenantId);
|
||||
\App\Services\SmartNotifications::checkQuotaWarning($tenantId);
|
||||
\App\Services\GamificationService::award($userId, $tenantId, 'invoice_uploaded');
|
||||
// -----------------------
|
||||
|
||||
json_success(['id' => $invoiceId], 'تم رفع الفاتورة واستخراج البيانات بنجاح');
|
||||
@@ -207,14 +210,14 @@ try {
|
||||
if (isset($db) && $db->inTransaction()) {
|
||||
$db->rollBack();
|
||||
}
|
||||
error_log("Database Error: " . $e->getMessage());
|
||||
json_error('حدث خطأ في قاعدة البيانات: ' . $e->getMessage(), 500);
|
||||
error_log("Database Error [upload]: " . $e->getMessage() . " | File: " . $e->getFile() . ":" . $e->getLine());
|
||||
json_error('حدث خطأ أثناء حفظ بيانات الفاتورة. يرجى المحاولة مرة أخرى.', 500);
|
||||
exit;
|
||||
} catch (\Throwable $e) {
|
||||
if (isset($db) && $db->inTransaction()) {
|
||||
$db->rollBack();
|
||||
}
|
||||
error_log("Critical Error: " . $e->getMessage() . " on line " . $e->getLine());
|
||||
json_error('خطأ برمجي حرج: ' . $e->getMessage() . ' في السطر ' . $e->getLine(), 500);
|
||||
error_log("Critical Error [upload]: " . $e->getMessage() . " | File: " . $e->getFile() . ":" . $e->getLine());
|
||||
json_error('حدث خطأ غير متوقع. يرجى المحاولة مرة أخرى أو التواصل مع الدعم الفني.', 500);
|
||||
exit;
|
||||
}
|
||||
Reference in New Issue
Block a user