167 lines
6.3 KiB
PHP
167 lines
6.3 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_encrypted, c.jofotara_secret_key_encrypted, 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_encrypted, c.jofotara_secret_key_encrypted, 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 = Encryption::decrypt($invoice['jofotara_client_id_encrypted'] ?? '') ?: '';
|
|
$secretKey = Encryption::decrypt($invoice['jofotara_secret_key_encrypted'] ?? '') ?: '';
|
|
|
|
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);
|
|
|
|
\App\Services\SmartNotifications::jofotaraSuccess($tenantId, $userId, $invoiceId, $result['uuid']);
|
|
|
|
json_success([
|
|
'uuid' => $result['uuid'],
|
|
'qr_code' => $qrBase64,
|
|
'status' => 'accepted',
|
|
], 'تم إرسال الفاتورة لجوفتورة بنجاح');
|
|
} else {
|
|
AuditLogger::log('invoice.jofotara_rejected', 'invoice', $invoiceId, null, [
|
|
'error' => $result['error'] ?? 'Unknown',
|
|
], $decoded);
|
|
|
|
\App\Services\SmartNotifications::jofotaraRejected($tenantId, $userId, $invoiceId, $result['error'] ?? 'خطأ غير محدد');
|
|
|
|
json_error('رُفضت الفاتورة من جوفتورة: ' . ($result['error'] ?? 'خطأ غير محدد'), 422);
|
|
}
|