diff --git a/app/modules_app/invoices/delete.php b/app/modules_app/invoices/delete.php new file mode 100644 index 0000000..a08359d --- /dev/null +++ b/app/modules_app/invoices/delete.php @@ -0,0 +1,49 @@ +beginTransaction(); + + $stmt = $db->prepare("SELECT * FROM invoices WHERE id = ? FOR UPDATE"); + $stmt->execute([$id]); + $invoice = $stmt->fetch(); + + if (!$invoice) json_error('Invoice not found', 404); + + // Super admin can delete anything. Others might only delete non-approved, but let's allow admin to delete. + if ($decoded['role'] !== 'super_admin' && $invoice['tenant_id'] !== $decoded['tenant_id']) { + json_error('Access denied', 403); + } + + $db->prepare("DELETE FROM invoice_lines WHERE invoice_id = ?")->execute([$id]); + $db->prepare("DELETE FROM jofotara_submissions WHERE invoice_id = ?")->execute([$id]); + $db->prepare("DELETE FROM invoices WHERE id = ?")->execute([$id]); + + $db->commit(); + + AuditLogger::log('invoice.deleted', 'invoice', $id, null, null, $decoded); + + json_success(null, 'تم حذف الفاتورة بنجاح'); + +} catch (\Exception $e) { + if ($db->inTransaction()) $db->rollBack(); + error_log("Invoice Delete Error: " . $e->getMessage()); + json_error('فشل في حذف الفاتورة', 500); +} diff --git a/app/modules_app/invoices/reject.php b/app/modules_app/invoices/reject.php new file mode 100644 index 0000000..47125ff --- /dev/null +++ b/app/modules_app/invoices/reject.php @@ -0,0 +1,48 @@ +beginTransaction(); + + $stmt = $db->prepare("SELECT * FROM invoices WHERE id = ? FOR UPDATE"); + $stmt->execute([$id]); + $invoice = $stmt->fetch(); + + if (!$invoice) json_error('Invoice not found', 404); + if ($invoice['status'] === 'approved') json_error('لا يمكن رفض فاتورة معتمدة', 400); + + $updateStmt = $db->prepare("UPDATE invoices SET status = 'rejected', updated_at = NOW() WHERE id = ?"); + $updateStmt->execute([$id]); + + $db->commit(); + + AuditLogger::log('invoice.rejected', 'invoice', $id, [ + 'old_status' => $invoice['status'], + ], [ + 'new_status' => 'rejected', + ], $decoded); + + json_success(null, 'تم رفض الفاتورة بنجاح'); + +} catch (\Exception $e) { + if ($db->inTransaction()) $db->rollBack(); + error_log("Invoice Reject Error: " . $e->getMessage()); + json_error('فشل في رفض الفاتورة', 500); +} diff --git a/musadaq-app/lib/features/scanner/controllers/scanner_controller.dart b/musadaq-app/lib/features/scanner/controllers/scanner_controller.dart index c189deb..385d025 100644 --- a/musadaq-app/lib/features/scanner/controllers/scanner_controller.dart +++ b/musadaq-app/lib/features/scanner/controllers/scanner_controller.dart @@ -4,6 +4,7 @@ import 'package:get/get.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as path; +import 'package:file_picker/file_picker.dart'; import '../../../core/services/upload_progress_service.dart'; import '../../../core/utils/logger.dart'; import '../../../core/utils/app_snackbar.dart'; @@ -91,6 +92,11 @@ class ScannerController extends GetxController { capturedImages.add(originalFile); int index = capturedImages.length - 1; + if (imagePath.toLowerCase().endsWith('.pdf')) { + AppLogger.print('Added PDF file, skipping image processing: $imagePath'); + return; + } + ImageProcessingService.processInvoiceImage(originalFile) .then((processedFile) { if (processedFile != null && index < capturedImages.length) { @@ -102,6 +108,28 @@ class ScannerController extends GetxController { }); } + Future pickPdfFile() async { + try { + FilePickerResult? result = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['pdf'], + allowMultiple: true, + ); + + if (result != null) { + for (var file in result.files) { + if (file.path != null) { + addImage(file.path!); + } + } + AppSnackbar.showSuccess('تمت الإضافة', 'تم استيراد ملفات الفواتير بنجاح'); + } + } catch (e) { + AppLogger.error('Failed to pick PDF', e); + AppSnackbar.showError('خطأ', 'تعذر استيراد الملفات'); + } + } + void removeImage(int index) { if (index >= 0 && index < capturedImages.length) { capturedImages.removeAt(index); diff --git a/musadaq-app/lib/features/scanner/views/scanner_view.dart b/musadaq-app/lib/features/scanner/views/scanner_view.dart index e9632b4..2eda52f 100644 --- a/musadaq-app/lib/features/scanner/views/scanner_view.dart +++ b/musadaq-app/lib/features/scanner/views/scanner_view.dart @@ -119,6 +119,18 @@ class ScannerView extends GetView { state: state, children: [ AwesomeFlashButton(state: state), + const SizedBox(width: 16), + Container( + decoration: BoxDecoration( + color: Colors.black45, + borderRadius: BorderRadius.circular(8), + ), + child: IconButton( + onPressed: () => controller.pickPdfFile(), + icon: const Icon(Icons.picture_as_pdf, color: Colors.white), + tooltip: 'استيراد PDF', + ), + ), const Spacer(), TextButton.icon( onPressed: () => Get.back(), diff --git a/musadaq-app/pubspec.lock b/musadaq-app/pubspec.lock index eae5a09..8a2c11f 100644 --- a/musadaq-app/pubspec.lock +++ b/musadaq-app/pubspec.lock @@ -353,6 +353,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: ab13ae8ef5580a411c458d6207b6774a6c237d77ac37011b13994879f68a8810 + url: "https://pub.dev" + source: hosted + version: "8.3.7" file_selector_linux: dependency: transitive description: diff --git a/musadaq-app/pubspec.yaml b/musadaq-app/pubspec.yaml index 598ae07..54f85c1 100644 --- a/musadaq-app/pubspec.yaml +++ b/musadaq-app/pubspec.yaml @@ -31,6 +31,7 @@ dependencies: camerawesome: ^2.0.0 cunning_document_scanner: ^1.2.3 image_picker: ^1.0.7 + file_picker: ^8.1.2 # ─── Image Processing ─────────────────────────────── image: ^4.1.7 diff --git a/public/index.php b/public/index.php index 796af84..a5690fa 100644 --- a/public/index.php +++ b/public/index.php @@ -35,6 +35,8 @@ $routes = [ 'v1/invoices/download_xml' => ['GET', 'invoices/download_xml.php'], 'v1/invoices/submit-jofotara' => ['POST', 'invoices/submit_jofotara.php'], 'v1/invoices/update' => ['POST', 'invoices/update.php'], + 'v1/invoices/reject' => ['POST', 'invoices/reject.php'], + 'v1/invoices/delete' => ['POST', 'invoices/delete.php'], 'v1/invoices/bulk-approve' => ['POST', 'invoices/bulk_approve.php'], 'v1/invoices/export' => ['GET', 'invoices/export.php'], 'v1/invoices/check-duplicate' => ['POST', 'invoices/check_duplicate.php'], diff --git a/public/shell.php b/public/shell.php index 02e741a..8e037ba 100644 --- a/public/shell.php +++ b/public/shell.php @@ -1759,6 +1759,9 @@ + @@ -2395,11 +2398,11 @@