Update: 2026-05-07 23:06:22
This commit is contained in:
55
app/modules_app/assignments/create.php
Normal file
55
app/modules_app/assignments/create.php
Normal 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);
|
||||
}
|
||||
41
app/modules_app/assignments/index.php
Normal file
41
app/modules_app/assignments/index.php
Normal 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);
|
||||
}
|
||||
122
app/modules_app/excel/import.php
Normal file
122
app/modules_app/excel/import.php
Normal 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');
|
||||
}
|
||||
@@ -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
|
||||
أنت مساعد أوامر صوتية عربي لمنصة "مُصادَق" للفوترة الأردنية.
|
||||
|
||||
Reference in New Issue
Block a user