6c72fc7db1
═══ Integrări (Marketing → Integrări) ═══ - /app/integrations Page cu 6 carduri (Telegram/WhatsApp/Google Ads/FB/SMS/Webhook) - Toggle on/off per integrare; salvare în settings.integrations JSON - Câmpuri specifice per integrare (token/key/id/secret) - Banner explicativ: 'placeholder UI — implementare separată' ═══ Backup tenant ═══ - TenantBackupService::export($company) → ZIP cu: • data/ (1 JSON per tabel: clients/vehicles/leads/deals/work_orders cu sub-relații/...) • media/ (logo + favicon) • manifest.json (metadata + counts) - /app/backup Page cu buton 'Descarcă backup acum' - Streaming download cu deleteFileAfterSend - Util pentru: backup local, migrare, audit, GDPR right-to-erasure Total tenant routes: 104. Toate cele ~26 module din prototip implementate (sau echivalent funcțional).
96 lines
3.3 KiB
PHP
96 lines
3.3 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Central\Company;
|
|
use App\Models\Tenant\Appointment;
|
|
use App\Models\Tenant\Client;
|
|
use App\Models\Tenant\Deal;
|
|
use App\Models\Tenant\EmployeeProfile;
|
|
use App\Models\Tenant\Expense;
|
|
use App\Models\Tenant\Labor;
|
|
use App\Models\Tenant\Lead;
|
|
use App\Models\Tenant\Part;
|
|
use App\Models\Tenant\Payment;
|
|
use App\Models\Tenant\Purchase;
|
|
use App\Models\Tenant\PurchaseItem;
|
|
use App\Models\Tenant\Supplier;
|
|
use App\Models\Tenant\User;
|
|
use App\Models\Tenant\Vehicle;
|
|
use App\Models\Tenant\WorkOrder;
|
|
use App\Models\Tenant\WorkOrderPart;
|
|
use App\Models\Tenant\WorkOrderWork;
|
|
use ZipArchive;
|
|
|
|
class TenantBackupService
|
|
{
|
|
/**
|
|
* Export all tenant data to a temporary ZIP file. Returns absolute path.
|
|
* Caller is responsible for streaming + cleanup.
|
|
*/
|
|
public function export(Company $company): string
|
|
{
|
|
$tmp = storage_path('app/backups');
|
|
if (! is_dir($tmp)) @mkdir($tmp, 0775, true);
|
|
$file = $tmp . '/tenant-' . $company->slug . '-' . date('Ymd-His') . '.zip';
|
|
|
|
$zip = new ZipArchive();
|
|
if ($zip->open($file, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
|
|
throw new \RuntimeException('Nu pot crea fișierul ZIP la ' . $file);
|
|
}
|
|
|
|
// Tables to dump.
|
|
$tables = [
|
|
'company' => Company::withoutGlobalScopes()->where('id', $company->id)->get(),
|
|
'users' => User::all(),
|
|
'clients' => Client::all(),
|
|
'vehicles' => Vehicle::all(),
|
|
'leads' => Lead::all(),
|
|
'deals' => Deal::all(),
|
|
'appointments' => Appointment::all(),
|
|
'work_orders' => WorkOrder::with(['works', 'parts'])->get(),
|
|
'wo_works' => WorkOrderWork::all(),
|
|
'wo_parts' => WorkOrderPart::all(),
|
|
'labors' => Labor::all(),
|
|
'parts' => Part::all(),
|
|
'suppliers' => Supplier::all(),
|
|
'purchases' => Purchase::with('items')->get(),
|
|
'purchase_items' => PurchaseItem::all(),
|
|
'payments' => Payment::all(),
|
|
'expenses' => Expense::all(),
|
|
'employee_profiles' => EmployeeProfile::all(),
|
|
];
|
|
|
|
$manifest = [
|
|
'tenant' => $company->only(['id', 'slug', 'name', 'city', 'phone', 'email']),
|
|
'exported_at' => now()->toIso8601String(),
|
|
'app_version' => 'AutoCRM-1.0',
|
|
'counts' => [],
|
|
];
|
|
|
|
foreach ($tables as $name => $rows) {
|
|
$json = $rows->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
$zip->addFromString("data/{$name}.json", $json);
|
|
$manifest['counts'][$name] = $rows->count();
|
|
}
|
|
|
|
$zip->addFromString('manifest.json', json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
|
|
|
// Optionally embed media files (logo + favicon).
|
|
foreach (['logo', 'favicon'] as $col) {
|
|
$m = $company->getFirstMedia($col);
|
|
if ($m && file_exists($m->getPath())) {
|
|
$zip->addFile($m->getPath(), 'media/' . $col . '.' . pathinfo($m->getPath(), PATHINFO_EXTENSION));
|
|
}
|
|
}
|
|
|
|
$zip->close();
|
|
return $file;
|
|
}
|
|
|
|
public function filename(Company $company): string
|
|
{
|
|
return 'tenant-' . $company->slug . '-' . date('Ymd-His') . '.zip';
|
|
}
|
|
}
|