129 lines
4.7 KiB
PHP
129 lines
4.7 KiB
PHP
<?php
|
||
|
||
namespace App\Core;
|
||
|
||
/**
|
||
* Gemini AI Integration for Invoice Extraction
|
||
*/
|
||
class AI
|
||
{
|
||
private static string $baseUrl = "https://generativelanguage.googleapis.com/v1beta/models/gemini-flash-lite-latest:generateContent";
|
||
|
||
/**
|
||
* Extract Data from Invoice Image or PDF (Base64)
|
||
*/
|
||
public static function extractInvoiceData(string $base64Data, string $mimeType): ?array
|
||
{
|
||
$apiKey = env('GEMINI_API_KEY');
|
||
if (!$apiKey) {
|
||
error_log('AI Error: GEMINI_API_KEY is missing');
|
||
return null;
|
||
}
|
||
|
||
$prompt = "أنت نظام متخصص في استخلاص بيانات الفواتير التجارية. مهمتك واحدة فقط: استخراج البيانات من الفاتورة المرفقة بدقة تامة.
|
||
|
||
## قواعد صارمة:
|
||
**اللغة:**
|
||
- إذا كانت الفاتورة بالعربية: أبقِ جميع أسماء السلع والعناوين بالعربية بدون ترجمة
|
||
- إذا كانت بالإنجليزية: أبقِها بالإنجليزية بدون ترجمة
|
||
- الأرقام دائماً بالأرقام اللاتينية (0-9) بغض النظر عن لغة الفاتورة
|
||
- المبالغ بـ 3 أرقام عشرية (مثال: 15.000 وليس 15)
|
||
|
||
**الدقة:**
|
||
- لا تخترع أي بيانات غير موجودة في الفاتورة — أعد null إذا لم تجد المعلومة
|
||
- تحقق رياضياً: subtotal = مجموع (quantity × unit_price - discount) لكل سطر
|
||
- تحقق: grand_total = subtotal - discount_total + tax_amount
|
||
- إذا وجدت تناقضاً بين الأرقام في الفاتورة، سجِّله في حقل \"validation_warnings\"
|
||
|
||
**الضريبة:**
|
||
- في الأردن: ضريبة المبيعات العامة (GST) = 16% للسلع العامة
|
||
- سلع معفاة من الضريبة: المواد الغذائية الأساسية، الأدوية، الكتب، بعض المعدات الطبية
|
||
- سلع بضريبة مخفضة: قد تكون 4% أو 8% — استخرج النسبة الفعلية من الفاتورة
|
||
- لكل سطر: حدد tax_rate الفعلي (0 للمعفاة، وإلا النسبة المئوية كعدد عشري مثل 0.16)
|
||
|
||
## البيانات المطلوبة (JSON فقط، بدون أي نص إضافي):
|
||
|
||
```json
|
||
{
|
||
\"invoice_number\": \"string | null\",
|
||
\"invoice_date\": \"YYYY-MM-DD | null\",
|
||
\"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\"
|
||
},
|
||
\"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\",
|
||
\"line_total\": 0.000
|
||
}
|
||
],
|
||
\"subtotal\": 0.000,
|
||
\"discount_total\": 0.000,
|
||
\"tax_amount\": 0.000,
|
||
\"grand_total\": 0.000,
|
||
\"currency_code\": \"JOD\",
|
||
\"math_verified\": true,
|
||
\"validation_warnings\": [],
|
||
\"ai_confidence\": 0.95
|
||
}
|
||
```
|
||
|
||
أعد JSON فقط بدون أي شرح أو مقدمة أو علامات Markdown.";
|
||
|
||
$payload = [
|
||
"contents" => [
|
||
[
|
||
"parts" => [
|
||
["text" => $prompt],
|
||
[
|
||
"inline_data" => [
|
||
"mime_type" => $mimeType,
|
||
"data" => $base64Data
|
||
]
|
||
]
|
||
]
|
||
]
|
||
],
|
||
"generationConfig" => [
|
||
"response_mime_type" => "application/json"
|
||
]
|
||
];
|
||
|
||
$ch = curl_init(self::$baseUrl . "?key=" . $apiKey);
|
||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||
curl_setopt($ch, CURLOPT_POST, true);
|
||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
|
||
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
|
||
|
||
$response = curl_exec($ch);
|
||
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
curl_close($ch);
|
||
|
||
if ($httpCode !== 200) {
|
||
error_log("AI Error: Gemini API returned code $httpCode. Response: " . $response);
|
||
return null;
|
||
}
|
||
|
||
$result = json_decode($response, true);
|
||
$textResponse = $result['candidates'][0]['content']['parts'][0]['text'] ?? null;
|
||
|
||
if (!$textResponse) return null;
|
||
|
||
return json_decode($textResponse, true);
|
||
}
|
||
}
|