Update: 2026-05-12 01:29:57
This commit is contained in:
@@ -96,6 +96,10 @@ class AI
|
|||||||
$textResponse = $result['candidates'][0]['content']['parts'][0]['text'] ?? null;
|
$textResponse = $result['candidates'][0]['content']['parts'][0]['text'] ?? null;
|
||||||
|
|
||||||
if (!$textResponse) return null;
|
if (!$textResponse) return null;
|
||||||
|
|
||||||
|
// --- ADDED FOR DEBUGGING ---
|
||||||
|
@file_put_contents(STORAGE_PATH . '/logs/worker.log', "[" . date('Y-m-d H:i:s') . "] [AI_RAW_RESPONSE]\n" . $textResponse . "\n", FILE_APPEND);
|
||||||
|
// ---------------------------
|
||||||
|
|
||||||
$data = json_decode($textResponse, true);
|
$data = json_decode($textResponse, true);
|
||||||
if (isset($data['error']) && $data['error'] === 'invalid_invoice') {
|
if (isset($data['error']) && $data['error'] === 'invalid_invoice') {
|
||||||
|
|||||||
@@ -102,21 +102,26 @@ class InvoiceProcessor
|
|||||||
supplier_tin, supplier_name, supplier_address,
|
supplier_tin, supplier_name, supplier_address,
|
||||||
buyer_tin, buyer_name, buyer_national_id,
|
buyer_tin, buyer_name, buyer_national_id,
|
||||||
subtotal, tax_amount, discount_total, grand_total, currency_code,
|
subtotal, tax_amount, discount_total, grand_total, currency_code,
|
||||||
|
ai_provider,
|
||||||
created_at
|
created_at
|
||||||
) VALUES (
|
) VALUES (
|
||||||
?, ?, ?, ?, ?, 'extracted',
|
?, ?, ?, ?, ?, 'extracted',
|
||||||
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
|
||||||
?, ?, ?, ?, ?,
|
?, ?, ?, ?, ?,
|
||||||
|
?,
|
||||||
NOW()
|
NOW()
|
||||||
)
|
)
|
||||||
");
|
");
|
||||||
|
|
||||||
|
$rawJsonSnippet = substr(json_encode($extracted, JSON_UNESCAPED_UNICODE), 0, 500);
|
||||||
|
|
||||||
$stmt->execute([
|
$stmt->execute([
|
||||||
$invoiceId, $tenantId, $companyId, $userId, $imagePath,
|
$invoiceId, $tenantId, $companyId, $userId, $imagePath,
|
||||||
$invoiceNum, $validDate, $extracted['invoice_type'] ?? 'cash', $extracted['invoice_category'] ?? 'simplified',
|
$invoiceNum, $validDate, $extracted['invoice_type'] ?? 'cash', $extracted['invoice_category'] ?? 'simplified',
|
||||||
Encryption::encrypt($supplierTin), Encryption::encrypt($extracted['supplier']['name'] ?? ''), Encryption::encrypt($extracted['supplier']['address'] ?? ''),
|
Encryption::encrypt($supplierTin), Encryption::encrypt($extracted['supplier']['name'] ?? ''), Encryption::encrypt($extracted['supplier']['address'] ?? ''),
|
||||||
Encryption::encrypt($extracted['buyer']['tin'] ?? ''), Encryption::encrypt($extracted['buyer']['name'] ?? ''), Encryption::encrypt($extracted['buyer']['national_id'] ?? ''),
|
Encryption::encrypt($extracted['buyer']['tin'] ?? ''), Encryption::encrypt($extracted['buyer']['name'] ?? ''), Encryption::encrypt($extracted['buyer']['national_id'] ?? ''),
|
||||||
$extracted['subtotal'] ?? 0, $extracted['tax_amount'] ?? 0, $extracted['discount_total'] ?? 0, $extracted['grand_total'] ?? 0, $extracted['currency_code'] ?? 'JOD'
|
$extracted['subtotal'] ?? 0, $extracted['tax_amount'] ?? 0, $extracted['discount_total'] ?? 0, $extracted['grand_total'] ?? 0, $extracted['currency_code'] ?? 'JOD',
|
||||||
|
$rawJsonSnippet
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Save invoice line items
|
// Save invoice line items
|
||||||
|
|||||||
@@ -13,51 +13,11 @@ class ImageProcessingService {
|
|||||||
/// 3. Increases contrast
|
/// 3. Increases contrast
|
||||||
/// 4. Saves as high-quality JPEG
|
/// 4. Saves as high-quality JPEG
|
||||||
static Future<File?> processInvoiceImage(File originalImage) async {
|
static Future<File?> processInvoiceImage(File originalImage) async {
|
||||||
try {
|
// The web app uploads the original file and OCR works 100%.
|
||||||
AppLogger.print('Started processing image: ${originalImage.path}');
|
// Processing/compressing it here strips EXIF/quality and causes OCR to fail.
|
||||||
|
// So we just return the original image intact.
|
||||||
// Step 1: Initial compression using flutter_image_compress (Native, fast)
|
AppLogger.print('Skipping image processing to preserve quality for AI: ${originalImage.path}');
|
||||||
final dir = await getTemporaryDirectory();
|
return originalImage;
|
||||||
final compressedPath = path.join(
|
|
||||||
dir.path, 'compressed_${DateTime.now().millisecondsSinceEpoch}.jpg');
|
|
||||||
|
|
||||||
final XFile? compressedXFile =
|
|
||||||
await FlutterImageCompress.compressAndGetFile(
|
|
||||||
originalImage.path,
|
|
||||||
compressedPath,
|
|
||||||
quality: 85,
|
|
||||||
minWidth: 1920,
|
|
||||||
minHeight: 1080,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (compressedXFile == null) {
|
|
||||||
AppLogger.error('Failed to compress image initially', null);
|
|
||||||
return originalImage; // Fallback to original
|
|
||||||
}
|
|
||||||
|
|
||||||
File compressedFile = File(compressedXFile.path);
|
|
||||||
|
|
||||||
// Step 2: Fine-tuning filters (Grayscale/Contrast) via 'image' package
|
|
||||||
// This helps OCR models detect text edges more accurately
|
|
||||||
final bytes = await compressedFile.readAsBytes();
|
|
||||||
final filteredBytes = await compute(_applyFilters, bytes);
|
|
||||||
|
|
||||||
if (filteredBytes != null) {
|
|
||||||
final filteredPath = path.join(
|
|
||||||
dir.path, 'filtered_${DateTime.now().millisecondsSinceEpoch}.jpg');
|
|
||||||
final filteredFile = File(filteredPath);
|
|
||||||
await filteredFile.writeAsBytes(filteredBytes);
|
|
||||||
|
|
||||||
AppLogger.print('Finished processing image with filters: ${filteredFile.path}');
|
|
||||||
return filteredFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppLogger.print('Finished processing image (no filters): ${compressedFile.path}');
|
|
||||||
return compressedFile;
|
|
||||||
} catch (e, stack) {
|
|
||||||
AppLogger.error('Error processing image', e, stack);
|
|
||||||
return originalImage; // Fallback
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs in an isolate to prevent UI freezing
|
/// Runs in an isolate to prevent UI freezing
|
||||||
|
|||||||
@@ -17,8 +17,14 @@ class InvoiceDetailController extends GetxController {
|
|||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
if (Get.arguments != null) {
|
final args = Get.arguments;
|
||||||
invoiceId = Get.arguments['id'];
|
if (args != null) {
|
||||||
|
if (args is Map) {
|
||||||
|
invoiceId = args['id']?.toString();
|
||||||
|
} else if (args is String) {
|
||||||
|
invoiceId = args;
|
||||||
|
}
|
||||||
|
|
||||||
if (invoiceId != null) {
|
if (invoiceId != null) {
|
||||||
fetchInvoiceDetails();
|
fetchInvoiceDetails();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user