From 47652b4d95dc6361487f8985bf1d0eee98bc26e6 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Mon, 4 May 2026 16:06:15 +0300 Subject: [PATCH] Update: 2026-05-04 16:06:15 --- app/core/AI.php | 69 ++++++++++++------------------ app/modules_app/invoices/file.php | 2 +- app/modules_app/invoices/index.php | 13 +++--- app/modules_app/invoices/view.php | 18 ++++---- public/shell.php | 52 +++++++++++++--------- 5 files changed, 77 insertions(+), 77 deletions(-) diff --git a/app/core/AI.php b/app/core/AI.php index 7ef11a8..37d98fb 100644 --- a/app/core/AI.php +++ b/app/core/AI.php @@ -4,6 +4,7 @@ namespace App\Core; /** * Gemini AI Integration for Invoice Extraction + * Optimized for Jordan UBL 2.1 Compliance */ class AI { @@ -20,54 +21,44 @@ class AI return null; } - $prompt = "أنت نظام متخصص في استخلاص بيانات الفواتير التجارية. مهمتك واحدة فقط: استخراج البيانات من الفاتورة المرفقة بدقة تامة. + $prompt = "أنت نظام خبير في استخراج البيانات الضريبية للفواتير في الأردن. +يجب أن تلتزم بالقواعد التالية بصرامة حسابية مطلقة. -## قواعد صارمة: -**اللغة:** -- إذا كانت الفاتورة بالعربية: أبقِ جميع أسماء السلع والعناوين بالعربية بدون ترجمة -- إذا كانت بالإنجليزية: أبقِها بالإنجليزية بدون ترجمة -- الأرقام دائماً بالأرقام اللاتينية (0-9) بغض النظر عن لغة الفاتورة -- المبالغ بـ 3 أرقام عشرية (مثال: 15.000 وليس 15) +### 1. القواعد الحسابية الصارمة (إلزامي): +يجب أن توازن الفاتورة حسابياً قبل إرجاع النتيجة. المعادلة الأساسية هي: +`Grand Total = Subtotal - Discount Total + Tax Amount` -**الدقة:** -- لا تخترع أي بيانات غير موجودة في الفاتورة — أعد null إذا لم تجد المعلومة -- تحقق رياضياً: subtotal = مجموع (quantity × unit_price - discount) لكل سطر -- تحقق: grand_total = subtotal - discount_total + tax_amount -- إذا وجدت تناقضاً بين الأرقام في الفاتورة، سجِّله في حقل \"validation_warnings\" +**مثال للتوضيح:** +إذا كانت البنود هي: +1. صنف أ: 12.000 (1 × 12.000) +2. صنف ب: 175.000 (35 × 5.000) +فإن المجموع الفرعي (Subtotal) هو 187.000. +إذا كانت الضريبة 16% على صنف أ فقط، فإن Tax Amount = 1.920. +إذاً الإجمالي (Grand Total) يجب أن يكون 188.920. -**الضريبة:** -- في الأردن: ضريبة المبيعات العامة (GST) = 16% للسلع العامة -- سلع معفاة من الضريبة: المواد الغذائية الأساسية، الأدوية، الكتب، بعض المعدات الطبية -- سلع بضريبة مخفضة: قد تكون 4% أو 8% — استخرج النسبة الفعلية من الفاتورة -- لكل سطر: حدد tax_rate الفعلي (0 للمعفاة، وإلا النسبة المئوية كعدد عشري مثل 0.16) +**تنبيه:** إذا وجدت في الفاتورة رقماً مكتوباً كإجمالي (Grand Total) ولكنه لا يطابق مجموع البنود والضريبة، قم بتصحيح البيانات المستخرجة للبنود لتتوافق مع المجموع الصحيح أو اتبع المجموع الرياضي الأدق. لا تخرج إجمالياً (مثلاً 15.000) بينما مجموع البنود (311.000). -## البيانات المطلوبة (JSON فقط، بدون أي نص إضافي): +### 2. قواعد استخراج البيانات: +- **اللغة:** لا تترجم. إذا كان الوصف 'صنف أول' أبقه 'صنف أول'. +- **الأرقام:** استخدم الأرقام اللاتينية (0-9). +- **الدقة:** استخدم 3 أرقام عشرية للمبالغ (مثال: 0.500). +- **الضريبة:** في الأردن، الضريبة العامة هي 16% (0.160). حدد لكل بند النسبة الفعلية. -```json +### 3. هيكل البيانات (JSON فقط): { - \"invoice_number\": \"string | null\", - \"invoice_date\": \"YYYY-MM-DD | null\", + \"invoice_number\": \"string\", + \"invoice_date\": \"YYYY-MM-DD\", \"invoice_type\": \"cash | credit\", - \"payment_method_code\": \"013 | 010 | 001\", - \"supplier\": { - \"name\": \"string | null\", - \"tin\": \"string | null\", - \"address\": \"string | null\" - }, - \"buyer\": { - \"name\": \"string | null\", - \"tin\": \"string | null\", - \"national_id\": \"string | null\" - }, + \"invoice_category\": \"simplified | standard\", + \"supplier\": { \"name\": \"string\", \"tin\": \"string\", \"address\": \"string\" }, + \"buyer\": { \"name\": \"string\", \"tin\": \"string\", \"national_id\": \"string\" }, \"lines\": [ { \"line_number\": 1, \"description\": \"string\", \"quantity\": 0.000, \"unit_price\": 0.000, - \"discount\": 0.000, - \"tax_rate\": 0.16, - \"tax_exempt_reason\": \"string | null\", + \"tax_rate\": 0.160, \"line_total\": 0.000 } ], @@ -75,14 +66,10 @@ class AI \"discount_total\": 0.000, \"tax_amount\": 0.000, \"grand_total\": 0.000, - \"currency_code\": \"JOD\", - \"math_verified\": true, - \"validation_warnings\": [], - \"ai_confidence\": 0.95 + \"currency_code\": \"JOD\" } -``` -أعد JSON فقط بدون أي شرح أو مقدمة أو علامات Markdown."; +أعد كود JSON فقط بدون أي علامات Markdown أو نصوص إضافية."; $payload = [ "contents" => [ diff --git a/app/modules_app/invoices/file.php b/app/modules_app/invoices/file.php index 7edd8c5..249cda7 100644 --- a/app/modules_app/invoices/file.php +++ b/app/modules_app/invoices/file.php @@ -32,7 +32,7 @@ if (preg_match('/Bearer\s(\S+)/', $authHeader, $matches)) { if (!$token) outputErrorImage('Forbidden: No token'); -$decoded = \App\Core\JWT::decode($token); +$decoded = \App\Core\JWT::decode($token, env('JWT_SECRET', '')); if (!$decoded) outputErrorImage('Forbidden: Invalid token'); $db = Database::getInstance(); diff --git a/app/modules_app/invoices/index.php b/app/modules_app/invoices/index.php index 8b23fd1..e3c645b 100644 --- a/app/modules_app/invoices/index.php +++ b/app/modules_app/invoices/index.php @@ -59,17 +59,18 @@ try { $invoices = $stmt->fetchAll(); - // 3. Decrypt sensitive fields for display + // 3. Decrypt sensitive fields for display (Robustly) + $decrypt = fn($val) => Encryption::decrypt($val ?? '') ?: ($val ?? '-'); foreach ($invoices as &$inv) { - $inv['supplier_name'] = Encryption::decrypt($inv['supplier_name'] ?? '') ?: ($inv['supplier_name'] ?? '-'); - $inv['supplier_tin'] = Encryption::decrypt($inv['supplier_tin'] ?? '') ?: ($inv['supplier_tin'] ?? '-'); - $inv['buyer_name'] = Encryption::decrypt($inv['buyer_name'] ?? '') ?: ($inv['buyer_name'] ?? '-'); + $inv['supplier_name'] = $decrypt($inv['supplier_name']); + $inv['supplier_tin'] = $decrypt($inv['supplier_tin']); + $inv['buyer_name'] = $decrypt($inv['buyer_name']); if (!empty($inv['company_name'])) { - $inv['company_name'] = Encryption::decrypt($inv['company_name']) ?: $inv['company_name']; + $inv['company_name'] = $decrypt($inv['company_name']); } if (!empty($inv['tenant_name'])) { - $inv['tenant_name'] = Encryption::decrypt($inv['tenant_name']) ?: $inv['tenant_name']; + $inv['tenant_name'] = $decrypt($inv['tenant_name']); } } diff --git a/app/modules_app/invoices/view.php b/app/modules_app/invoices/view.php index 2ae9341..28987a8 100644 --- a/app/modules_app/invoices/view.php +++ b/app/modules_app/invoices/view.php @@ -43,16 +43,18 @@ try { $stmtLines->execute([$id]); $invoice['items'] = $stmtLines->fetchAll(); - // 5. Decrypt Fields - $invoice['supplier_tin'] = Encryption::decrypt($invoice['supplier_tin'] ?? '') ?: $invoice['supplier_tin']; - $invoice['supplier_name'] = Encryption::decrypt($invoice['supplier_name'] ?? '') ?: $invoice['supplier_name']; - $invoice['supplier_address'] = Encryption::decrypt($invoice['supplier_address'] ?? '') ?: $invoice['supplier_address']; - $invoice['buyer_tin'] = Encryption::decrypt($invoice['buyer_tin'] ?? '') ?: $invoice['buyer_tin']; - $invoice['buyer_name'] = Encryption::decrypt($invoice['buyer_name'] ?? '') ?: $invoice['buyer_name']; - $invoice['buyer_national_id'] = Encryption::decrypt($invoice['buyer_national_id'] ?? '') ?: $invoice['buyer_national_id']; + // 5. Decrypt Fields (Robustly) + $decrypt = fn($val) => Encryption::decrypt($val ?? '') ?: $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'] = Encryption::decrypt($invoice['company_name']) ?: $invoice['company_name']; + $invoice['company_name'] = $decrypt($invoice['company_name']); } // 6. Generate Public URL for File (Assuming storage is symlinked or served) diff --git a/public/shell.php b/public/shell.php index b0131ab..4ed021e 100644 --- a/public/shell.php +++ b/public/shell.php @@ -328,8 +328,11 @@