Update: 2026-05-07 23:06:22

This commit is contained in:
Hamza-Ayed
2026-05-07 23:06:22 +03:00
parent e04229dfbe
commit 80f3d257b0
20 changed files with 1254 additions and 60 deletions

View File

@@ -0,0 +1,55 @@
<?php
/**
* Create User-Company Assignment
* POST /v1/assignments/create
*/
declare(strict_types=1);
use App\Core\Database;
use App\Middleware\AuthMiddleware;
use App\Middleware\RoleMiddleware;
// Only Admin/Super Admin
$decoded = RoleMiddleware::require(['super_admin', 'admin']);
$data = input();
$userId = $data['user_id'] ?? null;
$companyId = $data['company_id'] ?? null;
if (!$userId || !$companyId) {
json_error('userId and companyId are required', 422);
}
$db = Database::getInstance();
try {
// Check if user belongs to the same tenant (if not super_admin)
if ($decoded['role'] !== 'super_admin') {
$stmt = $db->prepare("SELECT tenant_id FROM users WHERE id = ?");
$stmt->execute([$userId]);
$userTenant = $stmt->fetchColumn();
if ($userTenant !== $decoded['tenant_id']) {
json_error('User does not belong to your office', 403);
}
}
$stmt = $db->prepare("
INSERT INTO user_company_assignments (id, user_id, company_id, is_active, created_at)
VALUES (?, ?, ?, 1, ?)
ON DUPLICATE KEY UPDATE is_active = 1
");
$stmt->execute([
Database::generateUuid(),
$userId,
$companyId,
date('Y-m-d H:i:s')
]);
json_success(null, 'تم تخصيص المستخدم للشركة بنجاح');
} catch (\Exception $e) {
json_error('حدث خطأ أثناء التخصيص: ' . $e->getMessage(), 500);
}

View File

@@ -0,0 +1,41 @@
<?php
/**
* List Assignments for a Company
* GET /v1/assignments?company_id=...
*/
declare(strict_types=1);
use App\Core\Database;
use App\Core\Encryption;
use App\Middleware\AuthMiddleware;
$decoded = AuthMiddleware::check();
$companyId = input('company_id');
if (!$companyId) {
json_error('company_id is required', 422);
}
$db = Database::getInstance();
try {
$stmt = $db->prepare("
SELECT a.id, a.user_id, a.is_active, u.name, u.email, u.role
FROM user_company_assignments a
JOIN users u ON a.user_id = u.id
WHERE a.company_id = ? AND a.is_active = 1
");
$stmt->execute([$companyId]);
$assignments = $stmt->fetchAll();
foreach ($assignments as &$a) {
$a['name'] = Encryption::decrypt($a['name']) ?: $a['name'];
$a['email'] = Encryption::decrypt($a['email']) ?: $a['email'];
}
json_success($assignments);
} catch (\Exception $e) {
json_error('SQL Error: ' . $e->getMessage(), 500);
}

View File

@@ -0,0 +1,122 @@
<?php
/**
* Bulk Excel Import Endpoint
* POST /v1/excel/import
*/
declare(strict_types=1);
use App\Core\Database;
use App\Core\Encryption;
use App\Middleware\AuthMiddleware;
use App\Middleware\QuotaMiddleware;
use PhpOffice\PhpSpreadsheet\IOFactory;
$decoded = AuthMiddleware::check();
$tenantId = $decoded['tenant_id'];
if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
json_error('الملف مطلوب', 422);
}
$companyId = input('company_id');
if (!$companyId) {
json_error('يجب تحديد الشركة', 422);
}
$file = $_FILES['file'];
$tmpPath = $file['tmp_name'];
try {
$spreadsheet = IOFactory::load($tmpPath);
$worksheet = $spreadsheet->getActiveSheet();
$rows = $worksheet->toArray();
if (count($rows) < 2) {
json_error('الملف فارغ أو لا يحتوي على بيانات', 422);
}
$header = array_shift($rows);
$mapping = mapColumns($header);
$db = Database::getInstance();
$db->beginTransaction();
$importedCount = 0;
$errors = [];
foreach ($rows as $index => $row) {
if (empty(array_filter($row))) continue; // Skip empty rows
try {
// Check quota for each invoice (preventive)
QuotaMiddleware::checkInvoiceQuota($tenantId);
$invoiceData = [
'id' => Database::generateUuid(),
'tenant_id' => $tenantId,
'company_id' => $companyId,
'invoice_number' => $row[$mapping['number']] ?? 'EXT-' . time() . '-' . $index,
'invoice_date' => formatDate($row[$mapping['date']] ?? date('Y-m-d')),
'customer_name' => Encryption::encrypt($row[$mapping['customer']] ?? 'عميل عام'),
'grand_total' => (float)($row[$mapping['total']] ?? 0),
'tax_amount' => (float)($row[$mapping['tax']] ?? 0),
'status' => 'extracted', // Ready for review/approval
'created_at' => date('Y-m-d H:i:s')
];
$stmt = $db->prepare("
INSERT INTO invoices (id, tenant_id, company_id, invoice_number, invoice_date, customer_name, grand_total, tax_amount, status, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute(array_values($invoiceData));
$importedCount++;
} catch (\Exception $e) {
$errors[] = "السطر " . ($index + 2) . ": " . $e->getMessage();
}
}
$db->commit();
json_success([
'imported_count' => $importedCount,
'errors' => $errors
], "تم استيراد $importedCount فاتورة بنجاح");
} catch (\Exception $e) {
if (isset($db)) $db->rollBack();
json_error('فشل معالجة ملف الاكسل: ' . $e->getMessage(), 500);
}
/**
* Intelligent Column Mapping
*/
function mapColumns(array $header): array {
$map = [
'number' => 0,
'date' => 1,
'customer' => 2,
'total' => 3,
'tax' => 4
];
foreach ($header as $i => $col) {
$col = mb_strtolower(trim((string)$col));
if (str_contains($col, 'رقم') || str_contains($col, 'number')) $map['number'] = $i;
if (str_contains($col, 'تاريخ') || str_contains($col, 'date')) $map['date'] = $i;
if (str_contains($col, 'عميل') || str_contains($col, 'customer') || str_contains($col, 'اسم')) $map['customer'] = $i;
if (str_contains($col, 'اجمالي') || str_contains($col, 'total') || str_contains($col, 'المجموع')) $map['total'] = $i;
if (str_contains($col, 'ضريبة') || str_contains($col, 'tax')) $map['tax'] = $i;
}
return $map;
}
function formatDate($val): string {
if (is_numeric($val)) {
return date('Y-m-d', \PhpOffice\PhpSpreadsheet\Shared\Date::excelToTimestamp($val));
}
$ts = strtotime((string)$val);
return $ts ? date('Y-m-d', $ts) : date('Y-m-d');
}

View File

@@ -98,7 +98,7 @@ function detectAudioMimeType(string $path, string $fallback, string $fileName =
function extractIntentFromAudio(string $base64Audio, string $mimeType, string $apiKey): array
{
$model = env('GEMINI_VOICE_MODEL', 'gemini-2.5-flash');
$model = env('GEMINI_VOICE_MODEL', 'gemini-1.5-flash');
$systemPrompt = <<<PROMPT
أنت مساعد أوامر صوتية عربي لمنصة "مُصادَق" للفوترة الأردنية.