78ff8d4b43
User screenshots showed the tenant admin panel (Filament) had sidebar
labels stuck in Romanian even when switching to Russian: 'Cereri',
'Calendar vizual', 'Atelierul meu', 'KPI mecanici', 'Fișe lucru',
'Norme-ore', 'Tehnicieni', 'Șabloane servicii', 'Depozite', 'Scaner',
'Depozit', 'VIN-căutare', 'Furnizori', 'Achiziții', 'Procentaj',
'Coeficienți preț', plus all group headers.
Root cause: every Filament Resource and Page had static properties
'protected static ?string $navigationLabel = "Fișe lucru"' — string
literals baked into class definitions. Static properties don't run
through the translation layer.
Fix in two parts:
1. New translation files with 52 label keys + 12 group keys:
- lang/ro/nav.php — Romanian (identity)
- lang/ru/nav.php — full Russian translations (Заказ-наряды,
Автомобили, Клиенты, Календарь, Моя мастерская, Механики KPI,
Настройки, etc.)
- lang/en/nav.php — English translations (Work orders, Vehicles,
Clients, Calendar, My workshop, Mechanic KPI, Settings, etc.)
Keyed by the Romanian original so lookups map 1:1 —
'nav.label.Fișe lucru' returns 'Заказ-наряды' in RU, 'Work orders'
in EN, 'Fișe lucru' in RO.
2. Python transformer converted 54 files:
- 33 Filament Tenant Resources
- 15 Filament Tenant Pages
- 4 Filament Central Resources
- 1 Filament Central Page
- 1 Widget
Each 'protected static ?string $navigationLabel = "X";' became
'public static function getNavigationLabel(): string { return
__("nav.label.X"); }'. Same treatment for $navigationGroup.
Cleanup: 6 resources already had manually-added getNavigationLabel
methods from an earlier partial effort — those used flat JSON keys
(__("Cereri")) that never resolved. Deduped so only the nav.label.*
version remains.
Untouched (intentional):
- $modelLabel / $pluralModelLabel (used in breadcrumbs and headings —
still hardcoded, next tier of work)
- Section titles, column headers, form field labels (medium priority)
- $navigationSort (numeric, no translation needed)
- $navigationIcon (icon reference)
Suite: 306 passed (853 assertions). Unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
150 lines
4.7 KiB
PHP
150 lines
4.7 KiB
PHP
<?php
|
|
|
|
namespace App\Filament\Tenant\Pages;
|
|
|
|
use App\Models\Tenant\Supplier;
|
|
use App\Services\ExcelInvoiceImportService;
|
|
use Filament\Notifications\Notification;
|
|
use Filament\Pages\Page;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Livewire\WithFileUploads;
|
|
|
|
class ExcelImportWizard extends Page
|
|
{
|
|
use WithFileUploads;
|
|
|
|
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-arrow-up-tray';
|
|
|
|
public static function getNavigationLabel(): string
|
|
{
|
|
return __('nav.label.Import factură Excel');
|
|
}
|
|
|
|
public static function getNavigationGroup(): ?string
|
|
{
|
|
return __('nav.group.Stoc & Finanțe');
|
|
}
|
|
|
|
protected static ?int $navigationSort = 65;
|
|
|
|
protected static ?string $title = 'Import factură Excel/CSV';
|
|
|
|
protected string $view = 'filament.tenant.pages.excel-import-wizard';
|
|
|
|
public int $step = 1;
|
|
public ?int $supplierId = null;
|
|
public $upload = null;
|
|
public ?string $storedPath = null;
|
|
public array $headersPreview = ['columns' => [], 'rows' => []];
|
|
public array $mapping = [
|
|
'article_col' => 'B',
|
|
'name_col' => 'C',
|
|
'qty_col' => 'E',
|
|
'price_col' => 'F',
|
|
'brand_col' => null,
|
|
'header_row' => 1,
|
|
];
|
|
public bool $rememberMapping = true;
|
|
public array $previewRows = [];
|
|
public array $previewSummary = ['total' => 0, 'found' => 0, 'new' => 0, 'no_article' => 0];
|
|
public bool $createNew = true;
|
|
|
|
public function getMaxContentWidth(): \Filament\Support\Enums\Width
|
|
{
|
|
return \Filament\Support\Enums\Width::Full;
|
|
}
|
|
|
|
public function getSupplierOptions(): array
|
|
{
|
|
return Supplier::orderBy('name')->pluck('name', 'id')->toArray();
|
|
}
|
|
|
|
public function goToStep2(): void
|
|
{
|
|
if (! $this->supplierId) {
|
|
Notification::make()->title('Selectează furnizorul')->danger()->send();
|
|
return;
|
|
}
|
|
if (! $this->upload) {
|
|
Notification::make()->title('Încarcă fișierul Excel sau CSV')->danger()->send();
|
|
return;
|
|
}
|
|
|
|
// Persist the uploaded file so Livewire reuses can resolve it
|
|
$this->storedPath = $this->upload->store('imports', 'local');
|
|
|
|
// Try to load remembered mapping for this supplier
|
|
$svc = app(ExcelInvoiceImportService::class);
|
|
$supplier = Supplier::find($this->supplierId);
|
|
$remembered = $svc->rememberedMappingFor($supplier);
|
|
if ($remembered) {
|
|
$this->mapping = array_merge($this->mapping, $remembered);
|
|
}
|
|
|
|
$absPath = Storage::disk('local')->path($this->storedPath);
|
|
$this->headersPreview = $svc->headersPreview($absPath);
|
|
|
|
$this->step = 2;
|
|
}
|
|
|
|
public function goToStep3(): void
|
|
{
|
|
$absPath = Storage::disk('local')->path($this->storedPath);
|
|
$svc = app(ExcelInvoiceImportService::class);
|
|
$result = $svc->preview($absPath, $this->mapping);
|
|
$this->previewRows = $result['rows'];
|
|
$this->previewSummary = $result['summary'];
|
|
|
|
if (empty($this->previewRows)) {
|
|
Notification::make()->title('Nu am găsit linii valide — verifică maparea coloanelor')->warning()->send();
|
|
return;
|
|
}
|
|
|
|
$this->step = 3;
|
|
}
|
|
|
|
public function confirmImport(): void
|
|
{
|
|
$svc = app(ExcelInvoiceImportService::class);
|
|
$supplier = Supplier::find($this->supplierId);
|
|
|
|
if ($this->rememberMapping) {
|
|
$svc->rememberMapping($supplier, $this->mapping, basename($this->storedPath ?? ''));
|
|
}
|
|
|
|
$purchase = $svc->import($supplier, $this->previewRows, $this->createNew);
|
|
|
|
Notification::make()
|
|
->title("Import reușit — Purchase {$purchase->number}")
|
|
->body("{$this->previewSummary['total']} linii importate")
|
|
->success()
|
|
->send();
|
|
|
|
// Cleanup uploaded file
|
|
if ($this->storedPath) {
|
|
Storage::disk('local')->delete($this->storedPath);
|
|
}
|
|
|
|
$this->step = 4;
|
|
$this->dispatch('purchase-created', purchaseId: $purchase->id);
|
|
|
|
// Set the redirect URL on the page so the blade can show a CTA
|
|
session()->flash('purchase_id', $purchase->id);
|
|
}
|
|
|
|
public function reset_(): void
|
|
{
|
|
$this->step = 1;
|
|
$this->supplierId = null;
|
|
$this->upload = null;
|
|
$this->storedPath = null;
|
|
$this->headersPreview = ['columns' => [], 'rows' => []];
|
|
$this->mapping = [
|
|
'article_col' => 'B', 'name_col' => 'C', 'qty_col' => 'E',
|
|
'price_col' => 'F', 'brand_col' => null, 'header_row' => 1,
|
|
];
|
|
$this->previewRows = [];
|
|
$this->previewSummary = ['total' => 0, 'found' => 0, 'new' => 0, 'no_article' => 0];
|
|
}
|
|
}
|