Update: 2026-05-04 20:10:28

This commit is contained in:
Hamza-Ayed
2026-05-04 20:10:28 +03:00
parent 3ea64d59ce
commit 8d499716ce
5 changed files with 182 additions and 5 deletions

View File

@@ -362,6 +362,110 @@
</form>
</div>
</div>
<!-- Upload Invoice Modal -->
<div x-show="showUploadModal" x-cloak class="fixed inset-0 bg-black/90 flex items-center justify-center p-6 z-[120]">
<div class="bg-surface border border-gray-800 w-full max-w-lg p-10 rounded-[40px] shadow-2xl glass-elevated">
<h3 class="text-2xl font-bold mb-8">رفع فاتورة جديدة</h3>
<form @submit.prevent="uploadInvoice" class="space-y-6">
<div>
<label class="block text-xs font-bold text-gray-500 uppercase mb-2">اختر الشركة</label>
<select x-model="uploadData.company_id" class="w-full bg-gray-950 border border-gray-800 p-4 rounded-2xl outline-none" required>
<option value="">-- يرجى الاختيار --</option>
<template x-for="c in companies" :key="c.id">
<option :value="c.id" x-text="c.name"></option>
</template>
</select>
</div>
<div>
<label class="block text-xs font-bold text-gray-500 uppercase mb-2">ملف الفاتورة (صورة أو PDF)</label>
<input type="file" @change="selectedFile = $event.target.files[0]" class="w-full bg-gray-950 border border-gray-800 p-4 rounded-2xl outline-none" required>
</div>
<div class="flex gap-4 pt-4">
<button type="submit" class="flex-1 bg-emerald-600 hover:bg-emerald-500 py-4 rounded-2xl font-bold transition-all btn-glow" :disabled="isUploading">
<span x-show="!isUploading">📤 رفع وتحليل</span>
<span x-show="isUploading"> جاري التحليل...</span>
</button>
<button type="button" @click="showUploadModal = false" class="px-8 py-4 border border-gray-800 rounded-2xl">إلغاء</button>
</div>
</form>
</div>
</div>
<!-- View Invoice Modal -->
<div x-show="showViewModal" x-cloak class="fixed inset-0 bg-black/90 backdrop-blur-md flex items-center justify-center p-6 z-[110]">
<div class="bg-surface border border-gray-800 w-full max-w-6xl h-[90vh] p-8 rounded-[40px] shadow-2xl glass-elevated flex flex-col md:flex-row gap-8" @click.away="showViewModal = false">
<!-- Left: Document Preview -->
<div class="flex-1 bg-gray-950 rounded-3xl overflow-hidden border border-gray-800 relative">
<template x-if="currentInvoice?.file_url">
<iframe :src="currentInvoice.file_url" class="w-full h-full border-0"></iframe>
</template>
<div x-show="!currentInvoice?.file_url" class="absolute inset-0 flex items-center justify-center text-gray-600">لا يوجد ملف مرفق</div>
</div>
<!-- Right: Invoice Data -->
<div class="w-full md:w-96 flex flex-col h-full overflow-y-auto pr-4 scrollbar-hide">
<div class="flex justify-between items-start mb-6">
<h3 class="text-2xl font-bold text-emerald-400">تفاصيل الفاتورة</h3>
<button @click="showViewModal = false" class="text-gray-500 hover:text-white text-3xl transition"></button>
</div>
<div class="space-y-4 flex-1">
<div class="p-4 bg-gray-900/50 rounded-2xl border border-gray-800">
<p class="text-[10px] text-gray-500 uppercase tracking-widest mb-1">المورد</p>
<p class="font-bold text-lg text-emerald-300" x-text="currentInvoice?.supplier_name || 'غير متوفر'"></p>
<p class="text-xs text-gray-400 font-mono mt-1" x-text="'TIN: ' + (currentInvoice?.supplier_tin || '-')"></p>
</div>
<div class="p-4 bg-gray-900/50 rounded-2xl border border-gray-800">
<p class="text-[10px] text-gray-500 uppercase tracking-widest mb-1">الرقم والتاريخ</p>
<p class="font-bold" x-text="currentInvoice?.invoice_number || '-'"></p>
<p class="text-sm text-gray-400 font-mono" x-text="currentInvoice?.invoice_date || '-'"></p>
</div>
<div class="p-4 bg-gray-900/50 rounded-2xl border border-gray-800">
<p class="text-[10px] text-gray-500 uppercase tracking-widest mb-1">المجموع الكلي</p>
<p class="text-3xl font-bold font-mono text-emerald-400" x-text="parseFloat(currentInvoice?.grand_total || 0).toLocaleString() + ' JOD'"></p>
<p class="text-xs text-yellow-500 mt-1 font-mono" x-text="'الضريبة: ' + parseFloat(currentInvoice?.tax_amount || 0).toLocaleString()"></p>
</div>
<!-- Items -->
<div x-show="currentInvoice?.items?.length > 0" class="mt-4 border border-gray-800 rounded-2xl overflow-hidden">
<table class="w-full text-xs text-right">
<thead class="bg-gray-900">
<tr><th class="p-2">البند</th><th class="p-2">الكمية</th><th class="p-2">السعر</th></tr>
</thead>
<tbody class="divide-y divide-gray-800 bg-gray-900/30">
<template x-for="item in currentInvoice?.items" :key="item.id">
<tr>
<td class="p-2" x-text="item.description"></td>
<td class="p-2 font-mono text-gray-400" x-text="item.quantity"></td>
<td class="p-2 font-mono text-emerald-400" x-text="item.unit_price"></td>
</tr>
</template>
</tbody>
</table>
</div>
<!-- QR Code -->
<div x-show="currentInvoice?.jofotara?.qr_image_uri || currentInvoice?.qr_code" class="mt-6 p-4 bg-white rounded-2xl flex justify-center items-center">
<img :src="currentInvoice?.jofotara?.qr_image_uri || currentInvoice?.qr_code" class="w-40 h-40 object-contain" alt="QR Code">
</div>
</div>
<!-- Actions -->
<div class="mt-6 pt-6 border-t border-gray-800 space-y-3">
<button x-show="currentInvoice?.status === 'extracted'" @click="approveInvoice" class="w-full bg-emerald-600 hover:bg-emerald-500 py-4 rounded-2xl font-bold shadow-lg shadow-emerald-900/50 transition-all active:scale-95 btn-glow" :disabled="isBusy">
<span x-show="!isBusy">✔️ اعتماد وإرسال للفوترة</span>
<span x-show="isBusy"> جاري الاعتماد...</span>
</button>
<button x-show="currentInvoice?.status === 'extracted'" class="w-full bg-red-900/30 text-red-400 hover:bg-red-900/50 py-3 rounded-2xl font-bold transition-all"> رفض</button>
<div x-show="currentInvoice?.status === 'approved'" class="w-full bg-emerald-900/30 text-emerald-400 py-3 rounded-2xl font-bold text-center border border-emerald-900/50">
معتمدة مسبقاً
</div>
</div>
</div>
</div>
</div>
</div>
<script>
@@ -451,7 +555,80 @@
async viewInvoice(id) {
this.currentInvoice = await this.apiRequest('v1/invoices/view&id=' + id);
if (this.currentInvoice) this.showViewModal = true;
if (this.currentInvoice) {
this.showViewModal = true;
// Fallback QR code generation if missing from DB but we have invoice data
if (!this.currentInvoice.jofotara?.qr_image_uri && !this.currentInvoice.qr_code && this.currentInvoice.status === 'approved') {
try {
const qr = new QRious({
value: 'Invoice: ' + this.currentInvoice.invoice_number + '\nSeller: ' + this.currentInvoice.supplier_name + '\nTotal: ' + this.currentInvoice.grand_total,
size: 250
});
this.currentInvoice.qr_code = qr.toDataURL();
} catch (e) {}
}
}
},
selectedFile: null,
isUploading: false,
async uploadInvoice() {
if (!this.selectedFile) return alert('الرجاء اختيار ملف');
if (!this.uploadData.company_id) return alert('الرجاء اختيار الشركة');
this.isUploading = true;
const formData = new FormData();
formData.append('company_id', this.uploadData.company_id);
formData.append('invoice', this.selectedFile);
try {
const res = await fetch('/index.php?route=v1/invoices/upload', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + this.token() },
body: formData
});
const json = await res.json();
this.isUploading = false;
if (json.success) {
this.showUploadModal = false;
this.selectedFile = null;
await this.loadAll();
this.viewInvoice(json.data.id);
} else {
this.showError(json.message);
}
} catch (e) {
this.isUploading = false;
this.showError('فشل الاتصال بالخادم أثناء الرفع');
}
},
async approveInvoice() {
if (!this.currentInvoice || this.isBusy) return;
this.isBusy = true;
try {
const res = await fetch('/index.php?route=v1/invoices/approve', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + this.token(), 'Content-Type': 'application/json' },
body: JSON.stringify({ id: this.currentInvoice.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('حدث خطأ أثناء الاعتماد');
}
},
logout() { localStorage.clear(); window.location.href = '/login.php'; }