Files
musadaq-saas/app/modules_app/invoices/submit_jofotara.php
2026-05-08 00:26:40 +03:00

163 lines
5.9 KiB
PHP

<?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);
}