Update: 2026-05-08 00:26:39
This commit is contained in:
162
app/modules_app/invoices/submit_jofotara.php
Normal file
162
app/modules_app/invoices/submit_jofotara.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
/**
|
||||
* Submit Invoice to JoFotara (Jordan E-Invoicing)
|
||||
* POST /v1/invoices/submit-jofotara
|
||||
*
|
||||
* Generates UBL 2.1 XML, submits to JoFotara API, and records the result.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Core\Database;
|
||||
use App\Core\Encryption;
|
||||
use App\Core\Security;
|
||||
use App\Core\JoFotara;
|
||||
use App\Core\AuditLogger;
|
||||
use App\Middleware\AuthMiddleware;
|
||||
use App\Middleware\RoleMiddleware;
|
||||
|
||||
$decoded = AuthMiddleware::check();
|
||||
RoleMiddleware::require(['admin', 'super_admin', 'accountant']);
|
||||
|
||||
$tenantId = $decoded['tenant_id'];
|
||||
$userId = $decoded['user_id'];
|
||||
$role = $decoded['role'];
|
||||
|
||||
$data = Security::sanitize(input());
|
||||
$invoiceId = $data['invoice_id'] ?? null;
|
||||
|
||||
if (!$invoiceId) {
|
||||
json_error('معرّف الفاتورة مطلوب', 422);
|
||||
}
|
||||
|
||||
$db = Database::getInstance();
|
||||
|
||||
// 1. Fetch Invoice
|
||||
$query = $role === 'super_admin'
|
||||
? "SELECT i.*, c.name as company_name, c.tax_identification_number, c.jofotara_client_id, c.jofotara_secret_key, c.address as company_address
|
||||
FROM invoices i JOIN companies c ON i.company_id = c.id WHERE i.id = ?"
|
||||
: "SELECT i.*, c.name as company_name, c.tax_identification_number, c.jofotara_client_id, c.jofotara_secret_key, c.address as company_address
|
||||
FROM invoices i JOIN companies c ON i.company_id = c.id WHERE i.id = ? AND i.tenant_id = ?";
|
||||
|
||||
$params = $role === 'super_admin' ? [$invoiceId] : [$invoiceId, $tenantId];
|
||||
$stmt = $db->prepare($query);
|
||||
$stmt->execute($params);
|
||||
$invoice = $stmt->fetch();
|
||||
|
||||
if (!$invoice) {
|
||||
json_error('الفاتورة غير موجودة أو ليس لديك صلاحية', 404);
|
||||
}
|
||||
|
||||
if ($invoice['status'] !== 'approved') {
|
||||
json_error('يجب اعتماد الفاتورة أولاً قبل إرسالها لجوفتورة', 400);
|
||||
}
|
||||
|
||||
// 2. Check if already submitted
|
||||
$stmtCheck = $db->prepare("SELECT id FROM jofotara_submissions WHERE invoice_id = ? AND status = 'accepted'");
|
||||
$stmtCheck->execute([$invoiceId]);
|
||||
if ($stmtCheck->fetch()) {
|
||||
json_error('تم إرسال هذه الفاتورة لجوفتورة مسبقاً', 400);
|
||||
}
|
||||
|
||||
// 3. Verify JoFotara credentials
|
||||
$clientId = $invoice['jofotara_client_id'] ?? '';
|
||||
$secretKey = $invoice['jofotara_secret_key'] ?? '';
|
||||
|
||||
if (empty($clientId) || empty($secretKey)) {
|
||||
json_error('يجب ربط الشركة بمنظومة جوفتورة أولاً (Client ID + Secret Key)', 422);
|
||||
}
|
||||
|
||||
// 4. Decrypt sensitive fields for XML generation
|
||||
$dec = function($val) {
|
||||
if (empty($val)) return '';
|
||||
$result = Encryption::decrypt((string)$val);
|
||||
return ($result !== false && $result !== null) ? $result : (string)$val;
|
||||
};
|
||||
|
||||
// Prepare invoice data for XML
|
||||
$invoiceData = [
|
||||
'invoice_number' => $invoice['invoice_number'],
|
||||
'invoice_date' => $invoice['invoice_date'],
|
||||
'invoice_type' => $invoice['invoice_type'],
|
||||
'invoice_category' => $invoice['invoice_category'] ?? 'simplified',
|
||||
'ubl_type_code' => $invoice['ubl_type_code'] ?? '388',
|
||||
'payment_method_code' => $invoice['payment_method_code'] ?? '013',
|
||||
'buyer_name' => $dec($invoice['buyer_name']),
|
||||
'buyer_tin' => $dec($invoice['buyer_tin']),
|
||||
'buyer_national_id' => $dec($invoice['buyer_national_id']),
|
||||
'subtotal' => (float)$invoice['subtotal'],
|
||||
'tax_amount' => (float)$invoice['tax_amount'],
|
||||
'discount_total' => (float)$invoice['discount_total'],
|
||||
'grand_total' => (float)$invoice['grand_total'],
|
||||
];
|
||||
|
||||
// Fetch line items
|
||||
$stmtLines = $db->prepare("SELECT * FROM invoice_lines WHERE invoice_id = ? ORDER BY line_number ASC");
|
||||
$stmtLines->execute([$invoiceId]);
|
||||
$invoiceData['items'] = $stmtLines->fetchAll();
|
||||
|
||||
$companyData = [
|
||||
'name' => $invoice['company_name'],
|
||||
'tax_identification_number' => $invoice['tax_identification_number'],
|
||||
'address' => $invoice['company_address'] ?? '',
|
||||
];
|
||||
|
||||
// 5. Generate XML
|
||||
$jofotara = new JoFotara();
|
||||
$xml = $jofotara->generateXML($invoiceData, $companyData);
|
||||
|
||||
// 6. Submit to JoFotara API
|
||||
$result = $jofotara->submitInvoice($xml, $clientId, $secretKey);
|
||||
|
||||
// 7. Record submission
|
||||
$submissionId = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex(random_bytes(16)), 4));
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO jofotara_submissions (id, invoice_id, tenant_id, company_id, jofotara_uuid, xml_content, status, qr_code_raw, response_body, submitted_at, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NOW())
|
||||
");
|
||||
|
||||
$stmt->execute([
|
||||
$submissionId,
|
||||
$invoiceId,
|
||||
$tenantId,
|
||||
$invoice['company_id'],
|
||||
$result['uuid'] ?? null,
|
||||
$xml,
|
||||
$result['success'] ? 'accepted' : 'rejected',
|
||||
$result['qrCode'] ?? null,
|
||||
json_encode($result['raw'] ?? [], JSON_UNESCAPED_UNICODE),
|
||||
]);
|
||||
|
||||
// 8. Update invoice status if accepted
|
||||
if ($result['success']) {
|
||||
$db->prepare("UPDATE invoices SET status = 'submitted', jofotara_uuid = ? WHERE id = ?")
|
||||
->execute([$result['uuid'], $invoiceId]);
|
||||
|
||||
// Generate local QR code
|
||||
$qrBase64 = $jofotara->generateQRCode([
|
||||
'supplier_name' => $companyData['name'],
|
||||
'supplier_tin' => $companyData['tax_identification_number'],
|
||||
'invoice_date' => $invoiceData['invoice_date'],
|
||||
'grand_total' => $invoiceData['grand_total'],
|
||||
'tax_amount' => $invoiceData['tax_amount'],
|
||||
]);
|
||||
|
||||
$db->prepare("UPDATE invoices SET qr_code = ? WHERE id = ?")->execute([$qrBase64, $invoiceId]);
|
||||
|
||||
AuditLogger::log('invoice.submitted_jofotara', 'invoice', $invoiceId, null, [
|
||||
'jofotara_uuid' => $result['uuid'],
|
||||
], $decoded);
|
||||
|
||||
json_success([
|
||||
'uuid' => $result['uuid'],
|
||||
'qr_code' => $qrBase64,
|
||||
'status' => 'accepted',
|
||||
], 'تم إرسال الفاتورة لجوفتورة بنجاح');
|
||||
} else {
|
||||
AuditLogger::log('invoice.jofotara_rejected', 'invoice', $invoiceId, null, [
|
||||
'error' => $result['error'] ?? 'Unknown',
|
||||
], $decoded);
|
||||
|
||||
json_error('رُفضت الفاتورة من جوفتورة: ' . ($result['error'] ?? 'خطأ غير محدد'), 422);
|
||||
}
|
||||
Reference in New Issue
Block a user