Update: 2026-05-11 01:09:54
This commit is contained in:
49
app/modules_app/invoices/delete.php
Normal file
49
app/modules_app/invoices/delete.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Delete Invoice
|
||||||
|
*/
|
||||||
|
|
||||||
|
use App\Core\Database;
|
||||||
|
use App\Core\AuditLogger;
|
||||||
|
use App\Middleware\AuthMiddleware;
|
||||||
|
use App\Middleware\RoleMiddleware;
|
||||||
|
|
||||||
|
$decoded = RoleMiddleware::require(['super_admin', 'admin', 'accountant']);
|
||||||
|
$db = Database::getInstance();
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$id = $data['id'] ?? null;
|
||||||
|
|
||||||
|
if (!$id) {
|
||||||
|
json_error('Invoice ID is required', 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db->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);
|
||||||
|
}
|
||||||
48
app/modules_app/invoices/reject.php
Normal file
48
app/modules_app/invoices/reject.php
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Reject Invoice
|
||||||
|
*/
|
||||||
|
|
||||||
|
use App\Core\Database;
|
||||||
|
use App\Core\AuditLogger;
|
||||||
|
use App\Middleware\AuthMiddleware;
|
||||||
|
use App\Middleware\RoleMiddleware;
|
||||||
|
|
||||||
|
$decoded = RoleMiddleware::require(['super_admin', 'admin', 'accountant']);
|
||||||
|
$db = Database::getInstance();
|
||||||
|
|
||||||
|
$data = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$id = $data['id'] ?? null;
|
||||||
|
|
||||||
|
if (!$id) {
|
||||||
|
json_error('Invoice ID is required', 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$db->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);
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import 'package:get/get.dart';
|
|||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
import '../../../core/services/upload_progress_service.dart';
|
import '../../../core/services/upload_progress_service.dart';
|
||||||
import '../../../core/utils/logger.dart';
|
import '../../../core/utils/logger.dart';
|
||||||
import '../../../core/utils/app_snackbar.dart';
|
import '../../../core/utils/app_snackbar.dart';
|
||||||
@@ -91,6 +92,11 @@ class ScannerController extends GetxController {
|
|||||||
capturedImages.add(originalFile);
|
capturedImages.add(originalFile);
|
||||||
int index = capturedImages.length - 1;
|
int index = capturedImages.length - 1;
|
||||||
|
|
||||||
|
if (imagePath.toLowerCase().endsWith('.pdf')) {
|
||||||
|
AppLogger.print('Added PDF file, skipping image processing: $imagePath');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ImageProcessingService.processInvoiceImage(originalFile)
|
ImageProcessingService.processInvoiceImage(originalFile)
|
||||||
.then((processedFile) {
|
.then((processedFile) {
|
||||||
if (processedFile != null && index < capturedImages.length) {
|
if (processedFile != null && index < capturedImages.length) {
|
||||||
@@ -102,6 +108,28 @@ class ScannerController extends GetxController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> 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) {
|
void removeImage(int index) {
|
||||||
if (index >= 0 && index < capturedImages.length) {
|
if (index >= 0 && index < capturedImages.length) {
|
||||||
capturedImages.removeAt(index);
|
capturedImages.removeAt(index);
|
||||||
|
|||||||
@@ -119,6 +119,18 @@ class ScannerView extends GetView<ScannerController> {
|
|||||||
state: state,
|
state: state,
|
||||||
children: [
|
children: [
|
||||||
AwesomeFlashButton(state: state),
|
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(),
|
const Spacer(),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
onPressed: () => Get.back(),
|
onPressed: () => Get.back(),
|
||||||
|
|||||||
@@ -353,6 +353,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.0.1"
|
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:
|
file_selector_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ dependencies:
|
|||||||
camerawesome: ^2.0.0
|
camerawesome: ^2.0.0
|
||||||
cunning_document_scanner: ^1.2.3
|
cunning_document_scanner: ^1.2.3
|
||||||
image_picker: ^1.0.7
|
image_picker: ^1.0.7
|
||||||
|
file_picker: ^8.1.2
|
||||||
|
|
||||||
# ─── Image Processing ───────────────────────────────
|
# ─── Image Processing ───────────────────────────────
|
||||||
image: ^4.1.7
|
image: ^4.1.7
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ $routes = [
|
|||||||
'v1/invoices/download_xml' => ['GET', 'invoices/download_xml.php'],
|
'v1/invoices/download_xml' => ['GET', 'invoices/download_xml.php'],
|
||||||
'v1/invoices/submit-jofotara' => ['POST', 'invoices/submit_jofotara.php'],
|
'v1/invoices/submit-jofotara' => ['POST', 'invoices/submit_jofotara.php'],
|
||||||
'v1/invoices/update' => ['POST', 'invoices/update.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/bulk-approve' => ['POST', 'invoices/bulk_approve.php'],
|
||||||
'v1/invoices/export' => ['GET', 'invoices/export.php'],
|
'v1/invoices/export' => ['GET', 'invoices/export.php'],
|
||||||
'v1/invoices/check-duplicate' => ['POST', 'invoices/check_duplicate.php'],
|
'v1/invoices/check-duplicate' => ['POST', 'invoices/check_duplicate.php'],
|
||||||
|
|||||||
144
public/shell.php
144
public/shell.php
@@ -1759,6 +1759,9 @@
|
|||||||
<button @click="viewInvoice(inv.id)" class="btn-table-action btn-ta-navy">
|
<button @click="viewInvoice(inv.id)" class="btn-table-action btn-ta-navy">
|
||||||
👁️ عرض
|
👁️ عرض
|
||||||
</button>
|
</button>
|
||||||
|
<button @click="deleteInvoice(inv.id)" class="btn-table-action btn-ta-red" style="margin-right:4px;">
|
||||||
|
🗑️ حذف
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
@@ -2395,11 +2398,11 @@
|
|||||||
<!-- ── VIEW INVOICE MODAL ──────────────────────────── -->
|
<!-- ── VIEW INVOICE MODAL ──────────────────────────── -->
|
||||||
<div x-show="showViewModal" x-cloak class="modal-backdrop" @click.self="showViewModal = false">
|
<div x-show="showViewModal" x-cloak class="modal-backdrop" @click.self="showViewModal = false">
|
||||||
<div
|
<div
|
||||||
style="background:var(--bg-card); border-radius:22px; box-shadow:var(--shadow-lg); width:100%; max-width:900px; height:88vh; display:flex; overflow:hidden;">
|
style="background:var(--bg-card); border-radius:22px; box-shadow:var(--shadow-lg); width:100%; max-width:95vw; height:95vh; display:flex; overflow:hidden;">
|
||||||
|
|
||||||
<!-- Document Preview -->
|
<!-- Document Preview -->
|
||||||
<div
|
<div
|
||||||
style="flex:1; background:#F2F1FA; border-left:1px solid var(--border); position:relative; overflow:hidden;">
|
style="flex:3; background:#F2F1FA; border-left:1px solid var(--border); position:relative; overflow:hidden;">
|
||||||
<div style="position:absolute; top:12px; right:12px; z-index:10;">
|
<div style="position:absolute; top:12px; right:12px; z-index:10;">
|
||||||
<span class="badge badge-navy">معاينة الملف</span>
|
<span class="badge badge-navy">معاينة الملف</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -2415,15 +2418,20 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Invoice Data Panel -->
|
<!-- Invoice Data Panel -->
|
||||||
<div style="width:345px; flex-shrink:0; display:flex; flex-direction:column; overflow:hidden;">
|
<div style="width:25%; min-width:320px; max-width:450px; flex-shrink:0; display:flex; flex-direction:column; overflow:hidden;">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div
|
<div
|
||||||
style="padding:18px 20px 14px; border-bottom:1px solid var(--border); display:flex; align-items:center; justify-content:space-between;">
|
style="padding:18px 20px 14px; border-bottom:1px solid var(--border); display:flex; align-items:center; justify-content:space-between;">
|
||||||
<div>
|
<div>
|
||||||
<div style="font-size:16px; font-weight:700; color:var(--text-1);">تفاصيل الفاتورة</div>
|
<div style="font-size:16px; font-weight:700; color:var(--text-1); display:flex; gap:10px; align-items:center;">
|
||||||
|
تفاصيل الفاتورة
|
||||||
|
<button x-show="currentInvoice?.status === 'extracted' && !isEditingInvoice" @click="isEditingInvoice = true" class="btn-sm btn-ghost" style="font-size:11px; padding:2px 8px;">تعديل ✏️</button>
|
||||||
|
<button x-show="isEditingInvoice" @click="updateInvoice" class="btn-sm btn-teal" style="font-size:11px; padding:2px 8px;">حفظ 💾</button>
|
||||||
|
<button x-show="isEditingInvoice" @click="isEditingInvoice = false" class="btn-sm btn-ghost" style="font-size:11px; padding:2px 8px;">إلغاء ✕</button>
|
||||||
|
</div>
|
||||||
<span class="badge" style="margin-top:4px;"
|
<span class="badge" style="margin-top:4px;"
|
||||||
:class="currentInvoice?.status==='extracted' ? 'badge-blue' : (currentInvoice?.status==='approved' ? 'badge-teal' : 'badge-gray')"
|
:class="currentInvoice?.status==='extracted' ? 'badge-blue' : (currentInvoice?.status==='approved' ? 'badge-teal' : (currentInvoice?.status==='rejected' ? 'badge-red' : 'badge-gray'))"
|
||||||
x-text="currentInvoice?.status === 'approved' ? '✓ مدققة' : (currentInvoice?.status === 'extracted' ? 'جاهزة للتدقيق' : currentInvoice?.status)">
|
x-text="currentInvoice?.status === 'approved' ? '✓ مدققة' : (currentInvoice?.status === 'extracted' ? 'جاهزة للتدقيق' : (currentInvoice?.status === 'rejected' ? 'مرفوضة' : currentInvoice?.status))">
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button @click="showViewModal = false" class="modal-close-btn">✕</button>
|
<button @click="showViewModal = false" class="modal-close-btn">✕</button>
|
||||||
@@ -2455,26 +2463,35 @@
|
|||||||
|
|
||||||
<div class="invoice-field-card">
|
<div class="invoice-field-card">
|
||||||
<div class="invoice-field-label">المورد (البائع)</div>
|
<div class="invoice-field-label">المورد (البائع)</div>
|
||||||
<div class="invoice-field-value" x-text="currentInvoice?.supplier_name || 'غير متوفر'"></div>
|
<div x-show="!isEditingInvoice" class="invoice-field-value" x-text="currentInvoice?.supplier_name || 'غير متوفر'"></div>
|
||||||
<div style="font-size:12px; color:var(--text-3); font-family:'Outfit',sans-serif; margin-top:4px;"
|
<input x-show="isEditingInvoice" type="text" x-model="currentInvoice.supplier_name" class="form-input" style="padding:4px; font-size:13px; margin-bottom:4px;" placeholder="اسم المورد">
|
||||||
|
|
||||||
|
<div x-show="!isEditingInvoice" style="font-size:12px; color:var(--text-3); font-family:'Outfit',sans-serif; margin-top:4px;"
|
||||||
x-text="'TIN: ' + (currentInvoice?.supplier_tin || '—')"></div>
|
x-text="'TIN: ' + (currentInvoice?.supplier_tin || '—')"></div>
|
||||||
|
<input x-show="isEditingInvoice" type="text" x-model="currentInvoice.supplier_tin" class="form-input" placeholder="الرقم الضريبي" style="padding:4px; font-size:12px;">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="invoice-field-card">
|
<div class="invoice-field-card">
|
||||||
<div class="invoice-field-label">رقم الفاتورة والتاريخ</div>
|
<div class="invoice-field-label">رقم الفاتورة والتاريخ</div>
|
||||||
<div class="invoice-field-value" x-text="currentInvoice?.invoice_number || '—'"></div>
|
<div x-show="!isEditingInvoice" class="invoice-field-value" x-text="currentInvoice?.invoice_number || '—'"></div>
|
||||||
<div style="font-size:13px; color:var(--text-2); font-family:'Outfit',sans-serif; margin-top:3px;"
|
<input x-show="isEditingInvoice" type="text" x-model="currentInvoice.invoice_number" class="form-input" style="padding:4px; font-size:13px; margin-bottom:4px;" placeholder="رقم الفاتورة">
|
||||||
|
|
||||||
|
<div x-show="!isEditingInvoice" style="font-size:13px; color:var(--text-2); font-family:'Outfit',sans-serif; margin-top:3px;"
|
||||||
x-text="currentInvoice?.invoice_date || '—'"></div>
|
x-text="currentInvoice?.invoice_date || '—'"></div>
|
||||||
|
<input x-show="isEditingInvoice" type="date" x-model="currentInvoice.invoice_date" class="form-input" style="padding:4px; font-size:13px;">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="invoice-field-card"
|
<div class="invoice-field-card"
|
||||||
style="background:var(--green-subtle); border-color:rgba(5,150,105,0.2);">
|
style="background:var(--green-subtle); border-color:rgba(5,150,105,0.2);">
|
||||||
<div class="invoice-field-label" style="color:var(--green-mid);">المجموع الكلي</div>
|
<div class="invoice-field-label" style="color:var(--green-mid);">المجموع الكلي</div>
|
||||||
<div style="font-size:26px; font-weight:700; color:var(--green-mid); font-family:'El Messiri',sans-serif;"
|
<div x-show="!isEditingInvoice" style="font-size:26px; font-weight:700; color:var(--green-mid); font-family:'El Messiri',sans-serif;"
|
||||||
x-text="parseFloat(currentInvoice?.grand_total || 0).toLocaleString() + ' JOD'"></div>
|
x-text="parseFloat(currentInvoice?.grand_total || 0).toLocaleString() + ' JOD'"></div>
|
||||||
<div style="font-size:12px; color:var(--amber-mid); margin-top:4px; font-family:'Outfit',sans-serif;"
|
<input x-show="isEditingInvoice" type="number" step="0.01" x-model="currentInvoice.grand_total" class="form-input" style="padding:4px; font-size:16px; font-weight:bold; color:var(--green-mid); margin-bottom:4px;" placeholder="المجموع">
|
||||||
|
|
||||||
|
<div x-show="!isEditingInvoice" style="font-size:12px; color:var(--amber-mid); margin-top:4px; font-family:'Outfit',sans-serif;"
|
||||||
x-text="'الضريبة: ' + parseFloat(currentInvoice?.tax_amount || 0).toLocaleString() + ' JOD'">
|
x-text="'الضريبة: ' + parseFloat(currentInvoice?.tax_amount || 0).toLocaleString() + ' JOD'">
|
||||||
</div>
|
</div>
|
||||||
|
<input x-show="isEditingInvoice" type="number" step="0.01" x-model="currentInvoice.tax_amount" class="form-input" style="padding:4px; font-size:13px; color:var(--amber-mid);" placeholder="الضريبة">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Items Table -->
|
<!-- Items Table -->
|
||||||
@@ -2562,10 +2579,18 @@
|
|||||||
<span x-show="isBusy">⏳ جاري التدقيق...</span>
|
<span x-show="isBusy">⏳ جاري التدقيق...</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button x-show="currentInvoice?.status === 'extracted'"
|
<div style="display:flex; gap:8px;">
|
||||||
style="width:100%; background:var(--red-subtle); color:var(--red-mid); border:none; padding:10px; border-radius:11px; font-family:inherit; font-size:14px; font-weight:600; cursor:pointer; transition:all 0.18s;"
|
<button x-show="currentInvoice?.status === 'extracted' || currentInvoice?.status === 'rejected'"
|
||||||
onmouseover="this.style.background='#FECACA'"
|
@click="rejectInvoice(currentInvoice.id)"
|
||||||
onmouseout="this.style.background='var(--red-subtle)'">❌ رفض الفاتورة</button>
|
style="flex:1; background:var(--amber-subtle); color:var(--amber-mid); border:none; padding:10px; border-radius:11px; font-family:inherit; font-size:14px; font-weight:600; cursor:pointer; transition:all 0.18s;"
|
||||||
|
onmouseover="this.style.background='#FDE68A'"
|
||||||
|
onmouseout="this.style.background='var(--amber-subtle)'">❌ رفض</button>
|
||||||
|
|
||||||
|
<button @click="deleteInvoice(currentInvoice.id)"
|
||||||
|
style="flex:1; background:var(--red-subtle); color:var(--red-mid); border:none; padding:10px; border-radius:11px; font-family:inherit; font-size:14px; font-weight:600; cursor:pointer; transition:all 0.18s;"
|
||||||
|
onmouseover="this.style.background='#FECACA'"
|
||||||
|
onmouseout="this.style.background='var(--red-subtle)'">🗑️ حذف</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div x-show="currentInvoice?.status === 'approved'"
|
<div x-show="currentInvoice?.status === 'approved'"
|
||||||
style="width:100%; background:var(--green-subtle); color:var(--green-mid); border:1px solid rgba(5,150,105,0.2); padding:10px; border-radius:11px; font-size:14px; font-weight:700; text-align:center;">
|
style="width:100%; background:var(--green-subtle); color:var(--green-mid); border:1px solid rgba(5,150,105,0.2); padding:10px; border-radius:11px; font-size:14px; font-weight:700; text-align:center;">
|
||||||
@@ -2829,7 +2854,7 @@
|
|||||||
showExcelModal: false, showBatchUploadModal: false,
|
showExcelModal: false, showBatchUploadModal: false,
|
||||||
isUploadingBatch: false, batchProgress: { total: 0, current: 0 },
|
isUploadingBatch: false, batchProgress: { total: 0, current: 0 },
|
||||||
showAddTenantModal: false, showEditTenantModal: false, showTenantStatsModal: false,
|
showAddTenantModal: false, showEditTenantModal: false, showTenantStatsModal: false,
|
||||||
acknowledgedWarnings: false,
|
acknowledgedWarnings: false, isEditingInvoice: false,
|
||||||
isBusy: false, globalError: '',
|
isBusy: false, globalError: '',
|
||||||
|
|
||||||
newUser: { name: '', email: '', password: '', role: 'accountant', tenant_id: '' },
|
newUser: { name: '', email: '', password: '', role: 'accountant', tenant_id: '' },
|
||||||
@@ -2849,14 +2874,8 @@
|
|||||||
subtitle() { return { dashboard: 'نظرة شاملة على نشاط النظام', users: 'إدارة المستخدمين والصلاحيات', companies: 'إدارة الشركات والربط بالفوترة الحكومية', invoices: 'رفع ومعالجة الفواتير الضريبية', tenants: 'إدارة المكاتب المحاسبية المشتركة', subscription: 'تفاصيل باقتك الحالية واستهلاك الموارد' }[this.page] || ''; },
|
subtitle() { return { dashboard: 'نظرة شاملة على نشاط النظام', users: 'إدارة المستخدمين والصلاحيات', companies: 'إدارة الشركات والربط بالفوترة الحكومية', invoices: 'رفع ومعالجة الفواتير الضريبية', tenants: 'إدارة المكاتب المحاسبية المشتركة', subscription: 'تفاصيل باقتك الحالية واستهلاك الموارد' }[this.page] || ''; },
|
||||||
|
|
||||||
get filteredInvoices() {
|
get filteredInvoices() {
|
||||||
const now = new Date();
|
|
||||||
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
||||||
|
|
||||||
return this.invoices.filter(inv => {
|
return this.invoices.filter(inv => {
|
||||||
// 1. Time Filter (Current Month)
|
// Removed time filter to prevent invoices from disappearing after a month
|
||||||
const invDate = new Date(inv.invoice_date || inv.created_at);
|
|
||||||
if (invDate < startOfMonth) return false;
|
|
||||||
|
|
||||||
// 2. Company Filter
|
// 2. Company Filter
|
||||||
if (this.invoiceCompanyFilter && inv.company_id != this.invoiceCompanyFilter) return false;
|
if (this.invoiceCompanyFilter && inv.company_id != this.invoiceCompanyFilter) return false;
|
||||||
|
|
||||||
@@ -3155,6 +3174,83 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async rejectInvoice(id) {
|
||||||
|
if (this.isBusy) return;
|
||||||
|
if (!confirm('هل أنت متأكد من رفض هذه الفاتورة؟')) return;
|
||||||
|
this.isBusy = true;
|
||||||
|
try {
|
||||||
|
const res = await fetch('/index.php?route=v1/invoices/reject', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Authorization': 'Bearer ' + this.token(), 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ id: id })
|
||||||
|
});
|
||||||
|
const json = await res.json();
|
||||||
|
this.isBusy = false;
|
||||||
|
|
||||||
|
if (json.success) {
|
||||||
|
alert('تم رفض الفاتورة بنجاح!');
|
||||||
|
this.showViewModal = false;
|
||||||
|
this.loadAll();
|
||||||
|
} else {
|
||||||
|
this.showError(json.message);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.isBusy = false;
|
||||||
|
this.showError('حدث خطأ أثناء رفض الفاتورة');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteInvoice(id) {
|
||||||
|
if (this.isBusy) return;
|
||||||
|
if (!confirm('هل أنت متأكد من حذف هذه الفاتورة بشكل نهائي؟ لا يمكن التراجع عن هذا الإجراء.')) return;
|
||||||
|
this.isBusy = true;
|
||||||
|
try {
|
||||||
|
const res = await fetch('/index.php?route=v1/invoices/delete', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Authorization': 'Bearer ' + this.token(), 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ id: id })
|
||||||
|
});
|
||||||
|
const json = await res.json();
|
||||||
|
this.isBusy = false;
|
||||||
|
|
||||||
|
if (json.success) {
|
||||||
|
alert('تم حذف الفاتورة بنجاح!');
|
||||||
|
this.showViewModal = false;
|
||||||
|
this.loadAll();
|
||||||
|
} else {
|
||||||
|
this.showError(json.message);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.isBusy = false;
|
||||||
|
this.showError('حدث خطأ أثناء حذف الفاتورة');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateInvoice() {
|
||||||
|
if (!this.currentInvoice || this.isBusy) return;
|
||||||
|
this.isBusy = true;
|
||||||
|
try {
|
||||||
|
const res = await fetch('/index.php?route=v1/invoices/update', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Authorization': 'Bearer ' + this.token(), 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(this.currentInvoice)
|
||||||
|
});
|
||||||
|
const json = await res.json();
|
||||||
|
this.isBusy = false;
|
||||||
|
|
||||||
|
if (json.success) {
|
||||||
|
alert('تم تحديث بيانات الفاتورة بنجاح!');
|
||||||
|
this.isEditingInvoice = false;
|
||||||
|
this.loadAll();
|
||||||
|
} else {
|
||||||
|
this.showError(json.message);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.isBusy = false;
|
||||||
|
this.showError('حدث خطأ أثناء تحديث الفاتورة');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async uploadExcel() {
|
async uploadExcel() {
|
||||||
const fileInput = document.getElementById('excelFileInput');
|
const fileInput = document.getElementById('excelFileInput');
|
||||||
if (!fileInput.files[0]) return alert('الرجاء اختيار ملف اكسل');
|
if (!fileInput.files[0]) return alert('الرجاء اختيار ملف اكسل');
|
||||||
|
|||||||
Reference in New Issue
Block a user