diff --git a/app/modules_app/dashboard/stats.php b/app/modules_app/dashboard/stats.php index 5d2b125..97c4dc8 100644 --- a/app/modules_app/dashboard/stats.php +++ b/app/modules_app/dashboard/stats.php @@ -31,7 +31,7 @@ try { $stmt->execute($params); $total = $stmt->fetchColumn(); - $stmt = $db->prepare("SELECT COUNT(*) FROM invoices $where AND status = 'pending'"); + $stmt = $db->prepare("SELECT COUNT(*) FROM invoices $where AND status = 'extracted'"); $stmt->execute($params); $pending = $stmt->fetchColumn(); diff --git a/app/modules_app/invoices/approve.php b/app/modules_app/invoices/approve.php index d443820..f08634d 100644 --- a/app/modules_app/invoices/approve.php +++ b/app/modules_app/invoices/approve.php @@ -40,32 +40,39 @@ try { $invoice['items'] = $stmtLines->fetchAll(); // 3. Decrypt Company Keys for JoFotara - $clientId = \App\Core\Encryption::decrypt($invoice['jofotara_client_id_encrypted']); - $secretKey = \App\Core\Encryption::decrypt($invoice['jofotara_secret_key_encrypted']); + $clientId = \App\Core\Encryption::decrypt($invoice['jofotara_client_id_encrypted'] ?? ''); + $secretKey = \App\Core\Encryption::decrypt($invoice['jofotara_secret_key_encrypted'] ?? ''); - if (!$clientId || !$secretKey) { - throw new \Exception("JoFotara credentials missing for company: " . $invoice['company_name']); + $jofotara = new JoFotara(); + $apiResponse = ['success' => false]; + $xmlContent = null; + + // 4. Try JoFotara Submission if credentials exist + if ($clientId && $secretKey) { + $companyData = [ + 'name' => $invoice['company_name'], + 'tax_identification_number' => $invoice['company_tin'], + 'address' => $invoice['company_address'] + ]; + + // Decrypt Buyer Info for XML + $invoice['buyer_name'] = \App\Core\Encryption::decrypt($invoice['buyer_name'] ?? '') ?: ($invoice['buyer_name'] ?? ''); + $invoice['buyer_tin'] = \App\Core\Encryption::decrypt($invoice['buyer_tin'] ?? '') ?: ($invoice['buyer_tin'] ?? ''); + + $xmlContent = $jofotara->generateXML($invoice, $companyData); + $apiResponse = $jofotara->submitInvoice($xmlContent, $clientId, $secretKey); } - // Decrypt Buyer Info - $invoice['buyer_name'] = \App\Core\Encryption::decrypt($invoice['buyer_name']) ?: ''; - $invoice['buyer_tin'] = \App\Core\Encryption::decrypt($invoice['buyer_tin']) ?: ''; + // 5. Fallback: Generate Local QR if API failed or no credentials + $qrCode = $apiResponse['qrCode'] ?? $jofotara->generateQRCode([ + 'supplier_name' => $invoice['company_name'], + 'supplier_tin' => $invoice['company_tin'], + 'invoice_date' => $invoice['invoice_date'], + 'grand_total' => $invoice['grand_total'], + 'tax_amount' => $invoice['tax_amount'] + ]); - // 4. Initialize JoFotara Service - $jofotara = new JoFotara(); - - // 5. Generate UBL 2.1 XML - $companyData = [ - 'name' => $invoice['company_name'], - 'tax_identification_number' => $invoice['company_tin'], - 'address' => $invoice['company_address'] - ]; - $xmlContent = $jofotara->generateXML($invoice, $companyData); - - // 6. Submit to JoFotara API - $apiResponse = $jofotara->submitInvoice($xmlContent, $clientId, $secretKey); - - // 7. Record Submission (Audit Log) + // 6. Record Submission (Audit Log) $submissionId = \App\Core\Database::generateUuid(); $stmtSub = $db->prepare(" INSERT INTO jofotara_submissions @@ -74,41 +81,38 @@ try { VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW()) "); - $status = $apiResponse['success'] ? 'accepted' : 'rejected'; + $status = $apiResponse['success'] ? 'accepted' : 'error'; $stmtSub->execute([ $submissionId, $id, $invoice['company_id'], $invoice['tenant_id'], $xmlContent, - hash('sha256', $xmlContent), + $xmlContent ? hash('sha256', $xmlContent) : null, $apiResponse['uuid'] ?? null, - $apiResponse['qrCode'] ?? null, + $qrCode, $apiResponse['_http_code'] ?? '0', - json_encode($apiResponse['raw'] ?? []), + json_encode($apiResponse['raw'] ?? ['info' => 'Local approval / No credentials']), $status ]); - if (!$apiResponse['success']) { - throw new \Exception("JoFotara Rejection: " . ($apiResponse['error'] ?? 'Unknown Error')); - } - - // 8. Update Invoice + // 7. Update Invoice $updateStmt = $db->prepare(" UPDATE invoices SET status = 'approved', jofotara_uuid = ?, qr_code = ?, updated_at = NOW() WHERE id = ? "); - $updateStmt->execute([$apiResponse['uuid'], $apiResponse['qrCode'], $id]); + $updateStmt->execute([$apiResponse['uuid'] ?? null, $qrCode, $id]); $db->commit(); json_success([ - 'message' => 'Approved and submitted to JoFotara.', - 'uuid' => $apiResponse['uuid'], - 'qr_code' => $apiResponse['qrCode'] + 'message' => $apiResponse['success'] ? 'تم الاعتماد والإرسال إلى جوفوترة بنجاح' : 'تم الاعتماد محلياً (نظام جوفوترة غير متصل)', + 'uuid' => $apiResponse['uuid'] ?? null, + 'qr_code' => $qrCode, + 'is_api_success' => $apiResponse['success'] ]); } catch (\Exception $e) { - $db->rollBack(); + if ($db->inTransaction()) $db->rollBack(); error_log("JoFotara Approve Error: " . $e->getMessage()); - json_error('Failed to approve invoice: ' . $e->getMessage(), 500); + json_error('خطأ غير متوقع: ' . $e->getMessage(), 500); } diff --git a/app/modules_app/invoices/index.php b/app/modules_app/invoices/index.php index e3c645b..50958a9 100644 --- a/app/modules_app/invoices/index.php +++ b/app/modules_app/invoices/index.php @@ -60,18 +60,20 @@ try { $invoices = $stmt->fetchAll(); // 3. Decrypt sensitive fields for display (Robustly) - $decrypt = fn($val) => Encryption::decrypt($val ?? '') ?: ($val ?? '-'); + $dec = function($val) { + if (empty($val)) return ''; + $result = Encryption::decrypt((string)$val); + return ($result !== false && $result !== null && $result !== '') ? $result : (string)$val; + }; + foreach ($invoices as &$inv) { - $inv['supplier_name'] = $decrypt($inv['supplier_name']); - $inv['supplier_tin'] = $decrypt($inv['supplier_tin']); - $inv['buyer_name'] = $decrypt($inv['buyer_name']); + $inv['supplier_name'] = $dec($inv['supplier_name']); + $inv['supplier_tin'] = $dec($inv['supplier_tin']); + $inv['buyer_name'] = $dec($inv['buyer_name']); - if (!empty($inv['company_name'])) { - $inv['company_name'] = $decrypt($inv['company_name']); - } - if (!empty($inv['tenant_name'])) { - $inv['tenant_name'] = $decrypt($inv['tenant_name']); - } + // Note: company_name and tenant_name from JOIN are usually plaintext + // Only decrypt if you are absolutely sure they are encrypted in the source table. + // For companies.name, it's plaintext. } json_success($invoices); diff --git a/app/modules_app/invoices/view.php b/app/modules_app/invoices/view.php index cb84070..0ed2496 100644 --- a/app/modules_app/invoices/view.php +++ b/app/modules_app/invoices/view.php @@ -7,51 +7,63 @@ use App\Core\Database; use App\Core\Encryption; use App\Middleware\AuthMiddleware; -// 1. Auth Check $decoded = AuthMiddleware::check(); $db = Database::getInstance(); -// 2. Validate Request $id = $_GET['id'] ?? null; if (!$id) json_error('Invoice ID is required', 422); -// 3. Permission Check (Multi-Tenant Isolation) $tenantId = $decoded['tenant_id']; +$role = $decoded['role']; try { - $stmt = $db->prepare(" - SELECT i.*, c.name as company_name - FROM invoices i - JOIN companies c ON i.company_id = c.id - WHERE i.id = ? AND i.tenant_id = ? - "); - $stmt->execute([$id, $tenantId]); - $invoice = $stmt->fetch(); + // 1. Fetch Invoice (Super Admin sees all, others are tenant-scoped) + if ($role === 'super_admin') { + $stmt = $db->prepare(" + SELECT i.*, c.name as company_name + FROM invoices i + JOIN companies c ON i.company_id = c.id + WHERE i.id = ? + "); + $stmt->execute([$id]); + } else { + $stmt = $db->prepare(" + SELECT i.*, c.name as company_name + FROM invoices i + JOIN companies c ON i.company_id = c.id + WHERE i.id = ? AND i.tenant_id = ? + "); + $stmt->execute([$id, $tenantId]); + } + $invoice = $stmt->fetch(); if (!$invoice) json_error('Invoice not found or access denied', 404); - // 4. Fetch Line Items + // 2. Fetch Line Items $stmtLines = $db->prepare("SELECT * FROM invoice_lines WHERE invoice_id = ? ORDER BY line_number ASC"); $stmtLines->execute([$id]); $invoice['items'] = $stmtLines->fetchAll(); - // 5. Decrypt Fields (Robustly) - $decrypt = fn($val) => Encryption::decrypt($val ?? '') ?: $val; + // 3. Decrypt all encrypted fields — robust: if decryption fails, keep original value + $dec = function($val) { + if (empty($val)) return ''; + $result = \App\Core\Encryption::decrypt((string)$val); + return ($result !== false && $result !== null && $result !== '') ? $result : (string)$val; + }; - $invoice['supplier_tin'] = $decrypt($invoice['supplier_tin']); - $invoice['supplier_name'] = $decrypt($invoice['supplier_name']); - $invoice['supplier_address'] = $decrypt($invoice['supplier_address']); - $invoice['buyer_tin'] = $decrypt($invoice['buyer_tin']); - $invoice['buyer_name'] = $decrypt($invoice['buyer_name']); - $invoice['buyer_national_id'] = $decrypt($invoice['buyer_national_id']); - - if (!empty($invoice['company_name'])) { - $invoice['company_name'] = $decrypt($invoice['company_name']); - } + $invoice['supplier_tin'] = $dec($invoice['supplier_tin']); + $invoice['supplier_name'] = $dec($invoice['supplier_name']); + $invoice['supplier_address'] = $dec($invoice['supplier_address']); + $invoice['buyer_tin'] = $dec($invoice['buyer_tin']); + $invoice['buyer_name'] = $dec($invoice['buyer_name']); + $invoice['buyer_national_id'] = $dec($invoice['buyer_national_id']); - // 6. Fetch JoFotara Submission Data + // company_name is stored plaintext in the companies table — no decryption needed + // $invoice['company_name'] is already plaintext from the JOIN + + // 4. Fetch JoFotara Submission Data (latest accepted submission) $stmtSub = $db->prepare(" - SELECT jofotara_uuid, submitted_at, qr_code_raw, status as submission_status, response_body + SELECT jofotara_uuid, submitted_at, qr_code_raw, response_body FROM jofotara_submissions WHERE invoice_id = ? AND status = 'accepted' ORDER BY created_at DESC LIMIT 1 @@ -59,16 +71,21 @@ try { $stmtSub->execute([$id]); $submission = $stmtSub->fetch(); - $invoice['jofotara'] = $submission ? [ - 'uuid' => $submission['jofotara_uuid'], - 'submitted_at' => $submission['submitted_at'], - 'qr_image_uri' => $submission['qr_code_raw'] ? 'data:image/png;base64,' . $submission['qr_code_raw'] : null, - 'has_xml' => true - ] : null; + if ($submission) { + $invoice['jofotara'] = [ + 'uuid' => $submission['jofotara_uuid'], + 'submitted_at' => $submission['submitted_at'], + 'qr_image_uri' => $submission['qr_code_raw'] + ? 'data:image/png;base64,' . $submission['qr_code_raw'] + : null, + 'has_xml' => true, + ]; + } else { + $invoice['jofotara'] = null; + } - // 7. Generate Public URL for File - $token = Encryption::encrypt($invoice['original_file_path']); - $invoice['file_url'] = '/index.php?route=v1/invoices/file&file_token=' . urlencode($token); + // 5. Build the secure file URL using the invoice ID (file.php fetches path from DB) + $invoice['file_url'] = '/index.php?route=v1/invoices/file&id=' . urlencode($id); json_success($invoice); diff --git a/public/shell.php b/public/shell.php index 6edf3fa..79483ef 100644 --- a/public/shell.php +++ b/public/shell.php @@ -144,7 +144,7 @@
-

قيد المعالجة

+

بانتظار الاعتماد

@@ -165,11 +165,12 @@ الأرقام الرسمية العنوان المكتب + إحصائيات إجراءات - لا توجد شركات بعد + لا توجد شركات بعد