Update: 2026-05-04 02:29:13
This commit is contained in:
72
app/core/JoFotara.php
Normal file
72
app/core/JoFotara.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
/**
|
||||
* JoFotara (Jordan E-Invoicing) Integration Core
|
||||
* Handles UBL 2.1 XML Generation, Cryptography, and API Communication
|
||||
*/
|
||||
class JoFotara
|
||||
{
|
||||
private string $clientId;
|
||||
private string $secretKey;
|
||||
private string $environment;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Load credentials from DB or Environment
|
||||
$this->clientId = env('JOFOTARA_CLIENT_ID', '');
|
||||
$this->secretKey = env('JOFOTARA_SECRET', '');
|
||||
$this->environment = env('JOFOTARA_ENV', 'sandbox'); // sandbox or production
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Generate UBL 2.1 XML for an invoice
|
||||
*/
|
||||
public function generateXML(array $invoiceData): string
|
||||
{
|
||||
// To be implemented: Full XML DOM Document generation based on UBL 2.1 schema
|
||||
// This will map $invoiceData (Supplier, Buyer, Lines, Taxes) to exact XML nodes.
|
||||
return "<Invoice><dummy>This will be full UBL 2.1 XML</dummy></Invoice>";
|
||||
}
|
||||
|
||||
/**
|
||||
* 2. Generate Base64 TLV QR Code (required by Jordan Tax Authority)
|
||||
* Tag 1: Seller Name
|
||||
* Tag 2: Tax Number
|
||||
* Tag 3: Timestamp
|
||||
* Tag 4: Invoice Total
|
||||
* Tag 5: VAT Total
|
||||
*/
|
||||
public function generateQRCode(array $invoiceData): string
|
||||
{
|
||||
$sellerName = $invoiceData['supplier_name'] ?? '';
|
||||
$taxNumber = $invoiceData['supplier_tin'] ?? '';
|
||||
$timestamp = date('Y-m-d\TH:i:s\Z', strtotime($invoiceData['invoice_date'] ?? 'now'));
|
||||
$total = number_format($invoiceData['grand_total'] ?? 0, 3, '.', '');
|
||||
$vat = number_format($invoiceData['tax_amount'] ?? 0, 3, '.', '');
|
||||
|
||||
$tlv = $this->toTLV(1, $sellerName) .
|
||||
$this->toTLV(2, $taxNumber) .
|
||||
$this->toTLV(3, $timestamp) .
|
||||
$this->toTLV(4, $total) .
|
||||
$this->toTLV(5, $vat);
|
||||
|
||||
return base64_encode($tlv);
|
||||
}
|
||||
|
||||
private function toTLV(int $tag, string $value): string
|
||||
{
|
||||
return chr($tag) . chr(strlen($value)) . $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 3. Submit Invoice to JoFotara API
|
||||
*/
|
||||
public function submitInvoice(string $xmlContent): array
|
||||
{
|
||||
// To be implemented: cURL request to JoFotara Core API
|
||||
// Requires ECDSA signing of the XML before submission
|
||||
return ['success' => true, 'uuid' => 'dummy-jofotara-id'];
|
||||
}
|
||||
}
|
||||
142
public/shell.php
142
public/shell.php
@@ -133,7 +133,94 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- (Other pages simplified for brevity in this tool call) -->
|
||||
<!-- Dashboard -->
|
||||
<div x-show="page === 'dashboard'">
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div class="p-8 bg-surface border border-gray-800 rounded-2xl shadow-xl relative overflow-hidden group">
|
||||
<div class="absolute top-0 right-0 p-4 opacity-10 group-hover:opacity-20 transition-opacity"><span class="text-4xl">📄</span></div>
|
||||
<p class="text-gray-500 text-xs uppercase font-bold tracking-widest">إجمالي الفواتير</p>
|
||||
<p class="text-4xl font-bold mt-3" x-text="stats.total || 0"></p>
|
||||
</div>
|
||||
<div class="p-8 bg-surface border border-gray-800 rounded-2xl shadow-xl relative overflow-hidden group">
|
||||
<div class="absolute top-0 right-0 p-4 opacity-10 group-hover:opacity-20 transition-opacity"><span class="text-4xl text-yellow-500">⏳</span></div>
|
||||
<p class="text-gray-500 text-xs uppercase font-bold tracking-widest">قيد المعالجة</p>
|
||||
<p class="text-4xl font-bold mt-3 text-yellow-500" x-text="stats.pending || 0"></p>
|
||||
</div>
|
||||
<div class="p-8 bg-surface border border-gray-800 rounded-2xl shadow-xl relative overflow-hidden group">
|
||||
<div class="absolute top-0 right-0 p-4 opacity-10 group-hover:opacity-20 transition-opacity"><span class="text-4xl text-emerald-500">✅</span></div>
|
||||
<p class="text-gray-500 text-xs uppercase font-bold tracking-widest">تم الاعتماد</p>
|
||||
<p class="text-4xl font-bold mt-3 text-emerald-500" x-text="stats.approved || 0"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Companies -->
|
||||
<div x-show="page === 'companies'">
|
||||
<div class="bg-surface border border-gray-800 rounded-2xl overflow-hidden shadow-2xl">
|
||||
<table class="w-full text-right divide-y divide-gray-800">
|
||||
<thead class="bg-gray-900/50">
|
||||
<tr>
|
||||
<th class="p-5 text-xs font-bold text-gray-500 uppercase">الشركة</th>
|
||||
<th class="p-5 text-xs font-bold text-gray-500 uppercase">الأرقام الرسمية</th>
|
||||
<th class="p-5 text-xs font-bold text-gray-500 uppercase">العنوان</th>
|
||||
<th class="p-5 text-xs font-bold text-gray-500 uppercase">المكتب</th>
|
||||
<th class="p-5 text-xs font-bold text-gray-500 uppercase">إجراءات</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-800">
|
||||
<tr x-show="companies.length === 0"><td colspan="5" class="p-12 text-center text-gray-600">لا توجد شركات بعد</td></tr>
|
||||
<template x-for="c in companies" :key="c.id">
|
||||
<tr class="hover:bg-white/[0.01] transition-colors group">
|
||||
<td class="p-5"><p class="font-bold text-emerald-500" x-text="c.name"></p></td>
|
||||
<td class="p-5">
|
||||
<p class="text-xs text-gray-400">TIN: <span class="font-mono" x-text="c.tax_identification_number"></span></p>
|
||||
<p class="text-xs text-gray-400">CRN: <span class="font-mono" x-text="c.commercial_registration_number"></span></p>
|
||||
</td>
|
||||
<td class="p-5 text-sm text-gray-500" x-text="c.address"></td>
|
||||
<td class="p-5 text-xs text-gray-500" x-text="c.tenant_name || '-'"></td>
|
||||
<td class="p-5">
|
||||
<button x-show="user?.role === 'super_admin' || user?.role === 'admin'" @click="confirmDeleteCompany(c)" class="text-gray-500 hover:text-red-500 p-2 rounded-lg hover:bg-red-500/10 transition">🗑️</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Users List -->
|
||||
<div x-show="page === 'users'">
|
||||
<div class="bg-surface border border-gray-800 rounded-2xl overflow-hidden shadow-2xl">
|
||||
<table class="w-full text-right divide-y divide-gray-800">
|
||||
<thead class="bg-gray-900/50">
|
||||
<tr>
|
||||
<th class="p-5 text-xs font-bold text-gray-500 uppercase">المستخدم</th>
|
||||
<th class="p-5 text-xs font-bold text-gray-500 uppercase">المكتب</th>
|
||||
<th class="p-5 text-xs font-bold text-gray-500 uppercase">الدور</th>
|
||||
<th class="p-5 text-xs font-bold text-gray-500 uppercase">الحالة</th>
|
||||
<th class="p-5 text-xs font-bold text-gray-500 uppercase">إجراءات</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-800">
|
||||
<tr x-show="users.length === 0"><td colspan="5" class="p-12 text-center text-gray-600">لا يوجد مستخدمون بعد</td></tr>
|
||||
<template x-for="u in users" :key="u.id">
|
||||
<tr class="hover:bg-white/[0.01] transition-colors group">
|
||||
<td class="p-5">
|
||||
<p class="font-bold text-emerald-500" x-text="u.name"></p>
|
||||
<p class="text-xs text-gray-500" x-text="u.email"></p>
|
||||
</td>
|
||||
<td class="p-5 text-sm text-gray-400" x-text="u.tenant_name || '-'"></td>
|
||||
<td class="p-5"><span class="px-2 py-1 bg-gray-800 rounded text-[10px] font-bold uppercase" x-text="u.role"></span></td>
|
||||
<td class="p-5"><span class="w-2 h-2 rounded-full inline-block" :class="u.is_active?'bg-emerald-500 shadow-[0_0_8px_rgba(16,185,129,0.5)]':'bg-red-500'"></span></td>
|
||||
<td class="p-5 flex gap-2">
|
||||
<button x-show="u.id !== user.id" @click="confirmDeleteUser(u)" class="text-gray-500 hover:text-red-500 p-2 rounded-lg hover:bg-red-500/10 transition">🗑️</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -247,7 +334,58 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- (Other modals like Upload, Add User, etc.) -->
|
||||
<!-- Upload Invoice Modal -->
|
||||
<div x-show="showUploadModal" x-cloak class="fixed inset-0 bg-black/90 backdrop-blur-md flex items-center justify-center p-4 z-50">
|
||||
<div class="bg-surface border border-gray-800 w-full max-w-lg p-10 rounded-3xl shadow-2xl glass" @click.away="showUploadModal = false">
|
||||
<h3 class="text-2xl font-bold mb-2">رفع فواتير جديدة 📤</h3>
|
||||
<p class="text-gray-500 text-sm mb-8">سيقوم النظام باستخراج البيانات آلياً باستخدام الذكاء الاصطناعي</p>
|
||||
<form @submit.prevent="uploadInvoice" class="space-y-6">
|
||||
<div>
|
||||
<label class="block text-xs font-bold text-gray-500 uppercase mb-2 tracking-widest">اختر الشركة</label>
|
||||
<select x-model="uploadData.company_id" class="w-full bg-gray-950 border border-gray-800 p-4 rounded-xl outline-none focus:ring-2 focus:ring-emerald-500/20 transition-all" 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 class="border-2 border-dashed border-gray-800 rounded-2xl p-12 text-center hover:border-emerald-500/50 transition-colors relative cursor-pointer group">
|
||||
<input type="file" @change="handleFile" class="absolute inset-0 opacity-0 cursor-pointer" required>
|
||||
<div class="space-y-2">
|
||||
<span class="text-4xl block group-hover:scale-110 transition-transform">📄</span>
|
||||
<p class="text-sm font-bold text-gray-400" x-text="selectedFile ? selectedFile.name : 'اسحب الملف هنا أو اضغط للاختيار'"></p>
|
||||
<p class="text-[10px] text-gray-600 uppercase">PDF, PNG, JPG (Max 5MB)</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pt-4 flex gap-4">
|
||||
<button type="submit" class="flex-1 bg-emerald-600 hover:bg-emerald-500 py-4 rounded-xl font-bold shadow-lg shadow-emerald-900/20 transition-all active:scale-95 disabled:opacity-50" :disabled="isUploading">
|
||||
<span x-show="!isUploading">بدء المعالجة الذكية</span>
|
||||
<span x-show="isUploading" class="flex items-center justify-center gap-2">جارِ التحليل...</span>
|
||||
</button>
|
||||
<button type="button" @click="showUploadModal = false" class="px-8 py-4 border border-gray-800 rounded-xl hover:bg-gray-800 transition-all">إلغاء</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add User Modal -->
|
||||
<div x-show="showAddModal" x-cloak class="fixed inset-0 bg-black/80 backdrop-blur-sm flex items-center justify-center p-4 z-50">
|
||||
<div class="bg-surface border border-gray-800 w-full max-w-md p-8 rounded-3xl shadow-2xl glass" @click.away="showAddModal = false">
|
||||
<h3 class="text-xl font-bold mb-6">إضافة مستخدم جديد 👥</h3>
|
||||
<form @submit.prevent="createUser" class="space-y-4">
|
||||
<div><input type="text" x-model="newUser.name" placeholder="الاسم الكامل" class="w-full bg-gray-950 border border-gray-800 p-3 rounded-xl outline-none" required></div>
|
||||
<div><input type="email" x-model="newUser.email" placeholder="البريد الإلكتروني" class="w-full bg-gray-950 border border-gray-800 p-3 rounded-xl outline-none" required></div>
|
||||
<div><input type="password" x-model="newUser.password" placeholder="كلمة المرور" class="w-full bg-gray-950 border border-gray-800 p-3 rounded-xl outline-none" required></div>
|
||||
<div>
|
||||
<select x-model="newUser.role" class="w-full bg-gray-950 border border-gray-800 p-3 rounded-xl outline-none">
|
||||
<option value="employee">موظف</option><option value="accountant">محاسب</option><option value="admin">مدير مكتب</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="pt-4 flex gap-3">
|
||||
<button type="submit" class="flex-1 bg-emerald-600 hover:bg-emerald-500 py-3.5 rounded-xl font-bold transition">حفظ</button>
|
||||
<button type="button" @click="showAddModal = false" class="px-6 py-3.5 border border-gray-800 rounded-xl hover:bg-gray-800 transition">إلغاء</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
Reference in New Issue
Block a user