feat(i18n): wrap 1103 hardcoded UI strings across Filament with __()
- Convert static $modelLabel/$pluralModelLabel/$title to getter methods - Wrap ->label()/->placeholder()/->helperText()/->description()/->title()/->body() args - Wrap Section::make()/Fieldset::make()/Notification::make()->title() args - Fix RelationManagers::getTitle() signature to match parent (Model, string) - Fix Pages::getTitle() to instance method (BasePage::getTitle is non-static) - Extend lang/ru.json + lang/en.json with 700+ common terms; identity fallback for the rest - Remove duplicate getters in 5 resources that had manual getModelLabel already All 306 tests pass. Missing translations fall back to the RO key so the UI never breaks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -19,7 +19,12 @@ class PaymentSettings extends Page
|
||||
return __('nav.label.Setări plăți');
|
||||
}
|
||||
|
||||
protected static ?string $title = 'Integrări de plată';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('Integrări de plată');
|
||||
}
|
||||
|
||||
protected static ?int $navigationSort = 90;
|
||||
|
||||
@@ -84,132 +89,132 @@ class PaymentSettings extends Page
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Schemas\Components\Section::make('General')
|
||||
Schemas\Components\Section::make(__('General'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
Forms\Components\Select::make('platform_currency')
|
||||
->label('Monedă platformă')
|
||||
->label(__('Monedă platformă'))
|
||||
->options(['MDL' => 'MDL', 'EUR' => 'EUR', 'USD' => 'USD'])
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('invoice_prefix')
|
||||
->label('Prefix facturi')->maxLength(10)->default('INV'),
|
||||
->label(__('Prefix facturi'))->maxLength(10)->default('INV'),
|
||||
Forms\Components\Textarea::make('terms')
|
||||
->label('Termeni de plată')->rows(2)->columnSpanFull(),
|
||||
->label(__('Termeni de plată'))->rows(2)->columnSpanFull(),
|
||||
]),
|
||||
Schemas\Components\Section::make('Date legale (apar pe facturi)')
|
||||
Schemas\Components\Section::make(__('Date legale (apar pe facturi)'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('legal_name')->label('Denumire companie')->maxLength(160),
|
||||
Forms\Components\TextInput::make('legal_idno')->label('IDNO / CUI')->maxLength(40),
|
||||
Forms\Components\TextInput::make('legal_address')->label('Adresă')->columnSpanFull()->maxLength(255),
|
||||
Forms\Components\TextInput::make('legal_phone')->label('Telefon')->tel()->maxLength(40),
|
||||
Forms\Components\TextInput::make('legal_email')->label('Email')->email()->maxLength(120),
|
||||
Forms\Components\TextInput::make('legal_name')->label(__('Denumire companie'))->maxLength(160),
|
||||
Forms\Components\TextInput::make('legal_idno')->label(__('IDNO / CUI'))->maxLength(40),
|
||||
Forms\Components\TextInput::make('legal_address')->label(__('Adresă'))->columnSpanFull()->maxLength(255),
|
||||
Forms\Components\TextInput::make('legal_phone')->label(__('Telefon'))->tel()->maxLength(40),
|
||||
Forms\Components\TextInput::make('legal_email')->label(__('Email'))->email()->maxLength(120),
|
||||
]),
|
||||
Schemas\Components\Section::make('💳 Stripe')
|
||||
->description('Procesare carduri online. Funcționează în 30+ țări. Comision ~2.9% + 0.30€/tranzacție.')
|
||||
Schemas\Components\Section::make(__('💳 Stripe'))
|
||||
->description(__('Procesare carduri online. Funcționează în 30+ țări. Comision ~2.9% + 0.30€/tranzacție.'))
|
||||
->columns(3)
|
||||
->collapsible()
|
||||
->collapsed(fn (Get $get) => ! $get('stripe_enabled'))
|
||||
->schema([
|
||||
Forms\Components\Toggle::make('stripe_enabled')->label('Activează Stripe')->default(false)->live(),
|
||||
Forms\Components\Toggle::make('stripe_enabled')->label(__('Activează Stripe'))->default(false)->live(),
|
||||
Forms\Components\Select::make('stripe_mode')
|
||||
->label('Mod')
|
||||
->label(__('Mod'))
|
||||
->options(['test' => '🧪 Test', 'live' => '🟢 Live'])
|
||||
->default('test')
|
||||
->required(fn (Get $get) => $get('stripe_enabled'))
|
||||
->visible(fn (Get $get) => $get('stripe_enabled')),
|
||||
Forms\Components\TextInput::make('stripe_publishable')
|
||||
->label('Publishable key')->placeholder('pk_test_...')
|
||||
->label(__('Publishable key'))->placeholder('pk_test_...')
|
||||
->visible(fn (Get $get) => $get('stripe_enabled')),
|
||||
Forms\Components\TextInput::make('stripe_secret')
|
||||
->label('Secret key')->password()->revealable()->placeholder('sk_test_...')
|
||||
->label(__('Secret key'))->password()->revealable()->placeholder('sk_test_...')
|
||||
->visible(fn (Get $get) => $get('stripe_enabled')),
|
||||
Forms\Components\TextInput::make('stripe_webhook')
|
||||
->label('Webhook secret')->password()->revealable()->placeholder('whsec_...')
|
||||
->label(__('Webhook secret'))->password()->revealable()->placeholder('whsec_...')
|
||||
->columnSpanFull()
|
||||
->helperText('Endpoint webhook: https://service.mir.md/payments/stripe/webhook')
|
||||
->helperText(__('Endpoint webhook: https://service.mir.md/payments/stripe/webhook'))
|
||||
->visible(fn (Get $get) => $get('stripe_enabled')),
|
||||
]),
|
||||
Schemas\Components\Section::make('🅿️ PayPal')
|
||||
->description('Plăți internaționale. Mai potrivit pentru clienți din afara MD.')
|
||||
Schemas\Components\Section::make(__('🅿️ PayPal'))
|
||||
->description(__('Plăți internaționale. Mai potrivit pentru clienți din afara MD.'))
|
||||
->columns(3)
|
||||
->collapsible()
|
||||
->collapsed(fn (Get $get) => ! $get('paypal_enabled'))
|
||||
->schema([
|
||||
Forms\Components\Toggle::make('paypal_enabled')->label('Activează PayPal')->default(false)->live(),
|
||||
Forms\Components\Toggle::make('paypal_enabled')->label(__('Activează PayPal'))->default(false)->live(),
|
||||
Forms\Components\Select::make('paypal_mode')
|
||||
->label('Mod')
|
||||
->label(__('Mod'))
|
||||
->options(['sandbox' => '🧪 Sandbox', 'live' => '🟢 Live'])
|
||||
->default('sandbox')
|
||||
->visible(fn (Get $get) => $get('paypal_enabled')),
|
||||
Forms\Components\Placeholder::make('paypal_webhook_info')
|
||||
->label('Webhook')
|
||||
->label(__('Webhook'))
|
||||
->content('https://service.mir.md/payments/paypal/webhook')
|
||||
->visible(fn (Get $get) => $get('paypal_enabled')),
|
||||
Forms\Components\TextInput::make('paypal_client_id')
|
||||
->label('Client ID')->placeholder('AYS...')
|
||||
->label(__('Client ID'))->placeholder(__('AYS...'))
|
||||
->visible(fn (Get $get) => $get('paypal_enabled')),
|
||||
Forms\Components\TextInput::make('paypal_secret')
|
||||
->label('Secret')->password()->revealable()->placeholder('EJk...')
|
||||
->label(__('Secret'))->password()->revealable()->placeholder(__('EJk...'))
|
||||
->visible(fn (Get $get) => $get('paypal_enabled')),
|
||||
]),
|
||||
Schemas\Components\Section::make('🇲🇩 Paynet (Moldova)')
|
||||
->description('Procesator de plăți Moldova. Acceptă carduri MAIB, MICB, Victoriabank, OTP, MD Cash, Bitcoin, e-money. Comision negociabil ~1.8-2.5%.')
|
||||
Schemas\Components\Section::make(__('🇲🇩 Paynet (Moldova)'))
|
||||
->description(__('Procesator de plăți Moldova. Acceptă carduri MAIB, MICB, Victoriabank, OTP, MD Cash, Bitcoin, e-money. Comision negociabil ~1.8-2.5%.'))
|
||||
->columns(3)
|
||||
->collapsible()
|
||||
->collapsed(fn (Get $get) => ! $get('paynet_enabled'))
|
||||
->schema([
|
||||
Forms\Components\Toggle::make('paynet_enabled')->label('Activează Paynet')->default(false)->live(),
|
||||
Forms\Components\Toggle::make('paynet_enabled')->label(__('Activează Paynet'))->default(false)->live(),
|
||||
Forms\Components\Select::make('paynet_mode')
|
||||
->label('Mod')
|
||||
->label(__('Mod'))
|
||||
->options(['test' => '🧪 Test', 'live' => '🟢 Live'])
|
||||
->default('test')
|
||||
->visible(fn (Get $get) => $get('paynet_enabled')),
|
||||
Forms\Components\Placeholder::make('paynet_webhook_info')
|
||||
->label('Webhook (Notify URL)')
|
||||
->label(__('Webhook (Notify URL)'))
|
||||
->content('https://service.mir.md/payments/paynet/webhook')
|
||||
->visible(fn (Get $get) => $get('paynet_enabled')),
|
||||
Forms\Components\TextInput::make('paynet_merchant_code')
|
||||
->label('Merchant Code')
|
||||
->placeholder('AUTOCRM_001')
|
||||
->label(__('Merchant Code'))
|
||||
->placeholder(__('AUTOCRM_001'))
|
||||
->visible(fn (Get $get) => $get('paynet_enabled')),
|
||||
Forms\Components\TextInput::make('paynet_service_id')
|
||||
->label('Service ID')
|
||||
->label(__('Service ID'))
|
||||
->numeric()
|
||||
->placeholder('12345')
|
||||
->visible(fn (Get $get) => $get('paynet_enabled')),
|
||||
Forms\Components\TextInput::make('paynet_user')
|
||||
->label('User API')
|
||||
->label(__('User API'))
|
||||
->visible(fn (Get $get) => $get('paynet_enabled')),
|
||||
Forms\Components\TextInput::make('paynet_password')
|
||||
->label('Parolă API')->password()->revealable()
|
||||
->label(__('Parolă API'))->password()->revealable()
|
||||
->visible(fn (Get $get) => $get('paynet_enabled')),
|
||||
Forms\Components\TextInput::make('paynet_secret')
|
||||
->label('Secret semnătură')->password()->revealable()
|
||||
->label(__('Secret semnătură'))->password()->revealable()
|
||||
->columnSpanFull()
|
||||
->helperText('Cheia HMAC pentru semnarea cererilor + verificarea webhook-urilor de la Paynet.')
|
||||
->helperText(__('Cheia HMAC pentru semnarea cererilor + verificarea webhook-urilor de la Paynet.'))
|
||||
->visible(fn (Get $get) => $get('paynet_enabled')),
|
||||
]),
|
||||
Schemas\Components\Section::make('🏦 Transfer bancar (manual)')
|
||||
->description('Datele apar pe facturi și pe pagina de plată a tenant-ului. Confirmarea o faci manual.')
|
||||
Schemas\Components\Section::make(__('🏦 Transfer bancar (manual)'))
|
||||
->description(__('Datele apar pe facturi și pe pagina de plată a tenant-ului. Confirmarea o faci manual.'))
|
||||
->columns(2)
|
||||
->collapsible()
|
||||
->schema([
|
||||
Forms\Components\Toggle::make('bank_enabled')->label('Activează plata prin transfer')->default(true)->live(),
|
||||
Forms\Components\Toggle::make('bank_enabled')->label(__('Activează plata prin transfer'))->default(true)->live(),
|
||||
Forms\Components\TextInput::make('bank_beneficiary')
|
||||
->label('Beneficiar')->placeholder('AutoCRM SRL')
|
||||
->label(__('Beneficiar'))->placeholder(__('AutoCRM SRL'))
|
||||
->visible(fn (Get $get) => $get('bank_enabled')),
|
||||
Forms\Components\TextInput::make('bank_iban')
|
||||
->label('IBAN')->placeholder('MD00 AG00 0000 ...')
|
||||
->label(__('IBAN'))->placeholder(__('MD00 AG00 0000 ...'))
|
||||
->visible(fn (Get $get) => $get('bank_enabled')),
|
||||
Forms\Components\TextInput::make('bank_bic')
|
||||
->label('BIC / SWIFT')->placeholder('AGRNMD2X')
|
||||
->label(__('BIC / SWIFT'))->placeholder(__('AGRNMD2X'))
|
||||
->visible(fn (Get $get) => $get('bank_enabled')),
|
||||
Forms\Components\TextInput::make('bank_name')
|
||||
->label('Banca')->placeholder('Moldova-Agroindbank')
|
||||
->label(__('Banca'))->placeholder(__('Moldova-Agroindbank'))
|
||||
->visible(fn (Get $get) => $get('bank_enabled')),
|
||||
Forms\Components\Textarea::make('bank_instructions')
|
||||
->label('Instrucțiuni adiționale')->rows(2)->columnSpanFull()
|
||||
->label(__('Instrucțiuni adiționale'))->rows(2)->columnSpanFull()
|
||||
->visible(fn (Get $get) => $get('bank_enabled')),
|
||||
]),
|
||||
])
|
||||
@@ -266,14 +271,14 @@ class PaymentSettings extends Page
|
||||
'instructions' => $data['bank_instructions'] ?? null,
|
||||
]);
|
||||
|
||||
Notification::make()->title('Setări plăți salvate')->success()->send();
|
||||
Notification::make()->title(__('Setări plăți salvate'))->success()->send();
|
||||
}
|
||||
|
||||
protected function getFormActions(): array
|
||||
{
|
||||
return [
|
||||
\Filament\Actions\Action::make('save')
|
||||
->label('Salvează')
|
||||
->label(__('Salvează'))
|
||||
->submit('save'),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ class CompanyResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Identificare')
|
||||
Schemas\Components\Section::make(__('Identificare'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('slug')
|
||||
@@ -53,19 +53,19 @@ class CompanyResource extends Resource
|
||||
)
|
||||
->dehydrateStateUsing(fn ($state) => strtolower((string) $state))
|
||||
->extraInputAttributes(['style' => 'text-transform: lowercase'])
|
||||
->helperText('Subdomeniul: <slug>.service.mir.md'),
|
||||
->helperText(__('Subdomeniul: <slug>.service.mir.md')),
|
||||
Forms\Components\TextInput::make('name')->required()->maxLength(120),
|
||||
Forms\Components\TextInput::make('display_name')->maxLength(120),
|
||||
Forms\Components\TextInput::make('city')->maxLength(60),
|
||||
]),
|
||||
Schemas\Components\Section::make('Contact')
|
||||
Schemas\Components\Section::make(__('Contact'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('contact_name')->maxLength(120),
|
||||
Forms\Components\TextInput::make('phone')->tel()->maxLength(40),
|
||||
Forms\Components\TextInput::make('email')->email()->maxLength(120),
|
||||
]),
|
||||
Schemas\Components\Section::make('Abonament')
|
||||
Schemas\Components\Section::make(__('Abonament'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Select::make('status')
|
||||
@@ -79,31 +79,31 @@ class CompanyResource extends Resource
|
||||
->default('trial')
|
||||
->required(),
|
||||
Forms\Components\Select::make('plan_id')
|
||||
->label('Plan')
|
||||
->label(__('Plan'))
|
||||
->options(fn () => Plan::pluck('name', 'id'))
|
||||
->searchable(),
|
||||
Forms\Components\DateTimePicker::make('trial_ends_at')->label('Trial expiră la'),
|
||||
Forms\Components\DateTimePicker::make('active_until')->label('Abonament până la'),
|
||||
Forms\Components\DateTimePicker::make('trial_ends_at')->label(__('Trial expiră la')),
|
||||
Forms\Components\DateTimePicker::make('active_until')->label(__('Abonament până la')),
|
||||
Forms\Components\Toggle::make('is_demo')
|
||||
->label('Tenant demo')
|
||||
->helperText('Marchează acest tenant ca demo (datele pot fi resetate periodic).'),
|
||||
->label(__('Tenant demo'))
|
||||
->helperText(__('Marchează acest tenant ca demo (datele pot fi resetate periodic).')),
|
||||
]),
|
||||
Schemas\Components\Section::make('Admin tenant (la creare)')
|
||||
Schemas\Components\Section::make(__('Admin tenant (la creare)'))
|
||||
->columns(2)
|
||||
->visible(fn (string $operation) => $operation === 'create')
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('admin_name')
|
||||
->label('Nume admin')
|
||||
->label(__('Nume admin'))
|
||||
->default('Administrator'),
|
||||
Forms\Components\TextInput::make('admin_email')
|
||||
->label('Email admin')
|
||||
->label(__('Email admin'))
|
||||
->email()
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('admin_password')
|
||||
->label('Parolă (lasă gol pentru auto-generat)')
|
||||
->label(__('Parolă (lasă gol pentru auto-generat)'))
|
||||
->password()
|
||||
->minLength(8)
|
||||
->helperText('Dacă e gol, generăm 10 caractere random.'),
|
||||
->helperText(__('Dacă e gol, generăm 10 caractere random.')),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -129,9 +129,9 @@ class CompanyResource extends Resource
|
||||
'danger' => ['suspended', 'expired'],
|
||||
'gray' => ['archived'],
|
||||
]),
|
||||
Tables\Columns\TextColumn::make('plan.name')->label('Plan')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('plan.name')->label(__('Plan'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('city')->toggleable(),
|
||||
Tables\Columns\TextColumn::make('users_count')->counts('users')->label('Useri'),
|
||||
Tables\Columns\TextColumn::make('users_count')->counts('users')->label(__('Useri')),
|
||||
Tables\Columns\TextColumn::make('created_at')->date()->sortable(),
|
||||
])
|
||||
->filters([
|
||||
@@ -145,20 +145,20 @@ class CompanyResource extends Resource
|
||||
Actions\ViewAction::make()
|
||||
->url(fn (Company $record) => CompanyResource::getUrl('view', ['record' => $record])),
|
||||
Actions\Action::make('open_tenant')
|
||||
->label('Deschide')
|
||||
->label(__('Deschide'))
|
||||
->icon('heroicon-m-arrow-top-right-on-square')
|
||||
->color('primary')
|
||||
->url(fn (Company $record) => $record->url('/app'))
|
||||
->openUrlInNewTab(),
|
||||
Actions\Action::make('suspend')
|
||||
->label('Suspendă')
|
||||
->label(__('Suspendă'))
|
||||
->icon('heroicon-m-no-symbol')
|
||||
->color('danger')
|
||||
->visible(fn (Company $record) => in_array($record->status, ['active', 'trial']))
|
||||
->requiresConfirmation()
|
||||
->action(fn (Company $record) => app(\App\Services\CompanyProvisioner::class)->suspend($record)),
|
||||
Actions\Action::make('activate')
|
||||
->label('Activează')
|
||||
->label(__('Activează'))
|
||||
->icon('heroicon-m-check-circle')
|
||||
->color('success')
|
||||
->visible(fn (Company $record) => in_array($record->status, ['suspended', 'expired']))
|
||||
@@ -167,8 +167,8 @@ class CompanyResource extends Resource
|
||||
Actions\EditAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
])
|
||||
->emptyStateHeading('Niciun tenant încă')
|
||||
->emptyStateDescription('Adaugă primul client SaaS folosind butonul „Adaugă companie" — se va crea un tenant nou cu admin pe `<slug>.service.mir.md/app`.')
|
||||
->emptyStateHeading(__('Niciun tenant încă'))
|
||||
->emptyStateDescription(__('Adaugă primul client SaaS folosind butonul „Adaugă companie" — se va crea un tenant nou cu admin pe `<slug>.service.mir.md/app`.'))
|
||||
->emptyStateIcon('heroicon-o-building-office-2')
|
||||
->defaultSort('created_at', 'desc');
|
||||
}
|
||||
|
||||
@@ -110,13 +110,13 @@ class ViewCompany extends Page
|
||||
{
|
||||
return [
|
||||
Actions\Action::make('view_as')
|
||||
->label('Deschide tenantul')
|
||||
->label(__('Deschide tenantul'))
|
||||
->icon('heroicon-m-arrow-top-right-on-square')
|
||||
->color('primary')
|
||||
->url(fn () => $this->record->url('/app'))
|
||||
->openUrlInNewTab(),
|
||||
Actions\Action::make('issue_invoice')
|
||||
->label('Generează factură')
|
||||
->label(__('Generează factură'))
|
||||
->icon('heroicon-m-document-plus')
|
||||
->color('success')
|
||||
->visible(fn () => (bool) $this->record->plan_id)
|
||||
@@ -147,7 +147,7 @@ class ViewCompany extends Page
|
||||
$this->dispatch('$refresh');
|
||||
}),
|
||||
Actions\Action::make('upload_logo')
|
||||
->label('Logo')
|
||||
->label(__('Logo'))
|
||||
->icon('heroicon-m-photo')
|
||||
->color('gray')
|
||||
->schema([
|
||||
@@ -166,29 +166,29 @@ class ViewCompany extends Page
|
||||
$this->record->clearMediaCollection('logo');
|
||||
$this->record->addMedia($abs)->preservingOriginal()->toMediaCollection('logo');
|
||||
@unlink($abs);
|
||||
Notification::make()->title('Logo actualizat')->success()->send();
|
||||
Notification::make()->title(__('Logo actualizat'))->success()->send();
|
||||
}
|
||||
}),
|
||||
Actions\Action::make('edit')
|
||||
->label('Editează')
|
||||
->label(__('Editează'))
|
||||
->icon('heroicon-m-pencil')
|
||||
->url(fn () => CompanyResource::getUrl('edit', ['record' => $this->record])),
|
||||
Actions\Action::make('suspend')
|
||||
->label('Suspendă')->icon('heroicon-m-no-symbol')->color('danger')
|
||||
->label(__('Suspendă'))->icon('heroicon-m-no-symbol')->color('danger')
|
||||
->visible(fn () => in_array($this->record->status, ['active', 'trial']))
|
||||
->requiresConfirmation()
|
||||
->action(function () {
|
||||
app(\App\Services\CompanyProvisioner::class)->suspend($this->record);
|
||||
Notification::make()->title('Tenant suspendat.')->success()->send();
|
||||
Notification::make()->title(__('Tenant suspendat.'))->success()->send();
|
||||
$this->dispatch('$refresh');
|
||||
}),
|
||||
Actions\Action::make('activate')
|
||||
->label('Reactivează')->icon('heroicon-m-check-circle')->color('success')
|
||||
->label(__('Reactivează'))->icon('heroicon-m-check-circle')->color('success')
|
||||
->visible(fn () => in_array($this->record->status, ['suspended', 'expired']))
|
||||
->requiresConfirmation()
|
||||
->action(function () {
|
||||
app(\App\Services\CompanyProvisioner::class)->reactivate($this->record);
|
||||
Notification::make()->title('Tenant activat.')->success()->send();
|
||||
Notification::make()->title(__('Tenant activat.'))->success()->send();
|
||||
$this->dispatch('$refresh');
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -46,7 +46,7 @@ class PlanResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Identificare')
|
||||
Schemas\Components\Section::make(__('Identificare'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('slug')
|
||||
@@ -54,25 +54,25 @@ class PlanResource extends Resource
|
||||
->dehydrateStateUsing(fn ($s) => strtolower((string) $s))
|
||||
->extraInputAttributes(['style' => 'text-transform:lowercase']),
|
||||
Forms\Components\TextInput::make('name')->required()->maxLength(60),
|
||||
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true),
|
||||
Forms\Components\Toggle::make('is_public')->label('Public (afișat la signup)')->default(true),
|
||||
Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
|
||||
Forms\Components\Toggle::make('is_public')->label(__('Public (afișat la signup)'))->default(true),
|
||||
Forms\Components\Toggle::make('is_demo')
|
||||
->label('Demo (pentru demonstrații sales)')
|
||||
->helperText('Plan ascuns public, folosit pentru tenant-i demo cu features deblocate.')
|
||||
->label(__('Demo (pentru demonstrații sales)'))
|
||||
->helperText(__('Plan ascuns public, folosit pentru tenant-i demo cu features deblocate.'))
|
||||
->default(false),
|
||||
Forms\Components\TextInput::make('trial_days')
|
||||
->label('Perioadă trial (zile)')
|
||||
->numeric()->placeholder('null = fără trial')
|
||||
->helperText('Numărul de zile gratis după ce un tenant alege acest plan.'),
|
||||
->label(__('Perioadă trial (zile)'))
|
||||
->numeric()->placeholder(__('null = fără trial'))
|
||||
->helperText(__('Numărul de zile gratis după ce un tenant alege acest plan.')),
|
||||
]),
|
||||
Schemas\Components\Section::make('Preț')
|
||||
Schemas\Components\Section::make(__('Preț'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('price_monthly')->label('Preț lunar')->numeric()->required()->suffix(fn (Get $get) => $get('currency') ?? 'MDL'),
|
||||
Forms\Components\TextInput::make('price_yearly')->label('Preț anual')->numeric()->helperText('De obicei 10× preț lunar (2 luni gratis).')->suffix(fn (Get $get) => $get('currency') ?? 'MDL'),
|
||||
Forms\Components\TextInput::make('price_monthly')->label(__('Preț lunar'))->numeric()->required()->suffix(fn (Get $get) => $get('currency') ?? 'MDL'),
|
||||
Forms\Components\TextInput::make('price_yearly')->label(__('Preț anual'))->numeric()->helperText(__('De obicei 10× preț lunar (2 luni gratis).'))->suffix(fn (Get $get) => $get('currency') ?? 'MDL'),
|
||||
Forms\Components\Select::make('currency')->options(['MDL' => 'MDL', 'EUR' => 'EUR', 'USD' => 'USD'])->default('MDL'),
|
||||
]),
|
||||
Schemas\Components\Section::make('Funcționalități incluse')
|
||||
Schemas\Components\Section::make(__('Funcționalități incluse'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\CheckboxList::make('features')
|
||||
@@ -80,15 +80,15 @@ class PlanResource extends Resource
|
||||
->columns(2)
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
Schemas\Components\Section::make('Limite')
|
||||
Schemas\Components\Section::make(__('Limite'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('limits.max_users')->label('Max useri')->numeric()->placeholder('∞'),
|
||||
Forms\Components\TextInput::make('limits.max_clients')->label('Max clienți')->numeric()->placeholder('∞'),
|
||||
Forms\Components\TextInput::make('limits.max_vehicles')->label('Max mașini')->numeric()->placeholder('∞'),
|
||||
Forms\Components\TextInput::make('limits.max_work_orders_month')->label('Max fișe/lună')->numeric()->placeholder('∞'),
|
||||
Forms\Components\TextInput::make('limits.storage_mb')->label('Storage (MB)')->numeric()->placeholder('∞'),
|
||||
Forms\Components\TextInput::make('limits.ai_messages_month')->label('Mesaje AI/lună')->numeric()->placeholder('∞'),
|
||||
Forms\Components\TextInput::make('limits.max_users')->label(__('Max useri'))->numeric()->placeholder('∞'),
|
||||
Forms\Components\TextInput::make('limits.max_clients')->label(__('Max clienți'))->numeric()->placeholder('∞'),
|
||||
Forms\Components\TextInput::make('limits.max_vehicles')->label(__('Max mașini'))->numeric()->placeholder('∞'),
|
||||
Forms\Components\TextInput::make('limits.max_work_orders_month')->label(__('Max fișe/lună'))->numeric()->placeholder('∞'),
|
||||
Forms\Components\TextInput::make('limits.storage_mb')->label(__('Storage (MB)'))->numeric()->placeholder('∞'),
|
||||
Forms\Components\TextInput::make('limits.ai_messages_month')->label(__('Mesaje AI/lună'))->numeric()->placeholder('∞'),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -103,29 +103,29 @@ class PlanResource extends Resource
|
||||
->description(fn ($record) => $record->is_demo ? '🎬 Demo' : ($record->trial_days ? "🎁 {$record->trial_days} zile trial" : null)),
|
||||
Tables\Columns\TextColumn::make('price_monthly')
|
||||
->money(fn ($record) => $record->currency)
|
||||
->label('Lunar')
|
||||
->label(__('Lunar'))
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('price_yearly')
|
||||
->money(fn ($record) => $record->currency)
|
||||
->label('Anual'),
|
||||
->label(__('Anual')),
|
||||
Tables\Columns\TextColumn::make('companies_count')
|
||||
->counts('companies')
|
||||
->label('Abonați')
|
||||
->label(__('Abonați'))
|
||||
->badge()
|
||||
->color('primary'),
|
||||
Tables\Columns\TextColumn::make('features')
|
||||
->label('Funcționalități')
|
||||
->label(__('Funcționalități'))
|
||||
->formatStateUsing(fn ($s) => is_array($s) ? count($s) . ' incluse' : '—')
|
||||
->badge(),
|
||||
Tables\Columns\IconColumn::make('is_active')->boolean()->label('Activ'),
|
||||
Tables\Columns\IconColumn::make('is_public')->boolean()->label('Public'),
|
||||
Tables\Columns\IconColumn::make('is_active')->boolean()->label(__('Activ')),
|
||||
Tables\Columns\IconColumn::make('is_public')->boolean()->label(__('Public')),
|
||||
])
|
||||
->actions([
|
||||
Actions\EditAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
])
|
||||
->emptyStateHeading('Niciun plan definit')
|
||||
->emptyStateDescription('Creează planuri (ex: Free, Basic, Pro) și atribuie-le companiilor.')
|
||||
->emptyStateHeading(__('Niciun plan definit'))
|
||||
->emptyStateDescription(__('Creează planuri (ex: Free, Basic, Pro) și atribuie-le companiilor.'))
|
||||
->defaultSort('price_monthly');
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,12 @@ class SubscriptionResource extends Resource
|
||||
return __('nav.label.Facturi & abonamente');
|
||||
}
|
||||
|
||||
protected static ?string $modelLabel = 'factură';
|
||||
protected static ?string $modelLabel = null;
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('factură');
|
||||
}
|
||||
|
||||
protected static ?string $pluralModelLabel = 'facturi';
|
||||
|
||||
@@ -45,15 +50,15 @@ class SubscriptionResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Companie & plan')
|
||||
Schemas\Components\Section::make(__('Companie & plan'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Select::make('company_id')
|
||||
->label('Companie')
|
||||
->label(__('Companie'))
|
||||
->options(fn () => Company::orderBy('name')->pluck('name', 'id'))
|
||||
->required()->searchable()->live(),
|
||||
Forms\Components\Select::make('plan_id')
|
||||
->label('Plan')
|
||||
->label(__('Plan'))
|
||||
->options(fn () => Plan::pluck('name', 'id'))
|
||||
->required()->searchable()->live()
|
||||
->afterStateUpdated(function ($state, Set $set, Get $get) {
|
||||
@@ -80,7 +85,7 @@ class SubscriptionResource extends Resource
|
||||
Forms\Components\Select::make('status')
|
||||
->options(Subscription::STATUSES)->default('pending')->required(),
|
||||
]),
|
||||
Schemas\Components\Section::make('Sumă')
|
||||
Schemas\Components\Section::make(__('Sumă'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('amount')->numeric()->required()->suffix(fn (Get $get) => $get('currency') ?? 'MDL'),
|
||||
@@ -88,19 +93,19 @@ class SubscriptionResource extends Resource
|
||||
Forms\Components\Select::make('payment_method')
|
||||
->options(Subscription::PAYMENT_METHODS),
|
||||
]),
|
||||
Schemas\Components\Section::make('Perioadă')
|
||||
Schemas\Components\Section::make(__('Perioadă'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
Forms\Components\DatePicker::make('period_start')->required()->default(today()),
|
||||
Forms\Components\DatePicker::make('period_end')->required(),
|
||||
Forms\Components\DateTimePicker::make('due_at')->label('Scadent la'),
|
||||
Forms\Components\DateTimePicker::make('paid_at')->label('Plătit la'),
|
||||
Forms\Components\DateTimePicker::make('due_at')->label(__('Scadent la')),
|
||||
Forms\Components\DateTimePicker::make('paid_at')->label(__('Plătit la')),
|
||||
]),
|
||||
Schemas\Components\Section::make('Detalii')
|
||||
Schemas\Components\Section::make(__('Detalii'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('invoice_number')->label('Nr. factură')->placeholder('auto-generat')->maxLength(30),
|
||||
Forms\Components\TextInput::make('reference')->label('Referință (Stripe id, transfer)')->maxLength(100),
|
||||
Forms\Components\TextInput::make('invoice_number')->label(__('Nr. factură'))->placeholder('auto-generat')->maxLength(30),
|
||||
Forms\Components\TextInput::make('reference')->label(__('Referință (Stripe id, transfer)'))->maxLength(100),
|
||||
Forms\Components\Textarea::make('notes')->columnSpanFull()->rows(2),
|
||||
]),
|
||||
]);
|
||||
@@ -111,15 +116,15 @@ class SubscriptionResource extends Resource
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('invoice_number')
|
||||
->label('Factură')
|
||||
->label(__('Factură'))
|
||||
->placeholder(fn ($record) => '—')
|
||||
->copyable()
|
||||
->searchable(),
|
||||
Tables\Columns\TextColumn::make('company.name')
|
||||
->label('Companie')
|
||||
->label(__('Companie'))
|
||||
->searchable()
|
||||
->url(fn ($record) => \App\Filament\Central\Resources\CompanyResource::getUrl('view', ['record' => $record->company_id])),
|
||||
Tables\Columns\TextColumn::make('plan.name')->label('Plan')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('plan.name')->label(__('Plan'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('period')
|
||||
->formatStateUsing(fn ($s) => Subscription::PERIODS[$s] ?? $s)
|
||||
->badge(),
|
||||
@@ -137,8 +142,8 @@ class SubscriptionResource extends Resource
|
||||
'cancelled', 'refunded' => 'gray',
|
||||
default => 'primary',
|
||||
}),
|
||||
Tables\Columns\TextColumn::make('period_end')->label('Până la')->date(),
|
||||
Tables\Columns\TextColumn::make('paid_at')->label('Plătit')->date()->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('period_end')->label(__('Până la'))->date(),
|
||||
Tables\Columns\TextColumn::make('paid_at')->label(__('Plătit'))->date()->placeholder('—'),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('status')->options(Subscription::STATUSES),
|
||||
@@ -146,7 +151,7 @@ class SubscriptionResource extends Resource
|
||||
])
|
||||
->actions([
|
||||
Actions\Action::make('mark_paid')
|
||||
->label('Marchează plătit')
|
||||
->label(__('Marchează plătit'))
|
||||
->icon('heroicon-m-check-circle')
|
||||
->color('success')
|
||||
->visible(fn ($record) => $record->status !== 'paid')
|
||||
@@ -163,8 +168,8 @@ class SubscriptionResource extends Resource
|
||||
Actions\EditAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
])
|
||||
->emptyStateHeading('Nicio factură generată')
|
||||
->emptyStateDescription('Crează manual prima factură sau folosește butonul „Generează factură" din pagina Companiei.')
|
||||
->emptyStateHeading(__('Nicio factură generată'))
|
||||
->emptyStateDescription(__('Crează manual prima factură sau folosește butonul „Generează factură" din pagina Companiei.'))
|
||||
->defaultSort('created_at', 'desc');
|
||||
}
|
||||
|
||||
|
||||
@@ -36,24 +36,24 @@ class SuperAdminResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Identificare')
|
||||
Schemas\Components\Section::make(__('Identificare'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')->required()->maxLength(120),
|
||||
Forms\Components\TextInput::make('email')->email()->required()->unique(ignoreRecord: true)->maxLength(120),
|
||||
Forms\Components\TextInput::make('phone')->tel()->maxLength(40),
|
||||
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true),
|
||||
Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
|
||||
]),
|
||||
Schemas\Components\Section::make('Permisiuni')
|
||||
Schemas\Components\Section::make(__('Permisiuni'))
|
||||
->columns(1)
|
||||
->schema([
|
||||
Forms\Components\Select::make('role')
|
||||
->options(SuperAdmin::ROLES)
|
||||
->default('support')
|
||||
->required()
|
||||
->helperText('Owner = drepturi totale. Admin = aproape la fel. Sales = doar tenanți + planuri. Finance = facturi. Support = read-only.'),
|
||||
->helperText(__('Owner = drepturi totale. Admin = aproape la fel. Sales = doar tenanți + planuri. Finance = facturi. Support = read-only.')),
|
||||
]),
|
||||
Schemas\Components\Section::make('Parolă')
|
||||
Schemas\Components\Section::make(__('Parolă'))
|
||||
->columns(1)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('password')
|
||||
@@ -65,7 +65,7 @@ class SuperAdminResource extends Resource
|
||||
->minLength(8)
|
||||
->helperText(fn (string $operation) => $operation === 'edit' ? 'Lasă gol ca să o păstrezi.' : 'Min 8 caractere.'),
|
||||
]),
|
||||
Forms\Components\Textarea::make('notes')->label('Note interne')->rows(2)->columnSpanFull(),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Note interne'))->rows(2)->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -92,25 +92,25 @@ class SuperAdminResource extends Resource
|
||||
'sales' => 'info',
|
||||
default => 'gray',
|
||||
}),
|
||||
Tables\Columns\IconColumn::make('is_active')->boolean()->label('Activ'),
|
||||
Tables\Columns\IconColumn::make('is_active')->boolean()->label(__('Activ')),
|
||||
Tables\Columns\IconColumn::make('app_authentication_secret')
|
||||
->label('2FA')
|
||||
->label(__('2FA'))
|
||||
->boolean()
|
||||
->getStateUsing(fn ($record) => $record?->app_authentication_secret !== null)
|
||||
->trueIcon('heroicon-o-lock-closed')
|
||||
->falseIcon('heroicon-o-lock-open')
|
||||
->trueColor('success')
|
||||
->falseColor('warning'),
|
||||
Tables\Columns\TextColumn::make('last_login_at')->label('Ultim login')->dateTime()->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('last_login_at')->label(__('Ultim login'))->dateTime()->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('created_at')->date(),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('role')->options(SuperAdmin::ROLES),
|
||||
Tables\Filters\TernaryFilter::make('is_active')->label('Activ'),
|
||||
Tables\Filters\TernaryFilter::make('is_active')->label(__('Activ')),
|
||||
])
|
||||
->actions([
|
||||
Actions\Action::make('reset_password')
|
||||
->label('Reset parolă')
|
||||
->label(__('Reset parolă'))
|
||||
->icon('heroicon-m-key')
|
||||
->color('warning')
|
||||
->schema([
|
||||
@@ -119,7 +119,7 @@ class SuperAdminResource extends Resource
|
||||
])
|
||||
->action(function (SuperAdmin $record, array $data) {
|
||||
$record->update(['password' => Hash::make($data['new_password'])]);
|
||||
Notification::make()->title('Parolă resetată.')->success()->send();
|
||||
Notification::make()->title(__('Parolă resetată.'))->success()->send();
|
||||
}),
|
||||
Actions\Action::make('toggle_2fa')
|
||||
->label(fn (SuperAdmin $record) => $record->app_authentication_secret ? 'Dezactivează 2FA' : 'Forțează re-setare 2FA')
|
||||
@@ -133,19 +133,19 @@ class SuperAdminResource extends Resource
|
||||
'app_authentication_recovery_codes' => null,
|
||||
'email_authentication_at' => null,
|
||||
])->saveQuietly();
|
||||
Notification::make()->title('2FA dezactivat.')->success()->send();
|
||||
Notification::make()->title(__('2FA dezactivat.'))->success()->send();
|
||||
}),
|
||||
Actions\EditAction::make(),
|
||||
Actions\DeleteAction::make()
|
||||
->before(function (SuperAdmin $record) {
|
||||
if (auth('central')->id() === $record->id) {
|
||||
Notification::make()->title('Nu te poți șterge pe tine!')->danger()->send();
|
||||
Notification::make()->title(__('Nu te poți șterge pe tine!'))->danger()->send();
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
])
|
||||
->emptyStateHeading('Doar tu ești aici')
|
||||
->emptyStateDescription('Adaugă echipa ta — colegi de la suport, sales, finance — fiecare cu rolul lui.')
|
||||
->emptyStateHeading(__('Doar tu ești aici'))
|
||||
->emptyStateDescription(__('Adaugă echipa ta — colegi de la suport, sales, finance — fiecare cu rolul lui.'))
|
||||
->defaultSort('created_at', 'desc');
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ class PendingPayments extends BaseWidget
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->heading('Facturi pending / overdue')
|
||||
->heading(__('Facturi pending / overdue'))
|
||||
->query(
|
||||
Subscription::query()
|
||||
->whereIn('status', ['pending', 'overdue'])
|
||||
@@ -27,10 +27,10 @@ class PendingPayments extends BaseWidget
|
||||
)
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('invoice_number')
|
||||
->label('Factură')
|
||||
->label(__('Factură'))
|
||||
->copyable()
|
||||
->url(fn ($record) => SubscriptionResource::getUrl('edit', ['record' => $record])),
|
||||
Tables\Columns\TextColumn::make('company.name')->label('Companie'),
|
||||
Tables\Columns\TextColumn::make('company.name')->label(__('Companie')),
|
||||
Tables\Columns\TextColumn::make('plan.name')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('amount')->money(fn ($record) => $record->currency)->weight('bold'),
|
||||
Tables\Columns\TextColumn::make('status')
|
||||
@@ -38,11 +38,11 @@ class PendingPayments extends BaseWidget
|
||||
->color(fn ($s) => $s === 'overdue' ? 'danger' : 'warning')
|
||||
->formatStateUsing(fn ($s) => Subscription::STATUSES[$s] ?? $s),
|
||||
Tables\Columns\TextColumn::make('due_at')
|
||||
->label('Scadent')
|
||||
->label(__('Scadent'))
|
||||
->dateTime()
|
||||
->color(fn ($record) => $record->due_at && $record->due_at->isPast() ? 'danger' : null),
|
||||
])
|
||||
->emptyStateHeading('🎉 Toate facturile sunt plătite')
|
||||
->emptyStateHeading(__('🎉 Toate facturile sunt plătite'))
|
||||
->paginated(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ class PlatformStats extends BaseWidget
|
||||
->color('primary')
|
||||
->url(\App\Filament\Central\Resources\CompanyResource::getUrl('index')),
|
||||
Stat::make('Active', $active)
|
||||
->description("✓ Pe abonament plătit")
|
||||
->description(__("✓ Pe abonament plătit"))
|
||||
->descriptionIcon('heroicon-m-check-circle')
|
||||
->color('success'),
|
||||
Stat::make('În trial', $trial)
|
||||
@@ -51,7 +51,7 @@ class PlatformStats extends BaseWidget
|
||||
->descriptionIcon('heroicon-m-exclamation-triangle')
|
||||
->color($expiring > 0 ? 'danger' : 'success'),
|
||||
Stat::make('Venit luna curentă', number_format($mrr, 0, ',', ' ') . ' MDL')
|
||||
->description('MRR realizat')
|
||||
->description(__('MRR realizat'))
|
||||
->descriptionIcon('heroicon-m-banknotes')
|
||||
->color('success'),
|
||||
Stat::make('Facturi neplătite', $overdue)
|
||||
|
||||
@@ -17,7 +17,7 @@ class RecentTenants extends BaseWidget
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->heading('Tenants recenți')
|
||||
->heading(__('Tenants recenți'))
|
||||
->query(Company::query()->latest()->limit(8))
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('slug')
|
||||
@@ -33,8 +33,8 @@ class RecentTenants extends BaseWidget
|
||||
default => 'gray',
|
||||
}),
|
||||
Tables\Columns\TextColumn::make('plan.name')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('users_count')->counts('users')->label('Useri'),
|
||||
Tables\Columns\TextColumn::make('created_at')->label('Creat')->since(),
|
||||
Tables\Columns\TextColumn::make('users_count')->counts('users')->label(__('Useri')),
|
||||
Tables\Columns\TextColumn::make('created_at')->label(__('Creat'))->since(),
|
||||
])
|
||||
->paginated(false);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,12 @@ class AiAssistant extends Page
|
||||
|
||||
protected static ?int $navigationSort = 71;
|
||||
|
||||
protected static ?string $title = 'Asistent AI';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('Asistent AI');
|
||||
}
|
||||
|
||||
protected string $view = 'filament.tenant.pages.ai-assistant';
|
||||
|
||||
|
||||
@@ -23,7 +23,12 @@ class ApiTokens extends Page
|
||||
|
||||
protected static ?int $navigationSort = 95;
|
||||
|
||||
protected static ?string $title = 'API Tokens';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('API Tokens');
|
||||
}
|
||||
|
||||
protected string $view = 'filament.tenant.pages.api-tokens';
|
||||
|
||||
@@ -46,13 +51,13 @@ class ApiTokens extends Page
|
||||
{
|
||||
return [
|
||||
Actions\Action::make('create')
|
||||
->label('Generează token')
|
||||
->label(__('Generează token'))
|
||||
->icon('heroicon-m-plus')
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')
|
||||
->label('Nume (descriere)')
|
||||
->label(__('Nume (descriere)'))
|
||||
->required()
|
||||
->placeholder('ex: app-mobil, integrare-erp'),
|
||||
->placeholder(__('ex: app-mobil, integrare-erp')),
|
||||
])
|
||||
->action(function (array $data) {
|
||||
$u = auth()->user();
|
||||
@@ -60,8 +65,8 @@ class ApiTokens extends Page
|
||||
$token = $u->createToken($data['name']);
|
||||
$this->newToken = $token->plainTextToken;
|
||||
Notification::make()
|
||||
->title('Token generat!')
|
||||
->body('Copiază token-ul ACUM — nu va mai fi afișat.')
|
||||
->title(__('Token generat!'))
|
||||
->body(__('Copiază token-ul ACUM — nu va mai fi afișat.'))
|
||||
->success()
|
||||
->persistent()
|
||||
->send();
|
||||
|
||||
@@ -24,7 +24,12 @@ class Backup extends Page
|
||||
|
||||
protected static ?int $navigationSort = 96;
|
||||
|
||||
protected static ?string $title = 'Backup & Export date';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('Backup & Export date');
|
||||
}
|
||||
|
||||
protected string $view = 'filament.tenant.pages.backup';
|
||||
|
||||
|
||||
@@ -27,7 +27,12 @@ class CalendarBoard extends Page
|
||||
|
||||
protected static ?int $navigationSort = 8;
|
||||
|
||||
protected static ?string $title = 'Calendar vizual';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('Calendar vizual');
|
||||
}
|
||||
|
||||
protected string $view = 'filament.tenant.pages.calendar';
|
||||
|
||||
@@ -290,7 +295,7 @@ class CalendarBoard extends Page
|
||||
}
|
||||
$a->date = $toDate;
|
||||
$a->save();
|
||||
Notification::make()->title('Programare mutată')->body($a->title . ' → ' . $toDate)->success()->send();
|
||||
Notification::make()->title(__('Programare mutată'))->body($a->title . ' → ' . $toDate)->success()->send();
|
||||
}
|
||||
|
||||
public function openEvent(int $id): void
|
||||
@@ -330,7 +335,7 @@ class CalendarBoard extends Page
|
||||
{
|
||||
Appointment::where('id', $id)->delete();
|
||||
$this->openEventId = null;
|
||||
Notification::make()->title('Programare ștearsă')->success()->send();
|
||||
Notification::make()->title(__('Programare ștearsă'))->success()->send();
|
||||
}
|
||||
|
||||
public function openNewForm(int $rowId = 0, string $date = ''): void
|
||||
@@ -372,7 +377,7 @@ class CalendarBoard extends Page
|
||||
{
|
||||
$name = trim($this->newPost['name'] ?? '');
|
||||
if ($name === '') {
|
||||
Notification::make()->title('Numele este obligatoriu')->danger()->send();
|
||||
Notification::make()->title(__('Numele este obligatoriu'))->danger()->send();
|
||||
return;
|
||||
}
|
||||
Post::create([
|
||||
@@ -384,7 +389,7 @@ class CalendarBoard extends Page
|
||||
'sort_order' => 100,
|
||||
]);
|
||||
$this->showNewPostForm = false;
|
||||
Notification::make()->title('Spațiu de lucru adăugat')->success()->send();
|
||||
Notification::make()->title(__('Spațiu de lucru adăugat'))->success()->send();
|
||||
}
|
||||
|
||||
/** Inline rename + reassign default master from row label click. */
|
||||
@@ -409,7 +414,7 @@ class CalendarBoard extends Page
|
||||
'default_master_id' => $this->renamingPostMasterId ?: null,
|
||||
]);
|
||||
$this->renamingPostId = null;
|
||||
Notification::make()->title('Post actualizat')->success()->send();
|
||||
Notification::make()->title(__('Post actualizat'))->success()->send();
|
||||
}
|
||||
|
||||
/** Generate PDF for all appointments in the visible period. */
|
||||
@@ -467,7 +472,7 @@ class CalendarBoard extends Page
|
||||
{
|
||||
$d = $this->newAppt;
|
||||
if (empty($d['title']) || empty($d['date'])) {
|
||||
Notification::make()->title('Subiect și data sunt obligatorii')->danger()->send();
|
||||
Notification::make()->title(__('Subiect și data sunt obligatorii'))->danger()->send();
|
||||
return;
|
||||
}
|
||||
Appointment::create([
|
||||
@@ -483,7 +488,7 @@ class CalendarBoard extends Page
|
||||
'status' => 'scheduled',
|
||||
]);
|
||||
$this->showNewForm = false;
|
||||
Notification::make()->title('Programare adăugată')->success()->send();
|
||||
Notification::make()->title(__('Programare adăugată'))->success()->send();
|
||||
}
|
||||
|
||||
public function getMasterOptions(): array
|
||||
|
||||
@@ -27,7 +27,12 @@ class ExcelImportWizard extends Page
|
||||
|
||||
protected static ?int $navigationSort = 65;
|
||||
|
||||
protected static ?string $title = 'Import factură Excel/CSV';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('Import factură Excel/CSV');
|
||||
}
|
||||
|
||||
protected string $view = 'filament.tenant.pages.excel-import-wizard';
|
||||
|
||||
@@ -62,11 +67,11 @@ class ExcelImportWizard extends Page
|
||||
public function goToStep2(): void
|
||||
{
|
||||
if (! $this->supplierId) {
|
||||
Notification::make()->title('Selectează furnizorul')->danger()->send();
|
||||
Notification::make()->title(__('Selectează furnizorul'))->danger()->send();
|
||||
return;
|
||||
}
|
||||
if (! $this->upload) {
|
||||
Notification::make()->title('Încarcă fișierul Excel sau CSV')->danger()->send();
|
||||
Notification::make()->title(__('Încarcă fișierul Excel sau CSV'))->danger()->send();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -96,7 +101,7 @@ class ExcelImportWizard extends Page
|
||||
$this->previewSummary = $result['summary'];
|
||||
|
||||
if (empty($this->previewRows)) {
|
||||
Notification::make()->title('Nu am găsit linii valide — verifică maparea coloanelor')->warning()->send();
|
||||
Notification::make()->title(__('Nu am găsit linii valide — verifică maparea coloanelor'))->warning()->send();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,12 @@ class Finance extends Page
|
||||
|
||||
protected static ?int $navigationSort = 49;
|
||||
|
||||
protected static ?string $title = 'Finanțe';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('Finanțe');
|
||||
}
|
||||
|
||||
protected string $view = 'filament.tenant.pages.finance';
|
||||
|
||||
|
||||
@@ -22,7 +22,12 @@ class Integrations extends Page
|
||||
|
||||
protected static ?int $navigationSort = 63;
|
||||
|
||||
protected static ?string $title = 'Integrări externe';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('Integrări externe');
|
||||
}
|
||||
|
||||
protected string $view = 'filament.tenant.pages.integrations';
|
||||
|
||||
@@ -54,7 +59,7 @@ class Integrations extends Page
|
||||
'integrations' => $this->config,
|
||||
]),
|
||||
]);
|
||||
Notification::make()->title('Integrări salvate')->success()->send();
|
||||
Notification::make()->title(__('Integrări salvate'))->success()->send();
|
||||
}
|
||||
|
||||
public function integrations(): array
|
||||
|
||||
@@ -28,7 +28,12 @@ class MechanicBoard extends Page
|
||||
|
||||
protected static ?int $navigationSort = 25;
|
||||
|
||||
protected static ?string $title = 'Atelierul meu';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('Atelierul meu');
|
||||
}
|
||||
|
||||
protected string $view = 'filament.tenant.pages.mechanic-board';
|
||||
|
||||
@@ -46,25 +51,25 @@ class MechanicBoard extends Page
|
||||
return [
|
||||
[
|
||||
'key' => 'in_work',
|
||||
'label' => 'În lucru',
|
||||
'label' => __('În lucru'),
|
||||
'color' => '#f59e0b',
|
||||
'items' => $all->where('status', 'in_work')->values(),
|
||||
],
|
||||
[
|
||||
'key' => 'awaiting_parts',
|
||||
'label' => 'Așteaptă piese',
|
||||
'label' => __('Așteaptă piese'),
|
||||
'color' => '#8b5cf6',
|
||||
'items' => $all->whereIn('status', ['awaiting_parts'])->values(),
|
||||
],
|
||||
[
|
||||
'key' => 'ready',
|
||||
'label' => 'Gata',
|
||||
'label' => __('Gata'),
|
||||
'color' => '#10b981',
|
||||
'items' => $all->where('status', 'ready')->values(),
|
||||
],
|
||||
[
|
||||
'key' => 'recent',
|
||||
'label' => 'Recente / restul',
|
||||
'label' => __('Recente / restul'),
|
||||
'color' => '#64748b',
|
||||
'items' => $all->whereIn('status', ['done', 'approved', 'diagnosis'])
|
||||
->take(20)
|
||||
@@ -119,7 +124,7 @@ class MechanicBoard extends Page
|
||||
return;
|
||||
}
|
||||
$work->block($this->blockReason, trim($this->blockNote) ?: null);
|
||||
Notification::make()->title('Lucrare blocată')->body($work->name . ' · ' . WorkOrderWork::BLOCK_REASONS[$this->blockReason])->warning()->send();
|
||||
Notification::make()->title(__('Lucrare blocată'))->body($work->name . ' · ' . WorkOrderWork::BLOCK_REASONS[$this->blockReason])->warning()->send();
|
||||
$this->blockingWorkId = null;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,12 @@ class MechanicKpi extends Page
|
||||
|
||||
protected static ?int $navigationSort = 28;
|
||||
|
||||
protected static ?string $title = 'KPI mecanici';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('KPI mecanici');
|
||||
}
|
||||
|
||||
protected string $view = 'filament.tenant.pages.mechanic-kpi';
|
||||
|
||||
|
||||
@@ -19,7 +19,12 @@ class Onboarding extends Page
|
||||
|
||||
protected static bool $shouldRegisterNavigation = false;
|
||||
|
||||
protected static ?string $title = 'Bun venit în AutoCRM!';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('Bun venit în AutoCRM!');
|
||||
}
|
||||
|
||||
protected string $view = 'filament.tenant.pages.onboarding';
|
||||
|
||||
@@ -54,18 +59,18 @@ class Onboarding extends Page
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Schemas\Components\Section::make('Pas 1 — Datele afacerii')
|
||||
Schemas\Components\Section::make(__('Pas 1 — Datele afacerii'))
|
||||
->visible(fn () => $this->step === 1)
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('display_name')
|
||||
->label('Denumire afișată')->required()->maxLength(120),
|
||||
->label(__('Denumire afișată'))->required()->maxLength(120),
|
||||
Forms\Components\TextInput::make('city')
|
||||
->label('Oraș')->maxLength(60),
|
||||
->label(__('Oraș'))->maxLength(60),
|
||||
Forms\Components\TextInput::make('phone')
|
||||
->label('Telefon principal')->tel()->maxLength(40),
|
||||
->label(__('Telefon principal'))->tel()->maxLength(40),
|
||||
Forms\Components\Select::make('currency')
|
||||
->label('Monedă')
|
||||
->label(__('Monedă'))
|
||||
->options([
|
||||
'MDL' => 'MDL — Leu moldovenesc',
|
||||
'EUR' => 'EUR — Euro',
|
||||
@@ -78,28 +83,28 @@ class Onboarding extends Page
|
||||
->required()
|
||||
->searchable(),
|
||||
]),
|
||||
Schemas\Components\Section::make('Pas 2 — Brand & limbă')
|
||||
Schemas\Components\Section::make(__('Pas 2 — Brand & limbă'))
|
||||
->visible(fn () => $this->step === 2)
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Select::make('language')
|
||||
->label('Limbă')
|
||||
->label(__('Limbă'))
|
||||
->options(['ro' => 'Română', 'ru' => 'Русский', 'en' => 'English'])
|
||||
->required(),
|
||||
Forms\Components\ColorPicker::make('theme_color')
|
||||
->label('Culoare brand'),
|
||||
->label(__('Culoare brand')),
|
||||
Forms\Components\FileUpload::make('logo')
|
||||
->label('Logo (opțional)')
|
||||
->label(__('Logo (opțional)'))
|
||||
->image()->imageEditor()->disk('public')
|
||||
->directory('tmp-uploads')->visibility('public')
|
||||
->maxSize(2048),
|
||||
]),
|
||||
Schemas\Components\Section::make('Pas 3 — Tarif & terminat')
|
||||
Schemas\Components\Section::make(__('Pas 3 — Tarif & terminat'))
|
||||
->visible(fn () => $this->step === 3)
|
||||
->columns(1)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('labor_rate')
|
||||
->label('Tarif normo-oră (poți schimba oricând)')
|
||||
->label(__('Tarif normo-oră (poți schimba oricând)'))
|
||||
->numeric()->required(),
|
||||
]),
|
||||
])
|
||||
@@ -147,8 +152,8 @@ class Onboarding extends Page
|
||||
}
|
||||
|
||||
Notification::make()
|
||||
->title('🎉 Bun venit în AutoCRM!')
|
||||
->body('Setările au fost salvate. Hai să adăugăm primul client.')
|
||||
->title(__('🎉 Bun venit în AutoCRM!'))
|
||||
->body(__('Setările au fost salvate. Hai să adăugăm primul client.'))
|
||||
->success()
|
||||
->send();
|
||||
|
||||
|
||||
@@ -27,7 +27,12 @@ class PipelineBoard extends Page
|
||||
|
||||
protected static ?int $navigationSort = 5;
|
||||
|
||||
protected static ?string $title = 'Pipeline';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('Pipeline');
|
||||
}
|
||||
|
||||
protected string $view = 'filament.tenant.pages.pipeline-board';
|
||||
|
||||
|
||||
@@ -23,7 +23,12 @@ class Recommendations extends Page
|
||||
|
||||
protected static ?int $navigationSort = 72;
|
||||
|
||||
protected static ?string $title = 'Recomandări — clienți de urmărit';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('Recomandări — clienți de urmărit');
|
||||
}
|
||||
|
||||
protected string $view = 'filament.tenant.pages.recommendations';
|
||||
|
||||
|
||||
@@ -30,7 +30,12 @@ class Reports extends Page
|
||||
|
||||
protected static ?int $navigationSort = 70;
|
||||
|
||||
protected static ?string $title = 'Rapoarte';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('Rapoarte');
|
||||
}
|
||||
|
||||
protected string $view = 'filament.tenant.pages.reports';
|
||||
|
||||
|
||||
@@ -33,7 +33,12 @@ class Scanner extends Page
|
||||
|
||||
protected static ?int $navigationSort = 39;
|
||||
|
||||
protected static ?string $title = 'Scaner cod QR / Bare';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('Scaner cod QR / Bare');
|
||||
}
|
||||
|
||||
protected string $view = 'filament.tenant.pages.scanner';
|
||||
|
||||
|
||||
@@ -27,7 +27,12 @@ class Settings extends Page
|
||||
|
||||
protected static ?int $navigationSort = 90;
|
||||
|
||||
protected static ?string $title = 'Setări companie';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('Setări companie');
|
||||
}
|
||||
|
||||
protected string $view = 'filament.tenant.pages.settings';
|
||||
|
||||
@@ -81,23 +86,23 @@ class Settings extends Page
|
||||
{
|
||||
return $schema
|
||||
->components([
|
||||
Schemas\Components\Section::make('Brand & contact')
|
||||
Schemas\Components\Section::make(__('Brand & contact'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('display_name')->label('Denumire afișată')->maxLength(120),
|
||||
Forms\Components\TextInput::make('city')->label('Oraș')->maxLength(60),
|
||||
Forms\Components\TextInput::make('phone')->label('Telefon')->tel()->maxLength(40),
|
||||
Forms\Components\TextInput::make('display_name')->label(__('Denumire afișată'))->maxLength(120),
|
||||
Forms\Components\TextInput::make('city')->label(__('Oraș'))->maxLength(60),
|
||||
Forms\Components\TextInput::make('phone')->label(__('Telefon'))->tel()->maxLength(40),
|
||||
Forms\Components\TextInput::make('email')->email()->maxLength(120),
|
||||
]),
|
||||
Schemas\Components\Section::make('Localizare & monedă')
|
||||
Schemas\Components\Section::make(__('Localizare & monedă'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
Forms\Components\Select::make('language')
|
||||
->label('Limbă default')
|
||||
->label(__('Limbă default'))
|
||||
->options(['ro' => 'Română', 'ru' => 'Русский', 'en' => 'English'])
|
||||
->required(),
|
||||
Forms\Components\Select::make('currency')
|
||||
->label('Monedă')
|
||||
->label(__('Monedă'))
|
||||
->options([
|
||||
'MDL' => 'MDL — Leu moldovenesc',
|
||||
'EUR' => 'EUR — Euro',
|
||||
@@ -108,133 +113,133 @@ class Settings extends Page
|
||||
])
|
||||
->required()
|
||||
->searchable(),
|
||||
Forms\Components\ColorPicker::make('theme_color')->label('Culoare brand'),
|
||||
Forms\Components\ColorPicker::make('theme_color')->label(__('Culoare brand')),
|
||||
]),
|
||||
Schemas\Components\Section::make('Servicii & tarif')
|
||||
Schemas\Components\Section::make(__('Servicii & tarif'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('labor_rate')->label('Tarif normo-oră')->numeric()->required(),
|
||||
Forms\Components\TextInput::make('labor_rate')->label(__('Tarif normo-oră'))->numeric()->required(),
|
||||
]),
|
||||
Schemas\Components\Section::make('Marjă internă (nu TVA)')
|
||||
->description('Procent implicit aplicat la manopere. Se scade din prețul de manoperă pentru a determina baza salariului mecanicului. Vizibilă doar rolurilor cu drept „finance.view_internal_margin".')
|
||||
Schemas\Components\Section::make(__('Marjă internă (nu TVA)'))
|
||||
->description(__('Procent implicit aplicat la manopere. Se scade din prețul de manoperă pentru a determina baza salariului mecanicului. Vizibilă doar rolurilor cu drept „finance.view_internal_margin".'))
|
||||
->columns(2)
|
||||
->visible(fn () => auth()->user()?->canDo(\App\Auth\Permissions::FINANCE_VIEW_INTERNAL_MARGIN) ?? false)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('default_internal_margin_pct')
|
||||
->label('% marjă implicit')
|
||||
->label(__('% marjă implicit'))
|
||||
->numeric()
|
||||
->step(0.01)
|
||||
->minValue(0)
|
||||
->maxValue(90)
|
||||
->suffix('%')
|
||||
->placeholder('Ex: 20')
|
||||
->helperText('Fallback când mecanicul nu are marja proprie setată. Lasă gol = 0%.'),
|
||||
->placeholder(__('Ex: 20'))
|
||||
->helperText(__('Fallback când mecanicul nu are marja proprie setată. Lasă gol = 0%.')),
|
||||
Forms\Components\Toggle::make('show_internal_margin_details')
|
||||
->label('Afișează detalii marjă la procesele calculate')
|
||||
->helperText('On = arată „Bază salariu: X · marjă Y%" sub Total în tabelul Manopere. Off = doar Total (util când review-uiești o Fișă cu clientul de față).')
|
||||
->label(__('Afișează detalii marjă la procesele calculate'))
|
||||
->helperText(__('On = arată „Bază salariu: X · marjă Y%" sub Total în tabelul Manopere. Off = doar Total (util când review-uiești o Fișă cu clientul de față).'))
|
||||
->default(true),
|
||||
]),
|
||||
Schemas\Components\Section::make('Liste configurabile')
|
||||
Schemas\Components\Section::make(__('Liste configurabile'))
|
||||
->columns(1)
|
||||
->schema([
|
||||
Forms\Components\Textarea::make('services')
|
||||
->label('Servicii oferite (separate prin virgulă)')
|
||||
->label(__('Servicii oferite (separate prin virgulă)'))
|
||||
->rows(2),
|
||||
Forms\Components\Textarea::make('cars')
|
||||
->label('Mărci auto suportate (separate prin virgulă)')
|
||||
->label(__('Mărci auto suportate (separate prin virgulă)'))
|
||||
->rows(2),
|
||||
]),
|
||||
Schemas\Components\Section::make('Logo & favicon')
|
||||
Schemas\Components\Section::make(__('Logo & favicon'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\FileUpload::make('logo')
|
||||
->label('Logo')
|
||||
->label(__('Logo'))
|
||||
->image()
|
||||
->imageEditor()
|
||||
->disk('public')
|
||||
->directory('tmp-uploads')
|
||||
->visibility('public')
|
||||
->maxSize(2048)
|
||||
->helperText('PNG/SVG, max 2 MB. Apare în sidebar.'),
|
||||
->helperText(__('PNG/SVG, max 2 MB. Apare în sidebar.')),
|
||||
Forms\Components\FileUpload::make('favicon')
|
||||
->label('Favicon')
|
||||
->label(__('Favicon'))
|
||||
->image()
|
||||
->disk('public')
|
||||
->directory('tmp-uploads')
|
||||
->visibility('public')
|
||||
->maxSize(512)
|
||||
->helperText('PNG/ICO, max 512 KB.'),
|
||||
->helperText(__('PNG/ICO, max 512 KB.')),
|
||||
]),
|
||||
Schemas\Components\Section::make('Notificări')
|
||||
->description('Activează / dezactivează notificările auto către clienți. Telegram are prioritate dacă clientul are cont legat.')
|
||||
Schemas\Components\Section::make(__('Notificări'))
|
||||
->description(__('Activează / dezactivează notificările auto către clienți. Telegram are prioritate dacă clientul are cont legat.'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Toggle::make('notify_wo_ready')->label('Mașina e gata de ridicat')->default(true),
|
||||
Forms\Components\Toggle::make('notify_payment')->label('Confirmare plată primită')->default(true),
|
||||
Forms\Components\Toggle::make('notify_appointment')->label('Programare confirmată')->default(true),
|
||||
Forms\Components\Toggle::make('notify_reminder')->label('Reminder ITP / revizie')->default(true),
|
||||
Forms\Components\Toggle::make('notify_wo_ready')->label(__('Mașina e gata de ridicat'))->default(true),
|
||||
Forms\Components\Toggle::make('notify_payment')->label(__('Confirmare plată primită'))->default(true),
|
||||
Forms\Components\Toggle::make('notify_appointment')->label(__('Programare confirmată'))->default(true),
|
||||
Forms\Components\Toggle::make('notify_reminder')->label(__('Reminder ITP / revizie'))->default(true),
|
||||
]),
|
||||
Schemas\Components\Section::make('Telegram bot')
|
||||
->description('Creează un bot la @BotFather, lipește token-ul aici și apasă „Setează webhook". Clienții îți scriu la bot, partajează telefonul, iar codul se leagă automat de fișa lor.')
|
||||
Schemas\Components\Section::make(__('Telegram bot'))
|
||||
->description(__('Creează un bot la @BotFather, lipește token-ul aici și apasă „Setează webhook". Clienții îți scriu la bot, partajează telefonul, iar codul se leagă automat de fișa lor.'))
|
||||
->columns(1)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('telegram_bot_token')
|
||||
->label('Bot token')
|
||||
->label(__('Bot token'))
|
||||
->password()
|
||||
->revealable()
|
||||
->placeholder('123456:ABC-XYZ...')
|
||||
->placeholder(__('123456:ABC-XYZ...'))
|
||||
->helperText(fn () => 'Webhook URL: ' .
|
||||
app(\App\Services\Notifications\TelegramService::class)
|
||||
->webhookUrlFor(app(\App\Tenancy\TenantManager::class)->current())),
|
||||
]),
|
||||
Schemas\Components\Section::make('Reminder service auto')
|
||||
Schemas\Components\Section::make(__('Reminder service auto'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('reminder_after_days')
|
||||
->label('Trimite reminder după X zile fără vizită')
|
||||
->label(__('Trimite reminder după X zile fără vizită'))
|
||||
->numeric()
|
||||
->minValue(30)
|
||||
->default(365),
|
||||
Forms\Components\TextInput::make('reminder_cooldown_days')
|
||||
->label('Nu re-trimite mai des de X zile')
|
||||
->label(__('Nu re-trimite mai des de X zile'))
|
||||
->numeric()
|
||||
->minValue(7)
|
||||
->default(30),
|
||||
]),
|
||||
Schemas\Components\Section::make('Magazin online')
|
||||
->description('Activează magazinul public la <slug>.service.mir.md/shop. Piesele apar doar dacă sunt marcate „Publicat".')
|
||||
Schemas\Components\Section::make(__('Magazin online'))
|
||||
->description(__('Activează magazinul public la <slug>.service.mir.md/shop. Piesele apar doar dacă sunt marcate „Publicat".'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Toggle::make('shop_enabled')->label('Magazin activ')->columnSpanFull(),
|
||||
Forms\Components\Toggle::make('shop_enabled')->label(__('Magazin activ'))->columnSpanFull(),
|
||||
Forms\Components\CheckboxList::make('shop_delivery_methods')
|
||||
->label('Metode de livrare')
|
||||
->label(__('Metode de livrare'))
|
||||
->options(\App\Models\Tenant\OnlineOrder::DELIVERY)
|
||||
->default(['pickup'])
|
||||
->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('shop_delivery_fee')->label('Taxă livrare')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('shop_free_delivery_over')->label('Livrare gratuită peste')->numeric()->default(0)->helperText('0 = dezactivat'),
|
||||
Forms\Components\TextInput::make('shop_delivery_fee')->label(__('Taxă livrare'))->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('shop_free_delivery_over')->label(__('Livrare gratuită peste'))->numeric()->default(0)->helperText(__('0 = dezactivat')),
|
||||
]),
|
||||
Schemas\Components\Section::make('Asistent AI')
|
||||
->description('Adaugă chei API ca să activezi asistentul. Cheile rămân la voi — nu sunt partajate.')
|
||||
Schemas\Components\Section::make(__('Asistent AI'))
|
||||
->description(__('Adaugă chei API ca să activezi asistentul. Cheile rămân la voi — nu sunt partajate.'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Select::make('ai_default_provider')
|
||||
->label('Provider implicit')
|
||||
->label(__('Provider implicit'))
|
||||
->options(['claude' => 'Claude (Anthropic)', 'gpt' => 'ChatGPT (OpenAI)', 'gemini' => 'Gemini (Google)'])
|
||||
->default('claude'),
|
||||
Forms\Components\TextInput::make('ai_claude_key')->label('Claude API Key')->password()->revealable()->placeholder('sk-ant-...'),
|
||||
Forms\Components\TextInput::make('ai_claude_key')->label(__('Claude API Key'))->password()->revealable()->placeholder('sk-ant-...'),
|
||||
Forms\Components\Select::make('ai_model_claude')
|
||||
->label('Model Claude')
|
||||
->label(__('Model Claude'))
|
||||
->options(\App\Services\Ai\AiAssistantService::MODEL_OPTIONS['claude'])
|
||||
->default(\App\Services\Ai\AiAssistantService::MODEL_DEFAULTS['claude']),
|
||||
Forms\Components\TextInput::make('ai_gpt_key')->label('OpenAI API Key')->password()->revealable()->placeholder('sk-proj-...'),
|
||||
Forms\Components\TextInput::make('ai_gpt_key')->label(__('OpenAI API Key'))->password()->revealable()->placeholder('sk-proj-...'),
|
||||
Forms\Components\Select::make('ai_model_gpt')
|
||||
->label('Model OpenAI')
|
||||
->label(__('Model OpenAI'))
|
||||
->options(\App\Services\Ai\AiAssistantService::MODEL_OPTIONS['gpt'])
|
||||
->default(\App\Services\Ai\AiAssistantService::MODEL_DEFAULTS['gpt']),
|
||||
Forms\Components\TextInput::make('ai_gemini_key')->label('Gemini API Key')->password()->revealable(),
|
||||
Forms\Components\TextInput::make('ai_gemini_key')->label(__('Gemini API Key'))->password()->revealable(),
|
||||
Forms\Components\Select::make('ai_model_gemini')
|
||||
->label('Model Gemini')
|
||||
->label(__('Model Gemini'))
|
||||
->options(\App\Services\Ai\AiAssistantService::MODEL_OPTIONS['gemini'])
|
||||
->default(\App\Services\Ai\AiAssistantService::MODEL_DEFAULTS['gemini']),
|
||||
]),
|
||||
@@ -247,7 +252,7 @@ class Settings extends Page
|
||||
$data = $this->form->getState();
|
||||
$company = app(TenantManager::class)->current();
|
||||
if (! $company) {
|
||||
Notification::make()->title('Tenant not resolved')->danger()->send();
|
||||
Notification::make()->title(__('Tenant not resolved'))->danger()->send();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -312,22 +317,22 @@ class Settings extends Page
|
||||
}
|
||||
}
|
||||
|
||||
Notification::make()->title('Setări salvate')->success()->send();
|
||||
Notification::make()->title(__('Setări salvate'))->success()->send();
|
||||
}
|
||||
|
||||
protected function getHeaderActions(): array
|
||||
{
|
||||
return [
|
||||
Actions\Action::make('push_test')
|
||||
->label('Test notificare push')
|
||||
->label(__('Test notificare push'))
|
||||
->icon('heroicon-m-bell-alert')
|
||||
->color('gray')
|
||||
->action(function () {
|
||||
$svc = app(\App\Services\Notifications\WebPushService::class);
|
||||
if (! $svc->configured()) {
|
||||
Notification::make()
|
||||
->title('Web Push neconfigurat')
|
||||
->body('Rulează `php artisan push:vapid` și adaugă cheile în .env.')
|
||||
->title(__('Web Push neconfigurat'))
|
||||
->body(__('Rulează `php artisan push:vapid` și adaugă cheile în .env.'))
|
||||
->warning()->send();
|
||||
return;
|
||||
}
|
||||
@@ -344,7 +349,7 @@ class Settings extends Page
|
||||
->send();
|
||||
}),
|
||||
Actions\Action::make('telegram_test')
|
||||
->label('Testează bot Telegram')
|
||||
->label(__('Testează bot Telegram'))
|
||||
->icon('heroicon-m-bolt')
|
||||
->color('gray')
|
||||
->action(function () {
|
||||
@@ -353,7 +358,7 @@ class Settings extends Page
|
||||
$r = app(TelegramService::class)->getMe($company);
|
||||
if (! ($r['ok'] ?? false)) {
|
||||
Notification::make()
|
||||
->title('Bot Telegram nu răspunde')
|
||||
->title(__('Bot Telegram nu răspunde'))
|
||||
->body($r['error'] ?? 'Verifică token-ul.')
|
||||
->danger()->send();
|
||||
return;
|
||||
@@ -364,24 +369,24 @@ class Settings extends Page
|
||||
->success()->send();
|
||||
}),
|
||||
Actions\Action::make('telegram_webhook')
|
||||
->label('Setează webhook')
|
||||
->label(__('Setează webhook'))
|
||||
->icon('heroicon-m-link')
|
||||
->color('primary')
|
||||
->requiresConfirmation()
|
||||
->modalDescription('Telegram va trimite mesajele primite la URL-ul webhook de mai jos.')
|
||||
->modalDescription(__('Telegram va trimite mesajele primite la URL-ul webhook de mai jos.'))
|
||||
->action(function () {
|
||||
$company = app(TenantManager::class)->current();
|
||||
if (! $company) return;
|
||||
$r = app(TelegramService::class)->setWebhook($company);
|
||||
if (! ($r['ok'] ?? false)) {
|
||||
Notification::make()
|
||||
->title('Webhook eșuat')
|
||||
->title(__('Webhook eșuat'))
|
||||
->body($r['error'] ?? json_encode($r['response'] ?? []))
|
||||
->danger()->send();
|
||||
return;
|
||||
}
|
||||
Notification::make()
|
||||
->title('Webhook setat — botul e gata')
|
||||
->title(__('Webhook setat — botul e gata'))
|
||||
->success()->send();
|
||||
}),
|
||||
];
|
||||
|
||||
@@ -23,7 +23,12 @@ class VinSearch extends Page
|
||||
|
||||
protected static ?int $navigationSort = 41;
|
||||
|
||||
protected static ?string $title = 'VIN căutare & piese';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('VIN căutare & piese');
|
||||
}
|
||||
|
||||
protected string $view = 'filament.tenant.pages.vin-search';
|
||||
|
||||
|
||||
@@ -23,7 +23,12 @@ class Workload extends Page
|
||||
|
||||
protected static ?int $navigationSort = 73;
|
||||
|
||||
protected static ?string $title = 'Încărcare service';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public function getTitle(): string
|
||||
{
|
||||
return __('Încărcare service');
|
||||
}
|
||||
|
||||
protected string $view = 'filament.tenant.pages.workload';
|
||||
|
||||
|
||||
@@ -48,8 +48,8 @@ class ActivityResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('created_at')->label('Când')->dateTime('d.m.Y H:i')->sortable(),
|
||||
Tables\Columns\TextColumn::make('description')->label('Acțiune')->badge()
|
||||
Tables\Columns\TextColumn::make('created_at')->label(__('Când'))->dateTime('d.m.Y H:i')->sortable(),
|
||||
Tables\Columns\TextColumn::make('description')->label(__('Acțiune'))->badge()
|
||||
->colors([
|
||||
'success' => ['creat'],
|
||||
'info' => ['modificat'],
|
||||
@@ -57,12 +57,12 @@ class ActivityResource extends Resource
|
||||
'warning' => ['restaurat'],
|
||||
]),
|
||||
Tables\Columns\TextColumn::make('subject_type')
|
||||
->label('Tip')
|
||||
->label(__('Tip'))
|
||||
->formatStateUsing(fn ($s) => $s ? class_basename($s) : '—'),
|
||||
Tables\Columns\TextColumn::make('subject_id')->label('ID')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('causer.name')->label('De către')->placeholder('Sistem'),
|
||||
Tables\Columns\TextColumn::make('subject_id')->label(__('ID'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('causer.name')->label(__('De către'))->placeholder(__('Sistem')),
|
||||
Tables\Columns\TextColumn::make('attribute_changes')
|
||||
->label('Detalii')
|
||||
->label(__('Detalii'))
|
||||
->formatStateUsing(function ($state) {
|
||||
if (! $state) return '—';
|
||||
$arr = is_string($state) ? json_decode($state, true) : $state;
|
||||
@@ -75,10 +75,10 @@ class ActivityResource extends Resource
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('description')
|
||||
->label('Acțiune')
|
||||
->label(__('Acțiune'))
|
||||
->options(['creat' => 'creat', 'modificat' => 'modificat', 'șters' => 'șters']),
|
||||
Tables\Filters\Filter::make('today')
|
||||
->label('Astăzi')
|
||||
->label(__('Astăzi'))
|
||||
->query(fn ($q) => $q->whereDate('created_at', today())),
|
||||
])
|
||||
->defaultSort('created_at', 'desc')
|
||||
|
||||
@@ -34,25 +34,30 @@ class AppointmentResource extends Resource
|
||||
|
||||
protected static ?string $modelLabel = 'programare';
|
||||
|
||||
protected static ?string $pluralModelLabel = 'programări';
|
||||
protected static ?string $pluralModelLabel = null;
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('programări');
|
||||
}
|
||||
|
||||
protected static ?int $navigationSort = 7;
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Când & unde')
|
||||
Schemas\Components\Section::make(__('Când & unde'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
Forms\Components\DatePicker::make('date')->label('Data')->default(today())->required(),
|
||||
Forms\Components\TimePicker::make('time_start')->label('De la')->required()->seconds(false),
|
||||
Forms\Components\TimePicker::make('time_end')->label('Până la')->required()->seconds(false),
|
||||
Forms\Components\DatePicker::make('date')->label(__('Data'))->default(today())->required(),
|
||||
Forms\Components\TimePicker::make('time_start')->label(__('De la'))->required()->seconds(false),
|
||||
Forms\Components\TimePicker::make('time_end')->label(__('Până la'))->required()->seconds(false),
|
||||
Forms\Components\Select::make('post_id')
|
||||
->label('Pod')
|
||||
->label(__('Pod'))
|
||||
->options(fn () => Post::where('is_active', true)->orderBy('sort_order')->pluck('name', 'id'))
|
||||
->searchable(),
|
||||
Forms\Components\Select::make('master_id')
|
||||
->label('Maistru / Mecanic')
|
||||
->label(__('Maistru / Mecanic'))
|
||||
->options(fn () => User::pluck('name', 'id'))
|
||||
->searchable(),
|
||||
Forms\Components\Select::make('status')
|
||||
@@ -60,23 +65,23 @@ class AppointmentResource extends Resource
|
||||
->default('scheduled')
|
||||
->required(),
|
||||
]),
|
||||
Schemas\Components\Section::make('Client & Auto')
|
||||
Schemas\Components\Section::make(__('Client & Auto'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Select::make('client_id')
|
||||
->label('Client')
|
||||
->label(__('Client'))
|
||||
->options(fn () => Client::pluck('name', 'id'))
|
||||
->searchable()
|
||||
->live(),
|
||||
Forms\Components\Select::make('vehicle_id')
|
||||
->label('Auto')
|
||||
->label(__('Auto'))
|
||||
->options(fn (Schemas\Components\Utilities\Get $get) => $get('client_id')
|
||||
? Vehicle::where('client_id', $get('client_id'))->pluck('plate', 'id')
|
||||
: [])
|
||||
->searchable(),
|
||||
]),
|
||||
Forms\Components\TextInput::make('title')->label('Subiect')->required()->maxLength(160),
|
||||
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(2),
|
||||
Forms\Components\TextInput::make('title')->label(__('Subiect'))->required()->maxLength(160),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(2),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -84,14 +89,14 @@ class AppointmentResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('date')->label('Data')->date('d.m.Y')->sortable(),
|
||||
Tables\Columns\TextColumn::make('time_start')->label('De la')->time('H:i'),
|
||||
Tables\Columns\TextColumn::make('time_end')->label('Până la')->time('H:i'),
|
||||
Tables\Columns\TextColumn::make('post.name')->label('Pod')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('title')->label('Subiect')->searchable()->limit(40),
|
||||
Tables\Columns\TextColumn::make('client.name')->label('Client')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('vehicle.plate')->label('Auto')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('master.name')->label('Maistru')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('date')->label(__('Data'))->date('d.m.Y')->sortable(),
|
||||
Tables\Columns\TextColumn::make('time_start')->label(__('De la'))->time('H:i'),
|
||||
Tables\Columns\TextColumn::make('time_end')->label(__('Până la'))->time('H:i'),
|
||||
Tables\Columns\TextColumn::make('post.name')->label(__('Pod'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('title')->label(__('Subiect'))->searchable()->limit(40),
|
||||
Tables\Columns\TextColumn::make('client.name')->label(__('Client'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('vehicle.plate')->label(__('Auto'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('master.name')->label(__('Maistru'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('status')
|
||||
->formatStateUsing(fn ($state) => Appointment::STATUSES[$state] ?? $state)
|
||||
->badge()
|
||||
@@ -104,14 +109,14 @@ class AppointmentResource extends Resource
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\Filter::make('today')
|
||||
->label('Astăzi')
|
||||
->label(__('Astăzi'))
|
||||
->query(fn ($q) => $q->whereDate('date', today())),
|
||||
Tables\Filters\Filter::make('upcoming')
|
||||
->label('Viitoare')
|
||||
->label(__('Viitoare'))
|
||||
->query(fn ($q) => $q->where('date', '>=', today())),
|
||||
Tables\Filters\SelectFilter::make('status')->options(Appointment::STATUSES),
|
||||
Tables\Filters\SelectFilter::make('post_id')
|
||||
->label('Pod')
|
||||
->label(__('Pod'))
|
||||
->options(fn () => Post::pluck('name', 'id')),
|
||||
])
|
||||
->actions([
|
||||
|
||||
@@ -32,10 +32,6 @@ class BodyshopJobResource extends Resource
|
||||
return __('nav.group.Tinichigerie');
|
||||
}
|
||||
|
||||
protected static ?string $modelLabel = 'lucrare caroserie';
|
||||
|
||||
protected static ?string $pluralModelLabel = 'lucrări caroserie';
|
||||
|
||||
protected static ?int $navigationSort = 80;
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
@@ -48,50 +44,50 @@ class BodyshopJobResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Lucrare')
|
||||
Schemas\Components\Section::make(__('Lucrare'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('number')->label('Nr.')->disabled()->dehydrated(false)->placeholder('Generat automat'),
|
||||
Forms\Components\Select::make('type')->label('Tip')->options(BodyshopJob::TYPES)->default('body_repair')->required(),
|
||||
Forms\Components\Select::make('status')->label('Status')->options(BodyshopJob::STATUSES)->default('estimate')->required(),
|
||||
Forms\Components\TextInput::make('number')->label(__('Nr.'))->disabled()->dehydrated(false)->placeholder(__('Generat automat')),
|
||||
Forms\Components\Select::make('type')->label(__('Tip'))->options(BodyshopJob::TYPES)->default('body_repair')->required(),
|
||||
Forms\Components\Select::make('status')->label(__('Status'))->options(BodyshopJob::STATUSES)->default('estimate')->required(),
|
||||
Forms\Components\Select::make('client_id')
|
||||
->label('Client')
|
||||
->label(__('Client'))
|
||||
->options(fn () => Client::pluck('name', 'id'))
|
||||
->searchable()->live(),
|
||||
Forms\Components\Select::make('vehicle_id')
|
||||
->label('Auto')
|
||||
->label(__('Auto'))
|
||||
->options(fn (Get $get) => $get('client_id')
|
||||
? Vehicle::where('client_id', $get('client_id'))->get()
|
||||
->mapWithKeys(fn ($v) => [$v->id => "{$v->make} {$v->model} {$v->plate}"])->toArray()
|
||||
: [])
|
||||
->searchable(),
|
||||
Forms\Components\TextInput::make('estimate_amount')->label('Deviz')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('approved_amount')->label('Aprobat')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('estimate_amount')->label(__('Deviz'))->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('approved_amount')->label(__('Aprobat'))->numeric()->default(0),
|
||||
]),
|
||||
Schemas\Components\Section::make('Asigurare')
|
||||
Schemas\Components\Section::make(__('Asigurare'))
|
||||
->collapsible()
|
||||
->columns(3)
|
||||
->schema([
|
||||
Forms\Components\Toggle::make('is_insurance')->label('Caz de asigurare')->live()->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('insurer')->label('Asigurător')
|
||||
Forms\Components\Toggle::make('is_insurance')->label(__('Caz de asigurare'))->live()->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('insurer')->label(__('Asigurător'))
|
||||
->visible(fn (Get $get) => $get('is_insurance')),
|
||||
Forms\Components\TextInput::make('policy_no')->label('Nr. poliță')
|
||||
Forms\Components\TextInput::make('policy_no')->label(__('Nr. poliță'))
|
||||
->visible(fn (Get $get) => $get('is_insurance')),
|
||||
Forms\Components\TextInput::make('claim_no')->label('Nr. dosar daună')
|
||||
Forms\Components\TextInput::make('claim_no')->label(__('Nr. dosar daună'))
|
||||
->visible(fn (Get $get) => $get('is_insurance')),
|
||||
Forms\Components\Select::make('insurance_status')->label('Status dosar')
|
||||
Forms\Components\Select::make('insurance_status')->label(__('Status dosar'))
|
||||
->options(BodyshopJob::INSURANCE_STATUSES)
|
||||
->visible(fn (Get $get) => $get('is_insurance')),
|
||||
]),
|
||||
Schemas\Components\Section::make('Foto înainte / după')
|
||||
Schemas\Components\Section::make(__('Foto înainte / după'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
\Filament\Forms\Components\SpatieMediaLibraryFileUpload::make('photos_before')
|
||||
->label('Înainte')->collection('photos_before')->multiple()->image()->reorderable()->maxFiles(20),
|
||||
->label(__('Înainte'))->collection('photos_before')->multiple()->image()->reorderable()->maxFiles(20),
|
||||
\Filament\Forms\Components\SpatieMediaLibraryFileUpload::make('photos_after')
|
||||
->label('După')->collection('photos_after')->multiple()->image()->reorderable()->maxFiles(20),
|
||||
->label(__('După'))->collection('photos_after')->multiple()->image()->reorderable()->maxFiles(20),
|
||||
]),
|
||||
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -99,15 +95,15 @@ class BodyshopJobResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('number')->label('Nr.')->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('client.name')->label('Client')->searchable()->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('vehicle.plate')->label('Auto')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('number')->label(__('Nr.'))->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('client.name')->label(__('Client'))->searchable()->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('vehicle.plate')->label(__('Auto'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('type')
|
||||
->formatStateUsing(fn ($s) => BodyshopJob::TYPES[$s] ?? $s)
|
||||
->badge()->color('info'),
|
||||
Tables\Columns\IconColumn::make('is_insurance')->label('Asig.')->boolean()->toggleable(),
|
||||
Tables\Columns\TextColumn::make('damage_points_count')->counts('damagePoints')->label('Daune')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('approved_amount')->label('Aprobat')->money('MDL')->alignRight(),
|
||||
Tables\Columns\IconColumn::make('is_insurance')->label(__('Asig.'))->boolean()->toggleable(),
|
||||
Tables\Columns\TextColumn::make('damage_points_count')->counts('damagePoints')->label(__('Daune'))->alignRight(),
|
||||
Tables\Columns\TextColumn::make('approved_amount')->label(__('Aprobat'))->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('status')
|
||||
->formatStateUsing(fn ($s) => BodyshopJob::STATUSES[$s] ?? $s)
|
||||
->badge()
|
||||
@@ -121,14 +117,14 @@ class BodyshopJobResource extends Resource
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('type')->options(BodyshopJob::TYPES),
|
||||
Tables\Filters\SelectFilter::make('status')->options(BodyshopJob::STATUSES),
|
||||
Tables\Filters\TernaryFilter::make('is_insurance')->label('Caz asigurare'),
|
||||
Tables\Filters\TernaryFilter::make('is_insurance')->label(__('Caz asigurare')),
|
||||
])
|
||||
->actions([
|
||||
Actions\EditAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
])
|
||||
->emptyStateHeading('Nicio lucrare de caroserie')
|
||||
->emptyStateDescription('Înregistrează lucrări de tinichigerie, vopsitorie, PDR, detailing, ceramică, PPF sau polish. Hartă daune, dosar asigurare și arhivă foto înainte/după.')
|
||||
->emptyStateHeading(__('Nicio lucrare de caroserie'))
|
||||
->emptyStateDescription(__('Înregistrează lucrări de tinichigerie, vopsitorie, PDR, detailing, ceramică, PPF sau polish. Hartă daune, dosar asigurare și arhivă foto înainte/după.'))
|
||||
->emptyStateIcon('heroicon-o-paint-brush')
|
||||
->defaultSort('created_at', 'desc');
|
||||
}
|
||||
|
||||
+15
-10
@@ -14,26 +14,31 @@ class DamagePointsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'damagePoints';
|
||||
|
||||
protected static ?string $title = 'Hartă daune';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
|
||||
{
|
||||
return __('Hartă daune');
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Forms\Components\Select::make('zone')
|
||||
->label('Zonă')
|
||||
->label(__('Zonă'))
|
||||
->options(array_combine(DamagePoint::ZONES, DamagePoint::ZONES))
|
||||
->searchable()
|
||||
->required(),
|
||||
Forms\Components\Select::make('kind')
|
||||
->label('Tip daună')
|
||||
->label(__('Tip daună'))
|
||||
->options(array_combine(DamagePoint::KINDS, DamagePoint::KINDS))
|
||||
->required(),
|
||||
Forms\Components\Select::make('severity')
|
||||
->label('Gravitate')
|
||||
->label(__('Gravitate'))
|
||||
->options(DamagePoint::SEVERITIES)
|
||||
->default('minor')
|
||||
->required(),
|
||||
Forms\Components\Textarea::make('notes')->label('Observații')->rows(2)->columnSpanFull(),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Observații'))->rows(2)->columnSpanFull(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -42,10 +47,10 @@ class DamagePointsRelationManager extends RelationManager
|
||||
return $table
|
||||
->recordTitleAttribute('zone')
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('zone')->label('Zonă')->badge()->color('gray'),
|
||||
Tables\Columns\TextColumn::make('kind')->label('Tip'),
|
||||
Tables\Columns\TextColumn::make('zone')->label(__('Zonă'))->badge()->color('gray'),
|
||||
Tables\Columns\TextColumn::make('kind')->label(__('Tip')),
|
||||
Tables\Columns\TextColumn::make('severity')
|
||||
->label('Gravitate')
|
||||
->label(__('Gravitate'))
|
||||
->formatStateUsing(fn ($s) => DamagePoint::SEVERITIES[$s] ?? $s)
|
||||
->badge()
|
||||
->colors(['gray' => ['minor'], 'warning' => ['medium'], 'danger' => ['severe']]),
|
||||
@@ -53,7 +58,7 @@ class DamagePointsRelationManager extends RelationManager
|
||||
])
|
||||
->headerActions([Actions\CreateAction::make()])
|
||||
->actions([Actions\EditAction::make(), Actions\DeleteAction::make()])
|
||||
->emptyStateHeading('Nicio daună marcată')
|
||||
->emptyStateDescription('Adaugă punctele de daună pe zone (capotă, ușă, aripă) cu tip și gravitate — formează harta de daune a mașinii.');
|
||||
->emptyStateHeading(__('Nicio daună marcată'))
|
||||
->emptyStateDescription(__('Adaugă punctele de daună pe zone (capotă, ușă, aripă) cu tip și gravitate — formează harta de daune a mașinii.'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,26 +38,26 @@ class CallResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Apel')
|
||||
Schemas\Components\Section::make(__('Apel'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\DateTimePicker::make('called_at')->label('Data & ora')->default(now())->required(),
|
||||
Forms\Components\DateTimePicker::make('called_at')->label(__('Data & ora'))->default(now())->required(),
|
||||
Forms\Components\Select::make('direction')
|
||||
->options(Call::DIRECTIONS)
|
||||
->default('incoming')
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('phone')->label('Telefon')->tel()->required()->maxLength(40),
|
||||
Forms\Components\TextInput::make('phone')->label(__('Telefon'))->tel()->required()->maxLength(40),
|
||||
Forms\Components\Select::make('status')
|
||||
->options(Call::STATUSES)
|
||||
->default('answered')
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('duration_sec')->label('Durată (sec)')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('duration_sec')->label(__('Durată (sec)'))->numeric()->default(0),
|
||||
Forms\Components\Select::make('client_id')
|
||||
->label('Client')
|
||||
->label(__('Client'))
|
||||
->options(fn () => Client::pluck('name', 'id'))
|
||||
->searchable(),
|
||||
]),
|
||||
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(2),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(2),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ class CallResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('called_at')->label('Data')->dateTime('d.m.Y H:i')->sortable(),
|
||||
Tables\Columns\TextColumn::make('called_at')->label(__('Data'))->dateTime('d.m.Y H:i')->sortable(),
|
||||
Tables\Columns\TextColumn::make('direction')
|
||||
->formatStateUsing(fn ($s) => Call::DIRECTIONS[$s] ?? $s)
|
||||
->badge()
|
||||
@@ -75,8 +75,8 @@ class CallResource extends Resource
|
||||
'danger' => ['missed'],
|
||||
]),
|
||||
Tables\Columns\TextColumn::make('phone')->copyable()->searchable(),
|
||||
Tables\Columns\TextColumn::make('client.name')->label('Client')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('duration_formatted')->label('Durată')->state(fn (Call $r) => $r->duration_formatted),
|
||||
Tables\Columns\TextColumn::make('client.name')->label(__('Client'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('duration_formatted')->label(__('Durată'))->state(fn (Call $r) => $r->duration_formatted),
|
||||
Tables\Columns\TextColumn::make('status')
|
||||
->formatStateUsing(fn ($s) => Call::STATUSES[$s] ?? $s)
|
||||
->badge(),
|
||||
@@ -84,10 +84,10 @@ class CallResource extends Resource
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('direction')->options(Call::DIRECTIONS),
|
||||
Tables\Filters\Filter::make('today')
|
||||
->label('Astăzi')
|
||||
->label(__('Astăzi'))
|
||||
->query(fn ($q) => $q->whereDate('called_at', today())),
|
||||
Tables\Filters\Filter::make('missed')
|
||||
->label('Pierdute')
|
||||
->label(__('Pierdute'))
|
||||
->query(fn ($q) => $q->where('direction', 'missed')->orWhere('status', 'missed')),
|
||||
])
|
||||
->actions([
|
||||
|
||||
@@ -25,7 +25,12 @@ class ClientResource extends Resource
|
||||
|
||||
protected static ?string $modelLabel = 'client';
|
||||
|
||||
protected static ?string $pluralModelLabel = 'clienți';
|
||||
protected static ?string $pluralModelLabel = null;
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('clienți');
|
||||
}
|
||||
|
||||
protected static ?int $navigationSort = 10;
|
||||
|
||||
@@ -47,18 +52,18 @@ class ClientResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Date generale')
|
||||
Schemas\Components\Section::make(__('Date generale'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Select::make('type')
|
||||
->label('Tip')
|
||||
->label(__('Tip'))
|
||||
->options(['individual' => 'Persoană fizică', 'company' => 'Persoană juridică'])
|
||||
->default('individual')
|
||||
->required()
|
||||
->live(),
|
||||
Forms\Components\TextInput::make('name')->label('Nume')->required()->maxLength(120),
|
||||
Forms\Components\TextInput::make('name')->label(__('Nume'))->required()->maxLength(120),
|
||||
Forms\Components\TextInput::make('company_name')
|
||||
->label('Denumire companie')
|
||||
->label(__('Denumire companie'))
|
||||
->visible(fn (Schemas\Components\Utilities\Get $get) => $get('type') === 'company')
|
||||
->maxLength(160),
|
||||
Forms\Components\Select::make('status')
|
||||
@@ -69,40 +74,40 @@ class ClientResource extends Resource
|
||||
->default('active')
|
||||
->required(),
|
||||
Forms\Components\Toggle::make('is_vip')
|
||||
->label('Client VIP')
|
||||
->helperText('Activează coeficienții de preț VIP pe fișele acestui client.'),
|
||||
->label(__('Client VIP'))
|
||||
->helperText(__('Activează coeficienții de preț VIP pe fișele acestui client.')),
|
||||
]),
|
||||
Schemas\Components\Section::make('Contacte')
|
||||
Schemas\Components\Section::make(__('Contacte'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('phone')->label('Telefon')->tel()->required()->maxLength(40),
|
||||
Forms\Components\TextInput::make('phone_alt')->label('Telefon alternativ')->tel()->maxLength(40),
|
||||
Forms\Components\TextInput::make('phone')->label(__('Telefon'))->tel()->required()->maxLength(40),
|
||||
Forms\Components\TextInput::make('phone_alt')->label(__('Telefon alternativ'))->tel()->maxLength(40),
|
||||
Forms\Components\TextInput::make('email')->email()->maxLength(120),
|
||||
Forms\Components\TextInput::make('telegram')->maxLength(60),
|
||||
Forms\Components\TextInput::make('telegram_chat_id')
|
||||
->label('Telegram chat ID')
|
||||
->label(__('Telegram chat ID'))
|
||||
->disabled()
|
||||
->dehydrated(false)
|
||||
->placeholder('Se completează automat când clientul scrie la bot')
|
||||
->placeholder(__('Se completează automat când clientul scrie la bot'))
|
||||
->helperText(fn ($record) => $record?->telegram_chat_id
|
||||
? '✅ Telegram legat — notificările vor merge prin bot'
|
||||
: null),
|
||||
Forms\Components\TextInput::make('whatsapp')->maxLength(60),
|
||||
Forms\Components\TextInput::make('viber')->maxLength(60),
|
||||
]),
|
||||
Schemas\Components\Section::make('Marketing')
|
||||
Schemas\Components\Section::make(__('Marketing'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('source')->label('Sursă')->maxLength(60),
|
||||
Forms\Components\TextInput::make('marketing_channel')->label('Canal marketing')->maxLength(60),
|
||||
Forms\Components\TextInput::make('source')->label(__('Sursă'))->maxLength(60),
|
||||
Forms\Components\TextInput::make('marketing_channel')->label(__('Canal marketing'))->maxLength(60),
|
||||
]),
|
||||
Schemas\Components\Section::make('Financiar')
|
||||
Schemas\Components\Section::make(__('Financiar'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('balance')->label('Sold')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('discount_pct')->label('Discount %')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('balance')->label(__('Sold'))->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('discount_pct')->label(__('Discount %'))->numeric()->default(0),
|
||||
]),
|
||||
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(3),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(3),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -113,7 +118,7 @@ class ClientResource extends Resource
|
||||
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('phone')->searchable()->copyable(),
|
||||
Tables\Columns\TextColumn::make('email')->searchable()->toggleable(),
|
||||
Tables\Columns\TextColumn::make('vehicles_count')->counts('vehicles')->label('Mașini'),
|
||||
Tables\Columns\TextColumn::make('vehicles_count')->counts('vehicles')->label(__('Mașini')),
|
||||
Tables\Columns\TextColumn::make('status')
|
||||
->badge()
|
||||
->colors([
|
||||
@@ -136,8 +141,8 @@ class ClientResource extends Resource
|
||||
Actions\EditAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
])
|
||||
->emptyStateHeading('Niciun client încă')
|
||||
->emptyStateDescription('Adaugă primul tău client manual sau importă din CSV. Toate mașinile, fișele și plățile se vor lega automat de el.')
|
||||
->emptyStateHeading(__('Niciun client încă'))
|
||||
->emptyStateDescription(__('Adaugă primul tău client manual sau importă din CSV. Toate mașinile, fișele și plățile se vor lega automat de el.'))
|
||||
->emptyStateIcon('heroicon-o-users')
|
||||
->defaultSort('created_at', 'desc');
|
||||
}
|
||||
|
||||
@@ -17,16 +17,16 @@ class ListClients extends ListRecords
|
||||
{
|
||||
return [
|
||||
Actions\Action::make('export')
|
||||
->label('Export CSV')
|
||||
->label(__('Export CSV'))
|
||||
->icon('heroicon-m-arrow-down-tray')
|
||||
->color('gray')
|
||||
->action(fn () => app(CsvImportExport::class)->exportClients()),
|
||||
Actions\Action::make('import')
|
||||
->label('Import CSV')
|
||||
->label(__('Import CSV'))
|
||||
->icon('heroicon-m-arrow-up-tray')
|
||||
->color('gray')
|
||||
->modalHeading('Import clienți din CSV')
|
||||
->modalDescription('CSV cu header: ' . implode(', ', CsvImportExport::CLIENT_COLUMNS) . '. Deduplicare după telefon.')
|
||||
->modalHeading(__('Import clienți din CSV'))
|
||||
->modalDescription(__('CSV cu header: ' . implode(', ', CsvImportExport::CLIENT_COLUMNS) . '. Deduplicare după telefon.'))
|
||||
->schema([
|
||||
Forms\Components\FileUpload::make('file')
|
||||
->required()
|
||||
|
||||
@@ -46,22 +46,22 @@ class DealResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Detalii')
|
||||
Schemas\Components\Section::make(__('Detalii'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Select::make('client_id')
|
||||
->label('Client')
|
||||
->label(__('Client'))
|
||||
->options(fn () => Client::pluck('name', 'id'))
|
||||
->searchable()
|
||||
->required(),
|
||||
Forms\Components\Select::make('vehicle_id')
|
||||
->label('Auto')
|
||||
->label(__('Auto'))
|
||||
->options(fn (Schemas\Components\Utilities\Get $get) => $get('client_id')
|
||||
? Vehicle::where('client_id', $get('client_id'))->pluck('plate', 'id')
|
||||
: [])
|
||||
->searchable(),
|
||||
Forms\Components\TextInput::make('name')->label('Subiect')->required()->maxLength(160),
|
||||
Forms\Components\TextInput::make('price')->label('Valoare')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('name')->label(__('Subiect'))->required()->maxLength(160),
|
||||
Forms\Components\TextInput::make('price')->label(__('Valoare'))->numeric()->default(0),
|
||||
Forms\Components\Select::make('stage')
|
||||
->options(Deal::STAGES)
|
||||
->default('new')
|
||||
@@ -70,11 +70,11 @@ class DealResource extends Resource
|
||||
->options(Lead::SOURCES)
|
||||
->searchable(),
|
||||
Forms\Components\Select::make('assigned_to')
|
||||
->label('Responsabil')
|
||||
->label(__('Responsabil'))
|
||||
->options(fn () => User::pluck('name', 'id'))
|
||||
->searchable(),
|
||||
]),
|
||||
Forms\Components\Textarea::make('note')->label('Notițe')->columnSpanFull()->rows(3),
|
||||
Forms\Components\Textarea::make('note')->label(__('Notițe'))->columnSpanFull()->rows(3),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -83,9 +83,9 @@ class DealResource extends Resource
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('id')->label('#')->sortable(),
|
||||
Tables\Columns\TextColumn::make('name')->label('Subiect')->searchable()->limit(40),
|
||||
Tables\Columns\TextColumn::make('client.name')->label('Client')->searchable(),
|
||||
Tables\Columns\TextColumn::make('vehicle.plate')->label('Auto')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('name')->label(__('Subiect'))->searchable()->limit(40),
|
||||
Tables\Columns\TextColumn::make('client.name')->label(__('Client'))->searchable(),
|
||||
Tables\Columns\TextColumn::make('vehicle.plate')->label(__('Auto'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('stage')
|
||||
->formatStateUsing(fn ($state) => Deal::STAGES[$state] ?? $state)
|
||||
->badge()
|
||||
@@ -97,14 +97,14 @@ class DealResource extends Resource
|
||||
'danger' => ['lost'],
|
||||
]),
|
||||
Tables\Columns\TextColumn::make('price')->money('MDL')->sortable(),
|
||||
Tables\Columns\TextColumn::make('source')->label('Sursă')->formatStateUsing(fn ($state) => Lead::SOURCES[$state] ?? $state)->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('assignedTo.name')->label('Responsabil')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('source')->label(__('Sursă'))->formatStateUsing(fn ($state) => Lead::SOURCES[$state] ?? $state)->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('assignedTo.name')->label(__('Responsabil'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('created_at')->date()->sortable(),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('stage')->options(Deal::STAGES),
|
||||
Tables\Filters\SelectFilter::make('assigned_to')
|
||||
->label('Responsabil')
|
||||
->label(__('Responsabil'))
|
||||
->options(fn () => User::pluck('name', 'id')),
|
||||
])
|
||||
->actions([
|
||||
|
||||
@@ -31,41 +31,46 @@ class EmployeeProfileResource extends Resource
|
||||
|
||||
protected static ?string $modelLabel = 'angajat';
|
||||
|
||||
protected static ?string $pluralModelLabel = 'angajați';
|
||||
protected static ?string $pluralModelLabel = null;
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('angajați');
|
||||
}
|
||||
|
||||
protected static ?int $navigationSort = 52;
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Profil')
|
||||
Schemas\Components\Section::make(__('Profil'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Select::make('user_id')
|
||||
->label('Utilizator')
|
||||
->label(__('Utilizator'))
|
||||
->options(fn () => User::orderBy('name')->pluck('name', 'id'))
|
||||
->searchable()
|
||||
->required()
|
||||
->unique(ignoreRecord: true),
|
||||
Forms\Components\TextInput::make('position')->label('Funcție')->maxLength(120),
|
||||
Forms\Components\DatePicker::make('hire_date')->label('Angajat din'),
|
||||
Forms\Components\TextInput::make('position')->label(__('Funcție'))->maxLength(120),
|
||||
Forms\Components\DatePicker::make('hire_date')->label(__('Angajat din')),
|
||||
]),
|
||||
Schemas\Components\Section::make('Salariu & comisioane')
|
||||
Schemas\Components\Section::make(__('Salariu & comisioane'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('base_salary')->label('Salariu bază')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('base_salary')->label(__('Salariu bază'))->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('works_pct')
|
||||
->label('% manopere')
|
||||
->label(__('% manopere'))
|
||||
->numeric()->default(0)
|
||||
->suffix('%')
|
||||
->helperText('% din venitul manoperelor finalizate.'),
|
||||
->helperText(__('% din venitul manoperelor finalizate.')),
|
||||
Forms\Components\TextInput::make('parts_pct')
|
||||
->label('% marja piese')
|
||||
->label(__('% marja piese'))
|
||||
->numeric()->default(0)
|
||||
->suffix('%')
|
||||
->helperText('% din marja (sell-buy) pieselor montate.'),
|
||||
->helperText(__('% din marja (sell-buy) pieselor montate.')),
|
||||
]),
|
||||
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(2),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(2),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -73,12 +78,12 @@ class EmployeeProfileResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('user.name')->label('Nume')->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('position')->label('Funcție')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('user.name')->label(__('Nume'))->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('position')->label(__('Funcție'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('base_salary')->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('works_pct')->label('% Manopere')
|
||||
Tables\Columns\TextColumn::make('works_pct')->label(__('% Manopere'))
|
||||
->formatStateUsing(fn ($s) => $s . '%')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('parts_pct')->label('% Piese')
|
||||
Tables\Columns\TextColumn::make('parts_pct')->label(__('% Piese'))
|
||||
->formatStateUsing(fn ($s) => $s . '%')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('hire_date')->date('d.m.Y')->placeholder('—'),
|
||||
])
|
||||
|
||||
@@ -29,7 +29,12 @@ class ExpenseResource extends Resource
|
||||
return __('nav.group.Finanțe');
|
||||
}
|
||||
|
||||
protected static ?string $modelLabel = 'cheltuială';
|
||||
protected static ?string $modelLabel = null;
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('cheltuială');
|
||||
}
|
||||
|
||||
protected static ?string $pluralModelLabel = 'cheltuieli';
|
||||
|
||||
@@ -48,27 +53,27 @@ class ExpenseResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Cheltuială')
|
||||
Schemas\Components\Section::make(__('Cheltuială'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\DatePicker::make('paid_at')->label('Data')->default(today())->required(),
|
||||
Forms\Components\DatePicker::make('paid_at')->label(__('Data'))->default(today())->required(),
|
||||
Forms\Components\Select::make('category')
|
||||
->options(Expense::CATEGORIES)
|
||||
->default('other')
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('name')->label('Denumire')->required()->maxLength(160)->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('amount')->label('Sumă')->numeric()->required(),
|
||||
Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()->maxLength(160)->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('amount')->label(__('Sumă'))->numeric()->required(),
|
||||
Forms\Components\Select::make('method')
|
||||
->options(Expense::METHODS)
|
||||
->default('cash')
|
||||
->required(),
|
||||
Forms\Components\Select::make('supplier_id')
|
||||
->label('Furnizor (opțional)')
|
||||
->label(__('Furnizor (opțional)'))
|
||||
->options(fn () => Supplier::pluck('name', 'id'))
|
||||
->searchable(),
|
||||
Forms\Components\TextInput::make('reference')->label('Ref.')->maxLength(64),
|
||||
Forms\Components\TextInput::make('reference')->label(__('Ref.'))->maxLength(64),
|
||||
]),
|
||||
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(2),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(2),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -76,22 +81,22 @@ class ExpenseResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('paid_at')->label('Data')->date('d.m.Y')->sortable(),
|
||||
Tables\Columns\TextColumn::make('paid_at')->label(__('Data'))->date('d.m.Y')->sortable(),
|
||||
Tables\Columns\TextColumn::make('category')
|
||||
->formatStateUsing(fn ($s) => Expense::CATEGORIES[$s] ?? $s)
|
||||
->badge(),
|
||||
Tables\Columns\TextColumn::make('name')->searchable()->wrap(),
|
||||
Tables\Columns\TextColumn::make('supplier.name')->label('Furnizor')->placeholder('—')->toggleable(),
|
||||
Tables\Columns\TextColumn::make('supplier.name')->label(__('Furnizor'))->placeholder('—')->toggleable(),
|
||||
Tables\Columns\TextColumn::make('method')
|
||||
->formatStateUsing(fn ($s) => Expense::METHODS[$s] ?? $s),
|
||||
Tables\Columns\TextColumn::make('amount')->money('MDL')->alignRight()->sortable()
|
||||
->color('danger')
|
||||
->summarize(Tables\Columns\Summarizers\Sum::make()->money('MDL')->label('Total')),
|
||||
->summarize(Tables\Columns\Summarizers\Sum::make()->money('MDL')->label(__('Total'))),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('category')->options(Expense::CATEGORIES),
|
||||
Tables\Filters\Filter::make('this_month')
|
||||
->label('Luna curentă')
|
||||
->label(__('Luna curentă'))
|
||||
->query(fn ($q) => $q->whereMonth('paid_at', now()->month)->whereYear('paid_at', now()->year)),
|
||||
])
|
||||
->actions([
|
||||
|
||||
@@ -29,7 +29,12 @@ class LaborResource extends Resource
|
||||
return __('nav.group.Service');
|
||||
}
|
||||
|
||||
protected static ?string $modelLabel = 'normă';
|
||||
protected static ?string $modelLabel = null;
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('normă');
|
||||
}
|
||||
|
||||
protected static ?string $pluralModelLabel = 'norme-ore';
|
||||
|
||||
@@ -38,31 +43,31 @@ class LaborResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Manoperă')
|
||||
Schemas\Components\Section::make(__('Manoperă'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Select::make('category')
|
||||
->label('Categorie')
|
||||
->label(__('Categorie'))
|
||||
->options(array_combine(Labor::CATEGORIES, Labor::CATEGORIES))
|
||||
->required()
|
||||
->searchable(),
|
||||
Forms\Components\TextInput::make('code')->label('Cod')->maxLength(32),
|
||||
Forms\Components\TextInput::make('name_ro')->label('Nume (RO)')->required()->maxLength(160),
|
||||
Forms\Components\TextInput::make('name_ru')->label('Nume (RU)')->maxLength(160),
|
||||
Forms\Components\TextInput::make('code')->label(__('Cod'))->maxLength(32),
|
||||
Forms\Components\TextInput::make('name_ro')->label(__('Nume (RO)'))->required()->maxLength(160),
|
||||
Forms\Components\TextInput::make('name_ru')->label(__('Nume (RU)'))->maxLength(160),
|
||||
Forms\Components\Select::make('pricing_mode')
|
||||
->label('Mod tarifare')
|
||||
->label(__('Mod tarifare'))
|
||||
->options(Labor::PRICING_MODES)
|
||||
->default('hourly')
|
||||
->live()
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('hours')->label('Ore (normă)')->numeric()->default(1)
|
||||
Forms\Components\TextInput::make('hours')->label(__('Ore (normă)'))->numeric()->default(1)
|
||||
->visible(fn (Schemas\Components\Utilities\Get $get) => $get('pricing_mode') !== 'fixed'),
|
||||
Forms\Components\TextInput::make('fixed_price')->label('Preț fix (MDL)')->numeric()->default(0)
|
||||
Forms\Components\TextInput::make('fixed_price')->label(__('Preț fix (MDL)'))->numeric()->default(0)
|
||||
->visible(fn (Schemas\Components\Utilities\Get $get) => $get('pricing_mode') === 'fixed'),
|
||||
Forms\Components\TextInput::make('price')->label('Preț orientativ (MDL)')->numeric()->default(0),
|
||||
Forms\Components\Toggle::make('is_active')->label('Activă')->default(true),
|
||||
Forms\Components\TextInput::make('price')->label(__('Preț orientativ (MDL)'))->numeric()->default(0),
|
||||
Forms\Components\Toggle::make('is_active')->label(__('Activă'))->default(true),
|
||||
]),
|
||||
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -70,23 +75,23 @@ class LaborResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('category')->label('Categorie')->badge()->sortable(),
|
||||
Tables\Columns\TextColumn::make('name_ro')->label('Manoperă')->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('category')->label(__('Categorie'))->badge()->sortable(),
|
||||
Tables\Columns\TextColumn::make('name_ro')->label(__('Manoperă'))->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('pricing_mode')
|
||||
->label('Tarifare')
|
||||
->label(__('Tarifare'))
|
||||
->formatStateUsing(fn ($s) => $s === 'fixed' ? 'Fix' : 'Pe oră')
|
||||
->badge()
|
||||
->color(fn ($s) => $s === 'fixed' ? 'info' : 'gray'),
|
||||
Tables\Columns\TextColumn::make('hours')->label('Ore')->numeric(decimalPlaces: 2)->alignRight(),
|
||||
Tables\Columns\TextColumn::make('fixed_price')->label('Preț fix')->money('MDL')->alignRight()
|
||||
Tables\Columns\TextColumn::make('hours')->label(__('Ore'))->numeric(decimalPlaces: 2)->alignRight(),
|
||||
Tables\Columns\TextColumn::make('fixed_price')->label(__('Preț fix'))->money('MDL')->alignRight()
|
||||
->placeholder('—')->toggleable(),
|
||||
Tables\Columns\TextColumn::make('laborParts_count')->counts('laborParts')->label('Piese impl.')->alignRight()->toggleable(),
|
||||
Tables\Columns\IconColumn::make('is_active')->label('Activă')->boolean(),
|
||||
Tables\Columns\TextColumn::make('laborParts_count')->counts('laborParts')->label(__('Piese impl.'))->alignRight()->toggleable(),
|
||||
Tables\Columns\IconColumn::make('is_active')->label(__('Activă'))->boolean(),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('category')
|
||||
->options(array_combine(Labor::CATEGORIES, Labor::CATEGORIES)),
|
||||
Tables\Filters\TernaryFilter::make('is_active')->label('Doar active'),
|
||||
Tables\Filters\TernaryFilter::make('is_active')->label(__('Doar active')),
|
||||
])
|
||||
->actions([
|
||||
Actions\EditAction::make(),
|
||||
|
||||
+15
-10
@@ -15,13 +15,18 @@ class DefaultPartsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'laborParts';
|
||||
|
||||
protected static ?string $title = 'Piese implicite';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
|
||||
{
|
||||
return __('Piese implicite');
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Forms\Components\Select::make('part_id')
|
||||
->label('Piesă')
|
||||
->label(__('Piesă'))
|
||||
->options(fn () => Part::where('is_active', true)
|
||||
->get()
|
||||
->mapWithKeys(fn ($p) => [$p->id => "{$p->name} " . ($p->article ? "[{$p->article}]" : '')])
|
||||
@@ -34,8 +39,8 @@ class DefaultPartsRelationManager extends RelationManager
|
||||
$set('unit', $p->unit);
|
||||
}
|
||||
}),
|
||||
Forms\Components\TextInput::make('qty')->label('Cantitate')->numeric()->default(1)->required(),
|
||||
Forms\Components\TextInput::make('unit')->label('UM')->default('buc')->maxLength(16),
|
||||
Forms\Components\TextInput::make('qty')->label(__('Cantitate'))->numeric()->default(1)->required(),
|
||||
Forms\Components\TextInput::make('unit')->label(__('UM'))->default('buc')->maxLength(16),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -44,14 +49,14 @@ class DefaultPartsRelationManager extends RelationManager
|
||||
return $table
|
||||
->recordTitleAttribute('part.name')
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('part.name')->label('Piesă')->wrap(),
|
||||
Tables\Columns\TextColumn::make('part.article')->label('Cod')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('qty')->label('Cant.')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('unit')->label('UM'),
|
||||
Tables\Columns\TextColumn::make('part.name')->label(__('Piesă'))->wrap(),
|
||||
Tables\Columns\TextColumn::make('part.article')->label(__('Cod'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('qty')->label(__('Cant.'))->alignRight(),
|
||||
Tables\Columns\TextColumn::make('unit')->label(__('UM')),
|
||||
])
|
||||
->headerActions([Actions\CreateAction::make()])
|
||||
->actions([Actions\EditAction::make(), Actions\DeleteAction::make()])
|
||||
->emptyStateHeading('Nicio piesă implicită')
|
||||
->emptyStateDescription('Adaugă piesele care se montează de obicei la această manoperă — se adaugă automat în fișă când selectezi manopera.');
|
||||
->emptyStateHeading(__('Nicio piesă implicită'))
|
||||
->emptyStateDescription(__('Adaugă piesele care se montează de obicei la această manoperă — se adaugă automat în fișă când selectezi manopera.'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,25 +52,25 @@ class LeadResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Contact')
|
||||
Schemas\Components\Section::make(__('Contact'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')->label('Nume')->required()->maxLength(120),
|
||||
Forms\Components\TextInput::make('phone')->label('Telefon')->tel()->required()->maxLength(40),
|
||||
Forms\Components\TextInput::make('name')->label(__('Nume'))->required()->maxLength(120),
|
||||
Forms\Components\TextInput::make('phone')->label(__('Telefon'))->tel()->required()->maxLength(40),
|
||||
Forms\Components\TextInput::make('email')->email()->maxLength(120),
|
||||
Forms\Components\Select::make('status')
|
||||
->options(Lead::STATUSES)
|
||||
->default('new')
|
||||
->required(),
|
||||
]),
|
||||
Schemas\Components\Section::make('Auto')
|
||||
Schemas\Components\Section::make(__('Auto'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('car')->label('Marca')->maxLength(60),
|
||||
Forms\Components\TextInput::make('car')->label(__('Marca'))->maxLength(60),
|
||||
Forms\Components\TextInput::make('model')->maxLength(60),
|
||||
]),
|
||||
Forms\Components\Textarea::make('message')->label('Mesaj client')->columnSpanFull()->rows(3),
|
||||
Schemas\Components\Section::make('Sursă & Atribuire')
|
||||
Forms\Components\Textarea::make('message')->label(__('Mesaj client'))->columnSpanFull()->rows(3),
|
||||
Schemas\Components\Section::make(__('Sursă & Atribuire'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Select::make('source')
|
||||
@@ -78,12 +78,12 @@ class LeadResource extends Resource
|
||||
->searchable()
|
||||
->default('manual'),
|
||||
Forms\Components\Select::make('assigned_to')
|
||||
->label('Responsabil')
|
||||
->label(__('Responsabil'))
|
||||
->options(fn () => User::pluck('name', 'id'))
|
||||
->searchable(),
|
||||
Forms\Components\TextInput::make('budget')->label('Buget')->numeric(),
|
||||
Forms\Components\TextInput::make('budget')->label(__('Buget'))->numeric(),
|
||||
]),
|
||||
Schemas\Components\Section::make('Marketing (UTM)')
|
||||
Schemas\Components\Section::make(__('Marketing (UTM)'))
|
||||
->collapsed()
|
||||
->columns(2)
|
||||
->schema([
|
||||
@@ -93,7 +93,7 @@ class LeadResource extends Resource
|
||||
Forms\Components\TextInput::make('utm_term'),
|
||||
Forms\Components\TextInput::make('utm_content'),
|
||||
]),
|
||||
Forms\Components\Textarea::make('notes')->label('Notițe interne')->columnSpanFull()->rows(2),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Notițe interne'))->columnSpanFull()->rows(2),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -101,11 +101,11 @@ class LeadResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('created_at')->label('Data')->dateTime('d.m.Y H:i')->sortable(),
|
||||
Tables\Columns\TextColumn::make('created_at')->label(__('Data'))->dateTime('d.m.Y H:i')->sortable(),
|
||||
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('phone')->copyable()->searchable(),
|
||||
Tables\Columns\TextColumn::make('car')->label('Auto')->formatStateUsing(fn ($state, $record) => trim($state . ' ' . ($record->model ?? ''))),
|
||||
Tables\Columns\TextColumn::make('source')->label('Sursă')->formatStateUsing(fn ($state) => Lead::SOURCES[$state] ?? $state)->badge(),
|
||||
Tables\Columns\TextColumn::make('car')->label(__('Auto'))->formatStateUsing(fn ($state, $record) => trim($state . ' ' . ($record->model ?? ''))),
|
||||
Tables\Columns\TextColumn::make('source')->label(__('Sursă'))->formatStateUsing(fn ($state) => Lead::SOURCES[$state] ?? $state)->badge(),
|
||||
Tables\Columns\TextColumn::make('status')
|
||||
->formatStateUsing(fn ($state) => Lead::STATUSES[$state] ?? $state)
|
||||
->badge()
|
||||
@@ -116,7 +116,7 @@ class LeadResource extends Resource
|
||||
'success' => ['converted'],
|
||||
'danger' => ['lost'],
|
||||
]),
|
||||
Tables\Columns\TextColumn::make('assignedTo.name')->label('Responsabil')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('assignedTo.name')->label(__('Responsabil'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('budget')->money('MDL')->placeholder('—'),
|
||||
])
|
||||
->filters([
|
||||
@@ -125,7 +125,7 @@ class LeadResource extends Resource
|
||||
])
|
||||
->actions([
|
||||
Actions\Action::make('convert')
|
||||
->label('Convertește')
|
||||
->label(__('Convertește'))
|
||||
->icon('heroicon-m-arrow-right-circle')
|
||||
->color('success')
|
||||
->visible(fn (Lead $r) => $r->status !== 'converted')
|
||||
@@ -141,7 +141,7 @@ class LeadResource extends Resource
|
||||
Actions\DeleteAction::make(),
|
||||
])
|
||||
->emptyStateHeading('Nicio cerere primită')
|
||||
->emptyStateDescription('Aici apar cererile clienților potențiali. Convertește-le în deal-uri sau direct în programări de la butonul „Convertește".')
|
||||
->emptyStateDescription(__('Aici apar cererile clienților potențiali. Convertește-le în deal-uri sau direct în programări de la butonul „Convertește".'))
|
||||
->emptyStateIcon('heroicon-o-inbox-arrow-down')
|
||||
->defaultSort('created_at', 'desc');
|
||||
}
|
||||
|
||||
@@ -30,31 +30,36 @@ class MarketingChannelResource extends Resource
|
||||
|
||||
protected static ?string $modelLabel = 'canal';
|
||||
|
||||
protected static ?string $pluralModelLabel = 'canale marketing';
|
||||
protected static ?string $pluralModelLabel = null;
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('canale marketing');
|
||||
}
|
||||
|
||||
protected static ?int $navigationSort = 61;
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Identificare')
|
||||
Schemas\Components\Section::make(__('Identificare'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')->label('Nume canal')->required()->maxLength(120),
|
||||
Forms\Components\TextInput::make('icon')->label('Iconiță (emoji)')->maxLength(8)->placeholder('🔍 / 📘 / 📸'),
|
||||
Forms\Components\TextInput::make('name')->label(__('Nume canal'))->required()->maxLength(120),
|
||||
Forms\Components\TextInput::make('icon')->label(__('Iconiță (emoji)'))->maxLength(8)->placeholder('🔍 / 📘 / 📸'),
|
||||
Forms\Components\ColorPicker::make('color'),
|
||||
]),
|
||||
Schemas\Components\Section::make('Buget & rezultate (luna curentă)')
|
||||
Schemas\Components\Section::make(__('Buget & rezultate (luna curentă)'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('budget_monthly')->label('Buget')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('spent_monthly')->label('Cheltuit')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('revenue')->label('Venit generat')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('leads_count')->label('Lead-uri')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('converted_count')->label('Convertite')->numeric()->default(0),
|
||||
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true),
|
||||
Forms\Components\TextInput::make('budget_monthly')->label(__('Buget'))->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('spent_monthly')->label(__('Cheltuit'))->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('revenue')->label(__('Venit generat'))->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('leads_count')->label(__('Lead-uri'))->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('converted_count')->label(__('Convertite'))->numeric()->default(0),
|
||||
Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
|
||||
]),
|
||||
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -65,26 +70,26 @@ class MarketingChannelResource extends Resource
|
||||
Tables\Columns\ColorColumn::make('color')->label(''),
|
||||
Tables\Columns\TextColumn::make('icon')->label('')->width(40),
|
||||
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('budget_monthly')->label('Buget')->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('spent_monthly')->label('Cheltuit')->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('leads_count')->label('Lead-uri')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('converted_count')->label('Convertite')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('revenue')->label('Venit')->money('MDL')->alignRight()->color('success'),
|
||||
Tables\Columns\TextColumn::make('budget_monthly')->label(__('Buget'))->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('spent_monthly')->label(__('Cheltuit'))->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('leads_count')->label(__('Lead-uri'))->alignRight(),
|
||||
Tables\Columns\TextColumn::make('converted_count')->label(__('Convertite'))->alignRight(),
|
||||
Tables\Columns\TextColumn::make('revenue')->label(__('Venit'))->money('MDL')->alignRight()->color('success'),
|
||||
Tables\Columns\TextColumn::make('roi')
|
||||
->label('ROI')
|
||||
->label(__('ROI'))
|
||||
->state(fn (MarketingChannel $r) => $r->roi)
|
||||
->formatStateUsing(fn ($s) => $s . '%')
|
||||
->color(fn ($s) => $s >= 0 ? 'success' : 'danger')
|
||||
->alignRight(),
|
||||
Tables\Columns\TextColumn::make('cost_per_lead')
|
||||
->label('Cost/lead')
|
||||
->label(__('Cost/lead'))
|
||||
->state(fn (MarketingChannel $r) => $r->cost_per_lead)
|
||||
->money('MDL')
|
||||
->alignRight(),
|
||||
Tables\Columns\IconColumn::make('is_active')->boolean(),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\TernaryFilter::make('is_active')->label('Active'),
|
||||
Tables\Filters\TernaryFilter::make('is_active')->label(__('Active')),
|
||||
])
|
||||
->actions([
|
||||
Actions\EditAction::make(),
|
||||
|
||||
@@ -31,20 +31,30 @@ class MarkupRuleResource extends Resource
|
||||
return __('nav.group.Depozit');
|
||||
}
|
||||
|
||||
protected static ?string $modelLabel = 'regulă';
|
||||
protected static ?string $modelLabel = null;
|
||||
|
||||
protected static ?string $pluralModelLabel = 'reguli markup';
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('regulă');
|
||||
}
|
||||
|
||||
protected static ?string $pluralModelLabel = null;
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('reguli markup');
|
||||
}
|
||||
|
||||
protected static ?int $navigationSort = 44;
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Regulă')
|
||||
Schemas\Components\Section::make(__('Regulă'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Select::make('type')
|
||||
->label('Tip')
|
||||
->label(__('Tip'))
|
||||
->options(MarkupRule::TYPES)
|
||||
->default('category')
|
||||
->required()
|
||||
@@ -58,25 +68,25 @@ class MarkupRuleResource extends Resource
|
||||
->searchable()
|
||||
->required(fn (Get $get) => in_array($get('type'), ['category', 'brand'], true)),
|
||||
Forms\Components\TextInput::make('range_from')
|
||||
->label('De la (preț achiziție)')
|
||||
->label(__('De la (preț achiziție)'))
|
||||
->numeric()
|
||||
->visible(fn (Get $get) => $get('type') === 'range'),
|
||||
Forms\Components\TextInput::make('range_to')
|
||||
->label('Până la (gol = ∞)')
|
||||
->label(__('Până la (gol = ∞)'))
|
||||
->numeric()
|
||||
->visible(fn (Get $get) => $get('type') === 'range'),
|
||||
Forms\Components\TextInput::make('markup_pct')
|
||||
->label('Markup %')
|
||||
->label(__('Markup %'))
|
||||
->numeric()
|
||||
->required()
|
||||
->suffix('%')
|
||||
->helperText('Ex 30 → preț vânzare = preț achiziție × 1.30'),
|
||||
->helperText(__('Ex 30 → preț vânzare = preț achiziție × 1.30')),
|
||||
Forms\Components\TextInput::make('priority')
|
||||
->label('Prioritate')
|
||||
->label(__('Prioritate'))
|
||||
->numeric()
|
||||
->default(100)
|
||||
->helperText('Mai mic = aplicat primul.'),
|
||||
Forms\Components\Toggle::make('is_active')->label('Activă')->default(true),
|
||||
->helperText(__('Mai mic = aplicat primul.')),
|
||||
Forms\Components\Toggle::make('is_active')->label(__('Activă'))->default(true),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -89,11 +99,11 @@ class MarkupRuleResource extends Resource
|
||||
Tables\Columns\TextColumn::make('type')
|
||||
->formatStateUsing(fn ($s) => MarkupRule::TYPES[$s] ?? $s)
|
||||
->badge(),
|
||||
Tables\Columns\TextColumn::make('key')->label('Cheie')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('range_from')->label('De la')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('range_to')->label('Până la')->placeholder('∞'),
|
||||
Tables\Columns\TextColumn::make('key')->label(__('Cheie'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('range_from')->label(__('De la'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('range_to')->label(__('Până la'))->placeholder('∞'),
|
||||
Tables\Columns\TextColumn::make('markup_pct')
|
||||
->label('Markup')
|
||||
->label(__('Markup'))
|
||||
->formatStateUsing(fn ($s) => '+' . $s . '%')
|
||||
->color('success')
|
||||
->weight('bold'),
|
||||
@@ -104,11 +114,11 @@ class MarkupRuleResource extends Resource
|
||||
])
|
||||
->headerActions([
|
||||
Actions\Action::make('apply_all')
|
||||
->label('Aplică toate regulile la stoc')
|
||||
->label(__('Aplică toate regulile la stoc'))
|
||||
->icon('heroicon-m-bolt')
|
||||
->color('warning')
|
||||
->requiresConfirmation()
|
||||
->modalDescription('Va recalcula sell_price pentru TOATE piesele active. Continui?')
|
||||
->modalDescription(__('Va recalcula sell_price pentru TOATE piesele active. Continui?'))
|
||||
->action(function () {
|
||||
$count = 0;
|
||||
Part::where('is_active', true)->where('buy_price', '>', 0)->chunk(100, function ($parts) use (&$count) {
|
||||
|
||||
@@ -47,34 +47,34 @@ class MasterResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Date personale')
|
||||
Schemas\Components\Section::make(__('Date personale'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')->label('Nume')->required()->maxLength(120),
|
||||
Forms\Components\TextInput::make('phone')->label('Telefon')->tel()->maxLength(40),
|
||||
Forms\Components\TextInput::make('email')->label('Email')->email()->maxLength(120),
|
||||
Forms\Components\TextInput::make('name')->label(__('Nume'))->required()->maxLength(120),
|
||||
Forms\Components\TextInput::make('phone')->label(__('Telefon'))->tel()->maxLength(40),
|
||||
Forms\Components\TextInput::make('email')->label(__('Email'))->email()->maxLength(120),
|
||||
Forms\Components\Select::make('status')
|
||||
->options(['active' => 'Activ', 'inactive' => 'Inactiv', 'blocked' => 'Blocat'])
|
||||
->default('active')
|
||||
->required(),
|
||||
]),
|
||||
Schemas\Components\Section::make('Profesie')
|
||||
Schemas\Components\Section::make(__('Profesie'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('specialization')
|
||||
->label('Specializare')
|
||||
->placeholder('Motor / Frâne / Electrică ...')
|
||||
->label(__('Specializare'))
|
||||
->placeholder(__('Motor / Frâne / Electrică ...'))
|
||||
->maxLength(120),
|
||||
Forms\Components\ColorPicker::make('color')->label('Culoare în calendar'),
|
||||
Forms\Components\TextInput::make('hourly_rate')->label('Tarif/oră')->numeric(),
|
||||
Forms\Components\ColorPicker::make('color')->label(__('Culoare în calendar')),
|
||||
Forms\Components\TextInput::make('hourly_rate')->label(__('Tarif/oră'))->numeric(),
|
||||
Forms\Components\Hidden::make('role')->default('mechanic'),
|
||||
]),
|
||||
Schemas\Components\Section::make('Acces în aplicație (opțional)')
|
||||
Schemas\Components\Section::make(__('Acces în aplicație (opțional)'))
|
||||
->columns(1)
|
||||
->collapsed()
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('password')
|
||||
->label('Parolă (lasă gol pentru a nu schimba)')
|
||||
->label(__('Parolă (lasă gol pentru a nu schimba)'))
|
||||
->password()
|
||||
->minLength(6)
|
||||
->dehydrated(fn ($state) => filled($state))
|
||||
@@ -89,9 +89,9 @@ class MasterResource extends Resource
|
||||
->columns([
|
||||
Tables\Columns\ColorColumn::make('color')->label(''),
|
||||
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('specialization')->label('Specializare')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('specialization')->label(__('Specializare'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('phone')->copyable()->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('hourly_rate')->label('Tarif/h')->money('MDL')->alignRight()->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('hourly_rate')->label(__('Tarif/h'))->money('MDL')->alignRight()->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('status')
|
||||
->badge()
|
||||
->colors([
|
||||
|
||||
@@ -10,5 +10,5 @@ class ListMasters extends ListRecords
|
||||
{
|
||||
protected static string $resource = MasterResource::class;
|
||||
|
||||
protected function getHeaderActions(): array { return [Actions\CreateAction::make()->label('Nou tehnician')]; }
|
||||
protected function getHeaderActions(): array { return [Actions\CreateAction::make()->label(__('Nou tehnician'))]; }
|
||||
}
|
||||
|
||||
@@ -37,25 +37,25 @@ class MessageTemplateResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Identificare')
|
||||
Schemas\Components\Section::make(__('Identificare'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')->label('Nume template')->required()->maxLength(120),
|
||||
Forms\Components\TextInput::make('name')->label(__('Nume template'))->required()->maxLength(120),
|
||||
Forms\Components\Select::make('channel')
|
||||
->options(MessageTemplate::CHANNELS)
|
||||
->default('telegram')
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('subject')->label('Subiect (email)')->maxLength(160)->columnSpanFull(),
|
||||
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true),
|
||||
Forms\Components\TextInput::make('subject')->label(__('Subiect (email)'))->maxLength(160)->columnSpanFull(),
|
||||
Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
|
||||
]),
|
||||
Schemas\Components\Section::make('Conținut')
|
||||
Schemas\Components\Section::make(__('Conținut'))
|
||||
->columns(1)
|
||||
->schema([
|
||||
Forms\Components\Textarea::make('body')
|
||||
->label('Mesaj')
|
||||
->label(__('Mesaj'))
|
||||
->required()
|
||||
->rows(6)
|
||||
->helperText('Variabile disponibile: {name}, {car}, {date}, {time}, {amount}, {service}, {mileage}'),
|
||||
->helperText(__('Variabile disponibile: {name}, {car}, {date}, {time}, {amount}, {service}, {mileage}')),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -68,7 +68,7 @@ class MessageTemplateResource extends Resource
|
||||
Tables\Columns\TextColumn::make('channel')
|
||||
->formatStateUsing(fn ($s) => MessageTemplate::CHANNELS[$s] ?? $s)
|
||||
->badge(),
|
||||
Tables\Columns\TextColumn::make('body')->label('Preview')->limit(60),
|
||||
Tables\Columns\TextColumn::make('body')->label(__('Preview'))->limit(60),
|
||||
Tables\Columns\IconColumn::make('is_active')->boolean(),
|
||||
])
|
||||
->filters([
|
||||
|
||||
@@ -30,9 +30,19 @@ class OnlineOrderResource extends Resource
|
||||
return __('nav.group.Magazin');
|
||||
}
|
||||
|
||||
protected static ?string $modelLabel = 'comandă';
|
||||
protected static ?string $modelLabel = null;
|
||||
|
||||
protected static ?string $pluralModelLabel = 'comenzi online';
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('comandă');
|
||||
}
|
||||
|
||||
protected static ?string $pluralModelLabel = null;
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('comenzi online');
|
||||
}
|
||||
|
||||
protected static ?int $navigationSort = 50;
|
||||
|
||||
@@ -50,18 +60,18 @@ class OnlineOrderResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Comandă')
|
||||
Schemas\Components\Section::make(__('Comandă'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('number')->label('Nr.')->disabled()->dehydrated(false),
|
||||
Forms\Components\TextInput::make('number')->label(__('Nr.'))->disabled()->dehydrated(false),
|
||||
Forms\Components\Select::make('status')->options(OnlineOrder::STATUSES)->required(),
|
||||
Forms\Components\Select::make('delivery_method')->label('Livrare')->options(OnlineOrder::DELIVERY)->required(),
|
||||
Forms\Components\TextInput::make('customer_name')->label('Client')->required(),
|
||||
Forms\Components\TextInput::make('customer_phone')->label('Telefon')->required(),
|
||||
Forms\Components\TextInput::make('customer_email')->label('Email'),
|
||||
Forms\Components\TextInput::make('address')->label('Adresă')->columnSpan(2),
|
||||
Forms\Components\TextInput::make('delivery_fee')->label('Taxă livrare')->numeric(),
|
||||
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2),
|
||||
Forms\Components\Select::make('delivery_method')->label(__('Livrare'))->options(OnlineOrder::DELIVERY)->required(),
|
||||
Forms\Components\TextInput::make('customer_name')->label(__('Client'))->required(),
|
||||
Forms\Components\TextInput::make('customer_phone')->label(__('Telefon'))->required(),
|
||||
Forms\Components\TextInput::make('customer_email')->label(__('Email')),
|
||||
Forms\Components\TextInput::make('address')->label(__('Adresă'))->columnSpan(2),
|
||||
Forms\Components\TextInput::make('delivery_fee')->label(__('Taxă livrare'))->numeric(),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -70,12 +80,12 @@ class OnlineOrderResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('number')->label('Nr.')->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('created_at')->label('Data')->dateTime('d.m.Y H:i')->sortable(),
|
||||
Tables\Columns\TextColumn::make('customer_name')->label('Client')->searchable(),
|
||||
Tables\Columns\TextColumn::make('customer_phone')->label('Telefon')->copyable(),
|
||||
Tables\Columns\TextColumn::make('number')->label(__('Nr.'))->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('created_at')->label(__('Data'))->dateTime('d.m.Y H:i')->sortable(),
|
||||
Tables\Columns\TextColumn::make('customer_name')->label(__('Client'))->searchable(),
|
||||
Tables\Columns\TextColumn::make('customer_phone')->label(__('Telefon'))->copyable(),
|
||||
Tables\Columns\TextColumn::make('delivery_method')
|
||||
->label('Livrare')
|
||||
->label(__('Livrare'))
|
||||
->formatStateUsing(fn ($s) => OnlineOrder::DELIVERY[$s] ?? $s),
|
||||
Tables\Columns\TextColumn::make('status')
|
||||
->formatStateUsing(fn ($s) => OnlineOrder::STATUSES[$s] ?? $s)
|
||||
@@ -94,12 +104,12 @@ class OnlineOrderResource extends Resource
|
||||
])
|
||||
->actions([
|
||||
Actions\Action::make('fulfill')
|
||||
->label('Onorează (scade stoc)')
|
||||
->label(__('Onorează (scade stoc)'))
|
||||
->icon('heroicon-m-check-badge')
|
||||
->color('success')
|
||||
->visible(fn (OnlineOrder $r) => ! in_array($r->status, ['delivered', 'cancelled'], true))
|
||||
->requiresConfirmation()
|
||||
->modalDescription('Scade din stoc piesele legate de catalog (FIFO) și marchează comanda confirmată.')
|
||||
->modalDescription(__('Scade din stoc piesele legate de catalog (FIFO) și marchează comanda confirmată.'))
|
||||
->action(function (OnlineOrder $r) {
|
||||
$svc = app(\App\Services\Warehouse\WarehouseService::class);
|
||||
$issued = 0; $skipped = 0;
|
||||
|
||||
+11
-6
@@ -10,19 +10,24 @@ class ItemsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'items';
|
||||
|
||||
protected static ?string $title = 'Produse';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
|
||||
{
|
||||
return __('Produse');
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->recordTitleAttribute('name')
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')->label('Piesă')->wrap(),
|
||||
Tables\Columns\TextColumn::make('article')->label('Cod')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('qty')->label('Cant.')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('price')->label('Preț')->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('name')->label(__('Piesă'))->wrap(),
|
||||
Tables\Columns\TextColumn::make('article')->label(__('Cod'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('qty')->label(__('Cant.'))->alignRight(),
|
||||
Tables\Columns\TextColumn::make('price')->label(__('Preț'))->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('total')->money('MDL')->alignRight(),
|
||||
Tables\Columns\IconColumn::make('fulfilled')->label('Onorat')->boolean(),
|
||||
Tables\Columns\IconColumn::make('fulfilled')->label(__('Onorat'))->boolean(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,12 @@ class PartResource extends Resource
|
||||
return __('nav.group.Depozit');
|
||||
}
|
||||
|
||||
protected static ?string $modelLabel = 'piesă';
|
||||
protected static ?string $modelLabel = null;
|
||||
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('piesă');
|
||||
}
|
||||
|
||||
protected static ?string $pluralModelLabel = 'piese';
|
||||
|
||||
@@ -66,50 +71,50 @@ class PartResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Identificare')
|
||||
Schemas\Components\Section::make(__('Identificare'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')->label('Denumire')->required()->columnSpan(3)->maxLength(200),
|
||||
Forms\Components\TextInput::make('article')->label('Cod articol')->maxLength(64),
|
||||
Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()->columnSpan(3)->maxLength(200),
|
||||
Forms\Components\TextInput::make('article')->label(__('Cod articol'))->maxLength(64),
|
||||
Forms\Components\TextInput::make('brand')->maxLength(64),
|
||||
Forms\Components\Select::make('category')
|
||||
->label('Categorie')
|
||||
->label(__('Categorie'))
|
||||
->options(array_combine(Part::CATEGORIES, Part::CATEGORIES))
|
||||
->searchable(),
|
||||
Forms\Components\TextInput::make('barcode')->label('Cod bare')->maxLength(64),
|
||||
Forms\Components\TextInput::make('location')->label('Locație rack/bin')->maxLength(64),
|
||||
Forms\Components\TextInput::make('barcode')->label(__('Cod bare'))->maxLength(64),
|
||||
Forms\Components\TextInput::make('location')->label(__('Locație rack/bin'))->maxLength(64),
|
||||
]),
|
||||
Schemas\Components\Section::make('Stoc')
|
||||
Schemas\Components\Section::make(__('Stoc'))
|
||||
->columns(4)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('qty')->label('Cantitate')->numeric()->default(0)->required(),
|
||||
Forms\Components\TextInput::make('unit')->label('UM')->default('buc')->maxLength(16),
|
||||
Forms\Components\TextInput::make('min_qty')->label('Minim')->numeric()->default(0),
|
||||
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true),
|
||||
Forms\Components\TextInput::make('qty')->label(__('Cantitate'))->numeric()->default(0)->required(),
|
||||
Forms\Components\TextInput::make('unit')->label(__('UM'))->default('buc')->maxLength(16),
|
||||
Forms\Components\TextInput::make('min_qty')->label(__('Minim'))->numeric()->default(0),
|
||||
Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
|
||||
Forms\Components\Toggle::make('is_published')
|
||||
->label('Publicat în magazin')
|
||||
->helperText('Apare în magazinul online public.')
|
||||
->label(__('Publicat în magazin'))
|
||||
->helperText(__('Apare în magazinul online public.'))
|
||||
->default(false),
|
||||
]),
|
||||
Schemas\Components\Section::make('Prețuri')
|
||||
Schemas\Components\Section::make(__('Prețuri'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('buy_price')->label('Preț achiziție')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('sell_price')->label('Preț vânzare')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('buy_price')->label(__('Preț achiziție'))->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('sell_price')->label(__('Preț vânzare'))->numeric()->default(0),
|
||||
]),
|
||||
Schemas\Components\Section::make('Furnizor preferat')
|
||||
Schemas\Components\Section::make(__('Furnizor preferat'))
|
||||
->columns(1)
|
||||
->schema([
|
||||
Forms\Components\Select::make('preferred_supplier_id')
|
||||
->label('Furnizor')
|
||||
->label(__('Furnizor'))
|
||||
->options(fn () => Supplier::pluck('name', 'id'))
|
||||
->searchable(),
|
||||
]),
|
||||
Schemas\Components\Section::make('Imagine')
|
||||
Schemas\Components\Section::make(__('Imagine'))
|
||||
->collapsible()
|
||||
->schema([
|
||||
\Filament\Forms\Components\SpatieMediaLibraryFileUpload::make('image')
|
||||
->label('Foto piesă')
|
||||
->label(__('Foto piesă'))
|
||||
->collection('image')
|
||||
->multiple()
|
||||
->reorderable()
|
||||
@@ -118,9 +123,9 @@ class PartResource extends Resource
|
||||
->maxFiles(8)
|
||||
->maxSize(2048)
|
||||
->columnSpanFull()
|
||||
->helperText('Galerie de până la 8 imagini. Prima e afișată în catalog. Max 2 MB / imagine.'),
|
||||
->helperText(__('Galerie de până la 8 imagini. Prima e afișată în catalog. Max 2 MB / imagine.')),
|
||||
]),
|
||||
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -134,40 +139,40 @@ class PartResource extends Resource
|
||||
->circular()
|
||||
->size(32),
|
||||
Tables\Columns\TextColumn::make('name')->searchable()->sortable()->wrap(),
|
||||
Tables\Columns\TextColumn::make('article')->label('Cod')->searchable()->copyable()->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('article')->label(__('Cod'))->searchable()->copyable()->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('brand')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('category')->badge()->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('qty')
|
||||
->label('Stoc')
|
||||
->label(__('Stoc'))
|
||||
->numeric(decimalPlaces: 2)
|
||||
->alignRight()
|
||||
->color(fn ($state, $record) => $record->qty <= 0 ? 'danger' : ($record->qty <= $record->min_qty ? 'warning' : null))
|
||||
->weight(fn ($state, $record) => $record->qty <= $record->min_qty ? 'bold' : null),
|
||||
Tables\Columns\TextColumn::make('qty_reserved')
|
||||
->label('Rezervat')
|
||||
->label(__('Rezervat'))
|
||||
->numeric(decimalPlaces: 2)
|
||||
->alignRight()
|
||||
->color(fn ($state) => (float) $state > 0 ? 'info' : null)
|
||||
->toggleable(),
|
||||
Tables\Columns\TextColumn::make('unit')->label('UM'),
|
||||
Tables\Columns\TextColumn::make('location')->label('Loc.')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('sell_price')->label('Preț vz.')->money('MDL')->alignRight(),
|
||||
Tables\Columns\IconColumn::make('is_published')->label('Magazin')->boolean()->toggleable(),
|
||||
Tables\Columns\TextColumn::make('preferredSupplier.name')->label('Furnizor')->placeholder('—')->toggleable(),
|
||||
Tables\Columns\TextColumn::make('unit')->label(__('UM')),
|
||||
Tables\Columns\TextColumn::make('location')->label(__('Loc.'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('sell_price')->label(__('Preț vz.'))->money('MDL')->alignRight(),
|
||||
Tables\Columns\IconColumn::make('is_published')->label(__('Magazin'))->boolean()->toggleable(),
|
||||
Tables\Columns\TextColumn::make('preferredSupplier.name')->label(__('Furnizor'))->placeholder('—')->toggleable(),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('category')
|
||||
->options(array_combine(Part::CATEGORIES, Part::CATEGORIES)),
|
||||
Tables\Filters\Filter::make('low_stock')
|
||||
->label('Stoc minim')
|
||||
->label(__('Stoc minim'))
|
||||
->query(fn ($q) => $q->whereColumn('qty', '<=', 'min_qty')),
|
||||
Tables\Filters\Filter::make('out_of_stock')
|
||||
->label('Lipsă')
|
||||
->label(__('Lipsă'))
|
||||
->query(fn ($q) => $q->where('qty', '<=', 0)),
|
||||
])
|
||||
->actions([
|
||||
Actions\Action::make('qr')
|
||||
->label('QR')
|
||||
->label(__('QR'))
|
||||
->icon('heroicon-m-qr-code')
|
||||
->color('gray')
|
||||
->modalHeading(fn (Part $r) => 'QR pentru ' . $r->name)
|
||||
@@ -187,7 +192,7 @@ class PartResource extends Resource
|
||||
]);
|
||||
}),
|
||||
Actions\Action::make('ai_price')
|
||||
->label('AI: preț recomandat')
|
||||
->label(__('AI: preț recomandat'))
|
||||
->icon('heroicon-m-sparkles')
|
||||
->color('primary')
|
||||
->modalHeading(fn (Part $r) => "AI: preț pentru {$r->name}")
|
||||
@@ -199,20 +204,20 @@ class PartResource extends Resource
|
||||
return view('filament.tenant.ai-reply', ['reply' => $reply, 'meta' => $meta]);
|
||||
}),
|
||||
Actions\Action::make('receive')
|
||||
->label('Recepție')
|
||||
->label(__('Recepție'))
|
||||
->icon('heroicon-m-arrow-down-tray')
|
||||
->color('success')
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('qty')->label('Cantitate')->numeric()->required()->minValue(0.001),
|
||||
Forms\Components\TextInput::make('buy_price')->label('Preț unitar')->numeric()->required(),
|
||||
Forms\Components\TextInput::make('qty')->label(__('Cantitate'))->numeric()->required()->minValue(0.001),
|
||||
Forms\Components\TextInput::make('buy_price')->label(__('Preț unitar'))->numeric()->required(),
|
||||
Forms\Components\Select::make('supplier_id')
|
||||
->label('Furnizor')
|
||||
->label(__('Furnizor'))
|
||||
->options(fn () => \App\Models\Tenant\Supplier::pluck('name', 'id')),
|
||||
Forms\Components\Select::make('warehouse_id')
|
||||
->label('Depozit')
|
||||
->label(__('Depozit'))
|
||||
->options(fn () => \App\Models\Tenant\Warehouse::where('is_active', true)->pluck('name', 'id'))
|
||||
->default(fn () => \App\Models\Tenant\Warehouse::where('is_default', true)->value('id')),
|
||||
Forms\Components\TextInput::make('batch_ref')->label('Ref. lot/factură')->maxLength(64),
|
||||
Forms\Components\TextInput::make('batch_ref')->label(__('Ref. lot/factură'))->maxLength(64),
|
||||
])
|
||||
->action(function (Part $record, array $data) {
|
||||
$warehouse = $data['warehouse_id']
|
||||
@@ -230,7 +235,7 @@ class PartResource extends Resource
|
||||
batchRef: $data['batch_ref'] ?? null,
|
||||
);
|
||||
\Filament\Notifications\Notification::make()
|
||||
->title('Stoc adăugat')
|
||||
->title(__('Stoc adăugat'))
|
||||
->success()
|
||||
->send();
|
||||
}),
|
||||
@@ -239,7 +244,7 @@ class PartResource extends Resource
|
||||
])
|
||||
->bulkActions([
|
||||
Actions\BulkAction::make('print_labels')
|
||||
->label('Tipărește etichete QR')
|
||||
->label(__('Tipărește etichete QR'))
|
||||
->icon('heroicon-m-printer')
|
||||
->color('gray')
|
||||
->action(function ($records) {
|
||||
@@ -248,20 +253,20 @@ class PartResource extends Resource
|
||||
})
|
||||
->deselectRecordsAfterCompletion(),
|
||||
Actions\BulkAction::make('publish')
|
||||
->label('Publică în magazin')
|
||||
->label(__('Publică în magazin'))
|
||||
->icon('heroicon-m-globe-alt')
|
||||
->color('success')
|
||||
->action(fn ($records) => collect($records)->each->update(['is_published' => true]))
|
||||
->deselectRecordsAfterCompletion(),
|
||||
Actions\BulkAction::make('unpublish')
|
||||
->label('Scoate din magazin')
|
||||
->label(__('Scoate din magazin'))
|
||||
->icon('heroicon-m-eye-slash')
|
||||
->color('gray')
|
||||
->action(fn ($records) => collect($records)->each->update(['is_published' => false]))
|
||||
->deselectRecordsAfterCompletion(),
|
||||
])
|
||||
->emptyStateHeading('Depozit gol')
|
||||
->emptyStateDescription('Adaugă piese manual, sau folosește Achiziții ca să le adaugi prin recepție de la furnizor (cu prețuri și stoc auto). Procentaj poate seta automat prețul de vânzare.')
|
||||
->emptyStateHeading(__('Depozit gol'))
|
||||
->emptyStateDescription(__('Adaugă piese manual, sau folosește Achiziții ca să le adaugi prin recepție de la furnizor (cu prețuri și stoc auto). Procentaj poate seta automat prețul de vânzare.'))
|
||||
->emptyStateIcon('heroicon-o-cube')
|
||||
->defaultSort('name');
|
||||
}
|
||||
|
||||
+15
-10
@@ -10,36 +10,41 @@ class BatchesRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'batches';
|
||||
|
||||
protected static ?string $title = 'Loturi (FIFO)';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
|
||||
{
|
||||
return __('Loturi (FIFO)');
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('received_at')
|
||||
->label('Recepție')
|
||||
->label(__('Recepție'))
|
||||
->dateTime('d.m.Y H:i')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('warehouse.code')->label('Depozit')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('batch_ref')->label('Ref.')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('supplier.name')->label('Furnizor')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('warehouse.code')->label(__('Depozit'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('batch_ref')->label(__('Ref.'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('supplier.name')->label(__('Furnizor'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('qty_in')
|
||||
->label('Intrat')
|
||||
->label(__('Intrat'))
|
||||
->numeric(decimalPlaces: 2)
|
||||
->alignRight(),
|
||||
Tables\Columns\TextColumn::make('qty_remaining')
|
||||
->label('Rămas')
|
||||
->label(__('Rămas'))
|
||||
->numeric(decimalPlaces: 2)
|
||||
->alignRight()
|
||||
->weight('bold')
|
||||
->color(fn ($state) => (float) $state <= 0 ? 'gray' : 'success'),
|
||||
Tables\Columns\TextColumn::make('buy_price')
|
||||
->label('Preț unit.')
|
||||
->label(__('Preț unit.'))
|
||||
->money('MDL')
|
||||
->alignRight(),
|
||||
])
|
||||
->defaultSort('received_at')
|
||||
->emptyStateHeading('Niciun lot înregistrat')
|
||||
->emptyStateDescription('Apasă „Recepție" pe lista de piese pentru a înregistra prima intrare în depozit.');
|
||||
->emptyStateHeading(__('Niciun lot înregistrat'))
|
||||
->emptyStateDescription(__('Apasă „Recepție" pe lista de piese pentru a înregistra prima intrare în depozit.'));
|
||||
}
|
||||
}
|
||||
|
||||
+11
-6
@@ -13,13 +13,18 @@ class CrossRefsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'crossRefs';
|
||||
|
||||
protected static ?string $title = 'Coduri cross (OEM/echivalente)';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
|
||||
{
|
||||
return __('Coduri cross (OEM/echivalente)');
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Forms\Components\TextInput::make('cross_article')->label('Cod echivalent')->required()->maxLength(64),
|
||||
Forms\Components\TextInput::make('brand')->label('Brand')->maxLength(64),
|
||||
Forms\Components\TextInput::make('cross_article')->label(__('Cod echivalent'))->required()->maxLength(64),
|
||||
Forms\Components\TextInput::make('brand')->label(__('Brand'))->maxLength(64),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -28,12 +33,12 @@ class CrossRefsRelationManager extends RelationManager
|
||||
return $table
|
||||
->recordTitleAttribute('cross_article')
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('cross_article')->label('Cod')->searchable(),
|
||||
Tables\Columns\TextColumn::make('cross_article')->label(__('Cod'))->searchable(),
|
||||
Tables\Columns\TextColumn::make('brand')->placeholder('—'),
|
||||
])
|
||||
->headerActions([Actions\CreateAction::make()])
|
||||
->actions([Actions\EditAction::make(), Actions\DeleteAction::make()])
|
||||
->emptyStateHeading('Niciun cod cross')
|
||||
->emptyStateDescription('Adaugă coduri echivalente OEM/aftermarket ca să fie găsite în căutarea din magazin.');
|
||||
->emptyStateHeading(__('Niciun cod cross'))
|
||||
->emptyStateDescription(__('Adaugă coduri echivalente OEM/aftermarket ca să fie găsite în căutarea din magazin.'));
|
||||
}
|
||||
}
|
||||
|
||||
+12
-7
@@ -10,26 +10,31 @@ class PriceHistoryRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'priceHistory';
|
||||
|
||||
protected static ?string $title = 'Istoric prețuri furnizori';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
|
||||
{
|
||||
return __('Istoric prețuri furnizori');
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('observed_at')
|
||||
->label('Data')
|
||||
->label(__('Data'))
|
||||
->dateTime('d.m.Y H:i')
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('supplier.name')->label('Furnizor')->searchable(),
|
||||
Tables\Columns\TextColumn::make('purchase.number')->label('PO')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('supplier.name')->label(__('Furnizor'))->searchable(),
|
||||
Tables\Columns\TextColumn::make('purchase.number')->label(__('PO'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('price')
|
||||
->money('MDL')
|
||||
->alignRight()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('currency')->label('Val.'),
|
||||
Tables\Columns\TextColumn::make('currency')->label(__('Val.')),
|
||||
])
|
||||
->defaultSort('observed_at', 'desc')
|
||||
->emptyStateHeading('Niciun preț înregistrat')
|
||||
->emptyStateDescription('Prețurile se înregistrează automat la fiecare recepție de PO.');
|
||||
->emptyStateHeading(__('Niciun preț înregistrat'))
|
||||
->emptyStateDescription(__('Prețurile se înregistrează automat la fiecare recepție de PO.'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,19 @@ class PaymentResource extends Resource
|
||||
return __('nav.group.Finanțe');
|
||||
}
|
||||
|
||||
protected static ?string $modelLabel = 'plată';
|
||||
protected static ?string $modelLabel = null;
|
||||
|
||||
protected static ?string $pluralModelLabel = 'plăți';
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('plată');
|
||||
}
|
||||
|
||||
protected static ?string $pluralModelLabel = null;
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('plăți');
|
||||
}
|
||||
|
||||
protected static ?int $navigationSort = 50;
|
||||
|
||||
@@ -54,28 +64,28 @@ class PaymentResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Plată')
|
||||
Schemas\Components\Section::make(__('Plată'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\DatePicker::make('paid_at')->label('Data')->default(today())->required(),
|
||||
Forms\Components\DatePicker::make('paid_at')->label(__('Data'))->default(today())->required(),
|
||||
Forms\Components\Select::make('method')
|
||||
->options(Payment::METHODS)
|
||||
->default('cash')
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('amount')->label('Sumă')->numeric()->required(),
|
||||
Forms\Components\TextInput::make('reference')->label('Referință (chitanță / tranzacție)')->maxLength(64),
|
||||
Forms\Components\TextInput::make('amount')->label(__('Sumă'))->numeric()->required(),
|
||||
Forms\Components\TextInput::make('reference')->label(__('Referință (chitanță / tranzacție)'))->maxLength(64),
|
||||
Forms\Components\Select::make('client_id')
|
||||
->label('Client')
|
||||
->label(__('Client'))
|
||||
->options(fn () => Client::pluck('name', 'id'))
|
||||
->searchable(),
|
||||
Forms\Components\Select::make('work_order_id')
|
||||
->label('Fișă lucru')
|
||||
->label(__('Fișă lucru'))
|
||||
->options(fn () => WorkOrder::orderBy('id', 'desc')->limit(50)
|
||||
->get()->mapWithKeys(fn ($w) => [$w->id => "{$w->number} — " . ($w->client?->name ?? '?')])
|
||||
->toArray())
|
||||
->searchable()
|
||||
->helperText('Plata stabilește automat starea fișei (unpaid/partial/paid)'),
|
||||
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(2),
|
||||
->helperText(__('Plata stabilește automat starea fișei (unpaid/partial/paid)')),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(2),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -84,22 +94,22 @@ class PaymentResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('paid_at')->label('Data')->date('d.m.Y')->sortable(),
|
||||
Tables\Columns\TextColumn::make('client.name')->label('Client')->searchable(),
|
||||
Tables\Columns\TextColumn::make('workOrder.number')->label('Fișă')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('paid_at')->label(__('Data'))->date('d.m.Y')->sortable(),
|
||||
Tables\Columns\TextColumn::make('client.name')->label(__('Client'))->searchable(),
|
||||
Tables\Columns\TextColumn::make('workOrder.number')->label(__('Fișă'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('method')
|
||||
->formatStateUsing(fn ($s) => Payment::METHODS[$s] ?? $s)
|
||||
->badge(),
|
||||
Tables\Columns\TextColumn::make('amount')->money('MDL')->alignRight()->sortable()->summarize(Tables\Columns\Summarizers\Sum::make()->money('MDL')->label('Total')),
|
||||
Tables\Columns\TextColumn::make('reference')->label('Ref.')->placeholder('—')->toggleable(),
|
||||
Tables\Columns\TextColumn::make('amount')->money('MDL')->alignRight()->sortable()->summarize(Tables\Columns\Summarizers\Sum::make()->money('MDL')->label(__('Total'))),
|
||||
Tables\Columns\TextColumn::make('reference')->label(__('Ref.'))->placeholder('—')->toggleable(),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('method')->options(Payment::METHODS),
|
||||
Tables\Filters\Filter::make('today')
|
||||
->label('Astăzi')
|
||||
->label(__('Astăzi'))
|
||||
->query(fn ($q) => $q->whereDate('paid_at', today())),
|
||||
Tables\Filters\Filter::make('this_month')
|
||||
->label('Luna curentă')
|
||||
->label(__('Luna curentă'))
|
||||
->query(fn ($q) => $q->whereMonth('paid_at', now()->month)->whereYear('paid_at', now()->year)),
|
||||
])
|
||||
->actions([
|
||||
|
||||
@@ -31,7 +31,12 @@ class PayrollAdjustmentResource extends Resource
|
||||
|
||||
protected static ?string $modelLabel = 'ajustare';
|
||||
|
||||
protected static ?string $pluralModelLabel = 'bonusuri/avansuri';
|
||||
protected static ?string $pluralModelLabel = null;
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('bonusuri/avansuri');
|
||||
}
|
||||
|
||||
protected static ?int $navigationSort = 54;
|
||||
|
||||
@@ -48,11 +53,11 @@ class PayrollAdjustmentResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Detalii')
|
||||
Schemas\Components\Section::make(__('Detalii'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Select::make('user_id')
|
||||
->label('Utilizator')
|
||||
->label(__('Utilizator'))
|
||||
->options(fn () => User::orderBy('name')->pluck('name', 'id'))
|
||||
->searchable()
|
||||
->required(),
|
||||
@@ -60,14 +65,14 @@ class PayrollAdjustmentResource extends Resource
|
||||
->options(PayrollAdjustment::TYPES)
|
||||
->default('bonus')
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('amount')->label('Sumă')->numeric()->required(),
|
||||
Forms\Components\TextInput::make('amount')->label(__('Sumă'))->numeric()->required(),
|
||||
Forms\Components\TextInput::make('period')
|
||||
->label('Perioadă (YYYY-MM)')
|
||||
->label(__('Perioadă (YYYY-MM)'))
|
||||
->placeholder(now()->format('Y-m'))
|
||||
->regex('/^\d{4}-\d{2}$/')
|
||||
->required(),
|
||||
Forms\Components\DatePicker::make('date')->default(today())->required(),
|
||||
Forms\Components\TextInput::make('reason')->label('Motiv')->maxLength(160),
|
||||
Forms\Components\TextInput::make('reason')->label(__('Motiv'))->maxLength(160),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -77,7 +82,7 @@ class PayrollAdjustmentResource extends Resource
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('date')->date('d.m.Y')->sortable(),
|
||||
Tables\Columns\TextColumn::make('user.name')->label('Utilizator')->searchable(),
|
||||
Tables\Columns\TextColumn::make('user.name')->label(__('Utilizator'))->searchable(),
|
||||
Tables\Columns\TextColumn::make('type')
|
||||
->formatStateUsing(fn ($s) => PayrollAdjustment::TYPES[$s] ?? $s)
|
||||
->badge()
|
||||
@@ -89,12 +94,12 @@ class PayrollAdjustmentResource extends Resource
|
||||
Tables\Columns\TextColumn::make('period'),
|
||||
Tables\Columns\TextColumn::make('amount')->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('reason')->limit(40),
|
||||
Tables\Columns\IconColumn::make('applied')->boolean()->label('Aplicat'),
|
||||
Tables\Columns\IconColumn::make('applied')->boolean()->label(__('Aplicat')),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('type')->options(PayrollAdjustment::TYPES),
|
||||
Tables\Filters\SelectFilter::make('user_id')
|
||||
->label('Utilizator')
|
||||
->label(__('Utilizator'))
|
||||
->options(fn () => User::pluck('name', 'id')),
|
||||
])
|
||||
->actions([
|
||||
|
||||
@@ -50,40 +50,40 @@ class PayrollRunResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Detalii')
|
||||
Schemas\Components\Section::make(__('Detalii'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Select::make('user_id')
|
||||
->label('Utilizator')
|
||||
->label(__('Utilizator'))
|
||||
->options(fn () => User::orderBy('name')->pluck('name', 'id'))
|
||||
->searchable()
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('period')
|
||||
->label('Perioada (YYYY-MM)')
|
||||
->label(__('Perioada (YYYY-MM)'))
|
||||
->placeholder(now()->format('Y-m'))
|
||||
->required()
|
||||
->regex('/^\d{4}-\d{2}$/'),
|
||||
]),
|
||||
Schemas\Components\Section::make('Calcul')
|
||||
Schemas\Components\Section::make(__('Calcul'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('base')->label('Bază')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('works_revenue')->label('Venit manopere')->numeric()->disabled(),
|
||||
Forms\Components\TextInput::make('works_pct_amount')->label('Comision manopere')->numeric()->disabled(),
|
||||
Forms\Components\TextInput::make('parts_margin')->label('Marja piese')->numeric()->disabled(),
|
||||
Forms\Components\TextInput::make('parts_pct_amount')->label('Comision piese')->numeric()->disabled(),
|
||||
Forms\Components\TextInput::make('base')->label(__('Bază'))->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('works_revenue')->label(__('Venit manopere'))->numeric()->disabled(),
|
||||
Forms\Components\TextInput::make('works_pct_amount')->label(__('Comision manopere'))->numeric()->disabled(),
|
||||
Forms\Components\TextInput::make('parts_margin')->label(__('Marja piese'))->numeric()->disabled(),
|
||||
Forms\Components\TextInput::make('parts_pct_amount')->label(__('Comision piese'))->numeric()->disabled(),
|
||||
Forms\Components\TextInput::make('bonus')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('fines')->label('Penalizări')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('advance')->label('Avans')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('total')->label('Total net')->numeric()->disabled(),
|
||||
Forms\Components\TextInput::make('fines')->label(__('Penalizări'))->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('advance')->label(__('Avans'))->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('total')->label(__('Total net'))->numeric()->disabled(),
|
||||
]),
|
||||
Schemas\Components\Section::make('Plată')
|
||||
Schemas\Components\Section::make(__('Plată'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Toggle::make('paid')->label('Achitat'),
|
||||
Forms\Components\DatePicker::make('paid_at')->label('Data plății'),
|
||||
Forms\Components\Toggle::make('paid')->label(__('Achitat')),
|
||||
Forms\Components\DatePicker::make('paid_at')->label(__('Data plății')),
|
||||
]),
|
||||
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(2),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(2),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -91,21 +91,21 @@ class PayrollRunResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('period')->label('Perioadă')->sortable(),
|
||||
Tables\Columns\TextColumn::make('user.name')->label('Utilizator')->searchable(),
|
||||
Tables\Columns\TextColumn::make('period')->label(__('Perioadă'))->sortable(),
|
||||
Tables\Columns\TextColumn::make('user.name')->label(__('Utilizator'))->searchable(),
|
||||
Tables\Columns\TextColumn::make('base')->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('works_pct_amount')->label('% manopere')->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('parts_pct_amount')->label('% piese')->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('works_pct_amount')->label(__('% manopere'))->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('parts_pct_amount')->label(__('% piese'))->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('bonus')->money('MDL')->alignRight()->color('success'),
|
||||
Tables\Columns\TextColumn::make('fines')->money('MDL')->alignRight()->color('danger'),
|
||||
Tables\Columns\TextColumn::make('advance')->money('MDL')->alignRight()->color('warning'),
|
||||
Tables\Columns\TextColumn::make('total')->label('Total')->money('MDL')->alignRight()->weight('bold')
|
||||
Tables\Columns\TextColumn::make('total')->label(__('Total'))->money('MDL')->alignRight()->weight('bold')
|
||||
->summarize(Tables\Columns\Summarizers\Sum::make()->money('MDL')),
|
||||
Tables\Columns\IconColumn::make('paid')->boolean(),
|
||||
])
|
||||
->headerActions([
|
||||
Actions\Action::make('compute_all')
|
||||
->label('Calculează luna curentă')
|
||||
->label(__('Calculează luna curentă'))
|
||||
->icon('heroicon-m-calculator')
|
||||
->color('primary')
|
||||
->action(function () {
|
||||
@@ -122,13 +122,13 @@ class PayrollRunResource extends Resource
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('period')
|
||||
->label('Perioadă')
|
||||
->label(__('Perioadă'))
|
||||
->options(fn () => PayrollRun::distinct()->pluck('period', 'period')->toArray()),
|
||||
Tables\Filters\TernaryFilter::make('paid')->label('Achitat'),
|
||||
Tables\Filters\TernaryFilter::make('paid')->label(__('Achitat')),
|
||||
])
|
||||
->actions([
|
||||
Actions\Action::make('recompute')
|
||||
->label('Recalculează')
|
||||
->label(__('Recalculează'))
|
||||
->icon('heroicon-m-arrow-path')
|
||||
->action(fn (PayrollRun $r) => app(PayrollCalculator::class)->compute($r->user_id, $r->period)),
|
||||
Actions\EditAction::make(),
|
||||
|
||||
@@ -32,7 +32,12 @@ class PostResource extends Resource
|
||||
|
||||
protected static ?string $modelLabel = 'pod';
|
||||
|
||||
protected static ?string $pluralModelLabel = 'posturi de lucru';
|
||||
protected static ?string $pluralModelLabel = null;
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('posturi de lucru');
|
||||
}
|
||||
|
||||
protected static ?int $navigationSort = 76;
|
||||
|
||||
@@ -44,37 +49,37 @@ class PostResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Pod / Spațiu lucru')
|
||||
Schemas\Components\Section::make(__('Pod / Spațiu lucru'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')
|
||||
->label('Nume')
|
||||
->label(__('Nume'))
|
||||
->required()
|
||||
->maxLength(80)
|
||||
->placeholder('Ex: Pod 1, Curte 1, Atelier electric'),
|
||||
->placeholder(__('Ex: Pod 1, Curte 1, Atelier electric')),
|
||||
Forms\Components\ColorPicker::make('color')
|
||||
->default('#3b82f6'),
|
||||
Forms\Components\TextInput::make('hours_per_day')
|
||||
->label('Ore disponibile / zi')
|
||||
->label(__('Ore disponibile / zi'))
|
||||
->numeric()
|
||||
->step(0.5)
|
||||
->default(10)
|
||||
->helperText('Capacitatea zilnică în ore'),
|
||||
->helperText(__('Capacitatea zilnică în ore')),
|
||||
Forms\Components\Select::make('default_master_id')
|
||||
->label('Mecanic implicit')
|
||||
->label(__('Mecanic implicit'))
|
||||
->options(fn () => User::where('status', 'active')->pluck('name', 'id'))
|
||||
->searchable()
|
||||
->placeholder('Niciun mecanic implicit')
|
||||
->helperText('Va fi pre-completat când creezi o programare pentru acest pod'),
|
||||
->placeholder(__('Niciun mecanic implicit'))
|
||||
->helperText(__('Va fi pre-completat când creezi o programare pentru acest pod')),
|
||||
Forms\Components\TextInput::make('description')
|
||||
->label('Descriere')
|
||||
->label(__('Descriere'))
|
||||
->maxLength(255)
|
||||
->placeholder('Ex: cu lift, fără lift, doar diagnoză...')
|
||||
->placeholder(__('Ex: cu lift, fără lift, doar diagnoză...'))
|
||||
->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('sort_order')
|
||||
->numeric()
|
||||
->default(100),
|
||||
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true),
|
||||
Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -85,11 +90,11 @@ class PostResource extends Resource
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
|
||||
Tables\Columns\ColorColumn::make('color'),
|
||||
Tables\Columns\TextColumn::make('hours_per_day')->label('Ore/zi')->sortable(),
|
||||
Tables\Columns\TextColumn::make('defaultMaster.name')->label('Mecanic implicit')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('hours_per_day')->label(__('Ore/zi'))->sortable(),
|
||||
Tables\Columns\TextColumn::make('defaultMaster.name')->label(__('Mecanic implicit'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('description')->placeholder('—')->limit(40)->toggleable(),
|
||||
Tables\Columns\TextColumn::make('appointments_count')->counts('appointments')->label('Programări')->badge(),
|
||||
Tables\Columns\ToggleColumn::make('is_active')->label('Activ'),
|
||||
Tables\Columns\TextColumn::make('appointments_count')->counts('appointments')->label(__('Programări'))->badge(),
|
||||
Tables\Columns\ToggleColumn::make('is_active')->label(__('Activ')),
|
||||
])
|
||||
->actions([
|
||||
Actions\EditAction::make(),
|
||||
|
||||
@@ -28,57 +28,53 @@ class PricingCoefficientResource extends Resource
|
||||
return __('nav.group.Depozit');
|
||||
}
|
||||
|
||||
protected static ?string $modelLabel = 'coeficient';
|
||||
|
||||
protected static ?string $pluralModelLabel = 'coeficienți preț';
|
||||
|
||||
protected static ?int $navigationSort = 46;
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Coeficient')
|
||||
Schemas\Components\Section::make(__('Coeficient'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')->label('Denumire')->required()
|
||||
->placeholder('ex: Mașină veche, Client VIP, Express')->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()
|
||||
->placeholder(__('ex: Mașină veche, Client VIP, Express'))->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('multiplier')
|
||||
->label('Multiplicator')
|
||||
->label(__('Multiplicator'))
|
||||
->numeric()
|
||||
->required()
|
||||
->default(1.10)
|
||||
->helperText('1.15 = +15% peste prețul de bază. 0.95 = -5%.'),
|
||||
Forms\Components\TextInput::make('priority')->label('Prioritate')->numeric()->default(100),
|
||||
->helperText(__('1.15 = +15% peste prețul de bază. 0.95 = -5%.')),
|
||||
Forms\Components\TextInput::make('priority')->label(__('Prioritate'))->numeric()->default(100),
|
||||
Forms\Components\Toggle::make('stackable')
|
||||
->label('Cumulabil')
|
||||
->label(__('Cumulabil'))
|
||||
->default(true)
|
||||
->helperText('Cumulabil = se înmulțește cu alți coeficienți. Necumulabil = doar cel mai mare necumulabil se aplică.'),
|
||||
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true),
|
||||
->helperText(__('Cumulabil = se înmulțește cu alți coeficienți. Necumulabil = doar cel mai mare necumulabil se aplică.')),
|
||||
Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
|
||||
]),
|
||||
Schemas\Components\Section::make('Condiții (toate trebuie îndeplinite)')
|
||||
->description('Lasă gol = se aplică mereu. Combină condițiile pentru a ținti situații specifice.')
|
||||
Schemas\Components\Section::make(__('Condiții (toate trebuie îndeplinite)'))
|
||||
->description(__('Lasă gol = se aplică mereu. Combină condițiile pentru a ținti situații specifice.'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\CheckboxList::make('conditions.classes')
|
||||
->label('Clase auto')
|
||||
->label(__('Clase auto'))
|
||||
->options(PricingCoefficient::VEHICLE_CLASSES)
|
||||
->columns(2)
|
||||
->columnSpanFull(),
|
||||
Forms\Components\CheckboxList::make('conditions.body_types')
|
||||
->label('Caroserie')
|
||||
->label(__('Caroserie'))
|
||||
->options(\App\Models\Tenant\Vehicle::BODY_TYPES)
|
||||
->columns(3)
|
||||
->columnSpanFull(),
|
||||
Forms\Components\CheckboxList::make('conditions.transmissions')
|
||||
->label('Cutie de viteze')
|
||||
->label(__('Cutie de viteze'))
|
||||
->options(\App\Models\Tenant\Vehicle::TRANSMISSION_TYPES)
|
||||
->columns(3)
|
||||
->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('conditions.age_min')->label('Vârstă min (ani)')->numeric(),
|
||||
Forms\Components\TextInput::make('conditions.age_max')->label('Vârstă max (ani)')->numeric(),
|
||||
Forms\Components\Toggle::make('conditions.client_vip')->label('Doar clienți VIP'),
|
||||
Forms\Components\TextInput::make('conditions.age_min')->label(__('Vârstă min (ani)'))->numeric(),
|
||||
Forms\Components\TextInput::make('conditions.age_max')->label(__('Vârstă max (ani)'))->numeric(),
|
||||
Forms\Components\Toggle::make('conditions.client_vip')->label(__('Doar clienți VIP')),
|
||||
Forms\Components\CheckboxList::make('conditions.urgency')
|
||||
->label('Urgență')
|
||||
->label(__('Urgență'))
|
||||
->options(PricingCoefficient::URGENCY)
|
||||
->columns(3)
|
||||
->columnSpanFull(),
|
||||
@@ -90,25 +86,25 @@ class PricingCoefficientResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('priority')->label('Prio')->sortable()->alignRight(),
|
||||
Tables\Columns\TextColumn::make('priority')->label(__('Prio'))->sortable()->alignRight(),
|
||||
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('multiplier')
|
||||
->label('Multiplicator')
|
||||
->label(__('Multiplicator'))
|
||||
->formatStateUsing(fn ($s) => '×' . rtrim(rtrim(number_format((float) $s, 3), '0'), '.'))
|
||||
->alignRight()
|
||||
->color(fn ($s) => (float) $s >= 1 ? 'success' : 'warning'),
|
||||
Tables\Columns\IconColumn::make('stackable')->label('Cumul.')->boolean(),
|
||||
Tables\Columns\IconColumn::make('is_active')->label('Activ')->boolean(),
|
||||
Tables\Columns\IconColumn::make('stackable')->label(__('Cumul.'))->boolean(),
|
||||
Tables\Columns\IconColumn::make('is_active')->label(__('Activ'))->boolean(),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\TernaryFilter::make('is_active')->label('Active'),
|
||||
Tables\Filters\TernaryFilter::make('is_active')->label(__('Active')),
|
||||
])
|
||||
->actions([
|
||||
Actions\EditAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
])
|
||||
->emptyStateHeading('Niciun coeficient')
|
||||
->emptyStateDescription('Adaugă reguli care ajustează prețul în funcție de vârsta mașinii, clasă (SUV, comercial, hibrid), client VIP sau urgență. Se aplică peste markup-ul de bază pe fișele de lucru.')
|
||||
->emptyStateHeading(__('Niciun coeficient'))
|
||||
->emptyStateDescription(__('Adaugă reguli care ajustează prețul în funcție de vârsta mașinii, clasă (SUV, comercial, hibrid), client VIP sau urgență. Se aplică peste markup-ul de bază pe fișele de lucru.'))
|
||||
->emptyStateIcon('heroicon-o-adjustments-horizontal')
|
||||
->defaultSort('priority');
|
||||
}
|
||||
|
||||
@@ -32,26 +32,36 @@ class PurchaseResource extends Resource
|
||||
return __('nav.group.Depozit');
|
||||
}
|
||||
|
||||
protected static ?string $modelLabel = 'achiziție';
|
||||
protected static ?string $modelLabel = null;
|
||||
|
||||
protected static ?string $pluralModelLabel = 'achiziții';
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('achiziție');
|
||||
}
|
||||
|
||||
protected static ?string $pluralModelLabel = null;
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('achiziții');
|
||||
}
|
||||
|
||||
protected static ?int $navigationSort = 43;
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Antet')
|
||||
Schemas\Components\Section::make(__('Antet'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('number')->label('Nr.')->disabled()->dehydrated(false)->placeholder('Generat automat'),
|
||||
Forms\Components\TextInput::make('number')->label(__('Nr.'))->disabled()->dehydrated(false)->placeholder(__('Generat automat')),
|
||||
Forms\Components\Select::make('supplier_id')
|
||||
->label('Furnizor')
|
||||
->label(__('Furnizor'))
|
||||
->options(fn () => Supplier::where('is_active', true)->pluck('name', 'id'))
|
||||
->searchable()
|
||||
->required(),
|
||||
Forms\Components\Select::make('warehouse_id')
|
||||
->label('Depozit țintă')
|
||||
->label(__('Depozit țintă'))
|
||||
->options(fn () => Warehouse::where('is_active', true)->pluck('name', 'id'))
|
||||
->default(fn () => Warehouse::where('is_default', true)->value('id'))
|
||||
->required(),
|
||||
@@ -59,12 +69,12 @@ class PurchaseResource extends Resource
|
||||
->options(Purchase::STATUSES)
|
||||
->default('draft')
|
||||
->required(),
|
||||
Forms\Components\DatePicker::make('order_date')->label('Data comandă')->default(today())->required(),
|
||||
Forms\Components\DatePicker::make('expected_at')->label('Așteptată'),
|
||||
Forms\Components\DatePicker::make('received_at')->label('Recepționată'),
|
||||
Forms\Components\DatePicker::make('paid_at')->label('Plătită')->columnSpanFull(),
|
||||
Forms\Components\DatePicker::make('order_date')->label(__('Data comandă'))->default(today())->required(),
|
||||
Forms\Components\DatePicker::make('expected_at')->label(__('Așteptată')),
|
||||
Forms\Components\DatePicker::make('received_at')->label(__('Recepționată')),
|
||||
Forms\Components\DatePicker::make('paid_at')->label(__('Plătită'))->columnSpanFull(),
|
||||
]),
|
||||
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -72,11 +82,11 @@ class PurchaseResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('number')->label('Nr.')->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('supplier.name')->label('Furnizor')->searchable(),
|
||||
Tables\Columns\TextColumn::make('order_date')->label('Comandată')->date('d.m.Y'),
|
||||
Tables\Columns\TextColumn::make('expected_at')->label('Așteptată')->date('d.m.Y')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('received_at')->label('Recepționată')->date('d.m.Y')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('number')->label(__('Nr.'))->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('supplier.name')->label(__('Furnizor'))->searchable(),
|
||||
Tables\Columns\TextColumn::make('order_date')->label(__('Comandată'))->date('d.m.Y'),
|
||||
Tables\Columns\TextColumn::make('expected_at')->label(__('Așteptată'))->date('d.m.Y')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('received_at')->label(__('Recepționată'))->date('d.m.Y')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('status')
|
||||
->formatStateUsing(fn ($s) => Purchase::STATUSES[$s] ?? $s)
|
||||
->badge()
|
||||
@@ -88,7 +98,7 @@ class PurchaseResource extends Resource
|
||||
'danger' => ['cancelled'],
|
||||
]),
|
||||
Tables\Columns\TextColumn::make('received_progress')
|
||||
->label('Progres')
|
||||
->label(__('Progres'))
|
||||
->state(function (Purchase $r) {
|
||||
$items = $r->items;
|
||||
$ord = (float) $items->sum('qty');
|
||||
@@ -101,27 +111,27 @@ class PurchaseResource extends Resource
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('status')->options(Purchase::STATUSES),
|
||||
Tables\Filters\SelectFilter::make('supplier_id')
|
||||
->label('Furnizor')
|
||||
->label(__('Furnizor'))
|
||||
->options(fn () => Supplier::pluck('name', 'id')),
|
||||
])
|
||||
->actions([
|
||||
Actions\Action::make('receive_all')
|
||||
->label('Recepție totală')
|
||||
->label(__('Recepție totală'))
|
||||
->icon('heroicon-m-check-circle')
|
||||
->color('success')
|
||||
->visible(fn (Purchase $r) => ! in_array($r->status, ['received', 'cancelled', 'draft'], true))
|
||||
->requiresConfirmation()
|
||||
->modalDescription('Se vor crea batch-uri pentru toate restanțele rămase în depozitul țintă.')
|
||||
->modalDescription(__('Se vor crea batch-uri pentru toate restanțele rămase în depozitul țintă.'))
|
||||
->action(function (Purchase $r) {
|
||||
try {
|
||||
$r->receiveAllRemaining();
|
||||
Notification::make()
|
||||
->title('Recepție completă — batch-uri create')
|
||||
->title(__('Recepție completă — batch-uri create'))
|
||||
->success()
|
||||
->send();
|
||||
} catch (\Throwable $e) {
|
||||
Notification::make()
|
||||
->title('Eroare')
|
||||
->title(__('Eroare'))
|
||||
->body($e->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
@@ -21,14 +21,14 @@ class ListPurchases extends ListRecords
|
||||
{
|
||||
return [
|
||||
Actions\Action::make('ocr')
|
||||
->label('Import factură (OCR)')
|
||||
->label(__('Import factură (OCR)'))
|
||||
->icon('heroicon-m-document-arrow-up')
|
||||
->color('gray')
|
||||
->modalHeading('Import factură via OCR')
|
||||
->modalDescription('Încarcă o poză cu factura. AI-ul extrage furnizorul, data și liniile. Verifici și salvezi.')
|
||||
->modalHeading(__('Import factură via OCR'))
|
||||
->modalDescription(__('Încarcă o poză cu factura. AI-ul extrage furnizorul, data și liniile. Verifici și salvezi.'))
|
||||
->schema([
|
||||
Forms\Components\FileUpload::make('invoice')
|
||||
->label('Foto factură')
|
||||
->label(__('Foto factură'))
|
||||
->image()
|
||||
->disk('local')
|
||||
->directory('ocr-imports')
|
||||
@@ -41,7 +41,7 @@ class ListPurchases extends ListRecords
|
||||
|
||||
if (! ($result['ok'] ?? false)) {
|
||||
Notification::make()
|
||||
->title('OCR eșuat')
|
||||
->title(__('OCR eșuat'))
|
||||
->body($result['error'] ?? 'Eroare necunoscută.')
|
||||
->danger()->send();
|
||||
@unlink($abs);
|
||||
@@ -83,7 +83,7 @@ class ListPurchases extends ListRecords
|
||||
@unlink($abs);
|
||||
|
||||
Notification::make()
|
||||
->title('Factură importată')
|
||||
->title(__('Factură importată'))
|
||||
->body(sprintf('%d linii, total %.2f. Verifică și ajustează înainte de a confirma.',
|
||||
count($payload['items']), (float) $purchase->total))
|
||||
->success()->send();
|
||||
|
||||
+21
-16
@@ -18,13 +18,18 @@ class ItemsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'items';
|
||||
|
||||
protected static ?string $title = 'Articole';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
|
||||
{
|
||||
return __('Articole');
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Forms\Components\Select::make('part_id')
|
||||
->label('Piesă din catalog')
|
||||
->label(__('Piesă din catalog'))
|
||||
->options(fn () => Part::where('is_active', true)
|
||||
->get()
|
||||
->mapWithKeys(fn ($p) => [$p->id => "{$p->name} " . ($p->article ? "[{$p->article}]" : '')])
|
||||
@@ -40,11 +45,11 @@ class ItemsRelationManager extends RelationManager
|
||||
}
|
||||
})
|
||||
->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('name')->label('Denumire')->required()->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('article')->label('Cod articol'),
|
||||
Forms\Components\TextInput::make('qty')->label('Cantitate')->numeric()->default(1)->required(),
|
||||
Forms\Components\TextInput::make('unit')->label('UM')->default('buc'),
|
||||
Forms\Components\TextInput::make('buy_price')->label('Preț achiziție')->numeric()->required(),
|
||||
Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('article')->label(__('Cod articol')),
|
||||
Forms\Components\TextInput::make('qty')->label(__('Cantitate'))->numeric()->default(1)->required(),
|
||||
Forms\Components\TextInput::make('unit')->label(__('UM'))->default('buc'),
|
||||
Forms\Components\TextInput::make('buy_price')->label(__('Preț achiziție'))->numeric()->required(),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -55,13 +60,13 @@ class ItemsRelationManager extends RelationManager
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')->wrap(),
|
||||
Tables\Columns\TextColumn::make('article')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('qty')->label('Comandat')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('qty')->label(__('Comandat'))->alignRight(),
|
||||
Tables\Columns\TextColumn::make('qty_received')
|
||||
->label('Recepționat')
|
||||
->label(__('Recepționat'))
|
||||
->alignRight()
|
||||
->color(fn ($state, $record) => $record->isFullyReceived() ? 'success' : ((float) $state > 0 ? 'warning' : 'gray'))
|
||||
->formatStateUsing(fn ($state, $record) => sprintf('%.2f / %.2f', (float) $state, (float) $record->qty)),
|
||||
Tables\Columns\TextColumn::make('unit')->label('UM'),
|
||||
Tables\Columns\TextColumn::make('unit')->label(__('UM')),
|
||||
Tables\Columns\TextColumn::make('buy_price')->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('total')->money('MDL')->alignRight(),
|
||||
])
|
||||
@@ -70,22 +75,22 @@ class ItemsRelationManager extends RelationManager
|
||||
])
|
||||
->actions([
|
||||
Actions\Action::make('receive_item')
|
||||
->label('Recepționează')
|
||||
->label(__('Recepționează'))
|
||||
->icon('heroicon-m-arrow-down-tray')
|
||||
->color('success')
|
||||
->visible(fn (PurchaseItem $r) => ! $r->isFullyReceived())
|
||||
->schema([
|
||||
Forms\Components\Placeholder::make('outstanding')
|
||||
->label('Restanță')
|
||||
->label(__('Restanță'))
|
||||
->content(fn (PurchaseItem $r) => sprintf('%.2f %s', $r->outstanding(), $r->unit ?? 'buc')),
|
||||
Forms\Components\TextInput::make('qty')
|
||||
->label('Cantitate recepționată')
|
||||
->label(__('Cantitate recepționată'))
|
||||
->numeric()
|
||||
->required()
|
||||
->minValue(0.001)
|
||||
->default(fn (PurchaseItem $r) => $r->outstanding()),
|
||||
Forms\Components\Select::make('warehouse_id')
|
||||
->label('Depozit țintă')
|
||||
->label(__('Depozit țintă'))
|
||||
->options(fn () => Warehouse::where('is_active', true)->pluck('name', 'id'))
|
||||
->default(fn (PurchaseItem $r) => $r->purchase?->warehouse_id
|
||||
?? Warehouse::where('is_default', true)->value('id'))
|
||||
@@ -96,12 +101,12 @@ class ItemsRelationManager extends RelationManager
|
||||
try {
|
||||
$r->purchase->receiveItem($r, (float) $data['qty'], $wh);
|
||||
Notification::make()
|
||||
->title('Recepționat — batch creat')
|
||||
->title(__('Recepționat — batch creat'))
|
||||
->success()
|
||||
->send();
|
||||
} catch (\Throwable $e) {
|
||||
Notification::make()
|
||||
->title('Eroare la recepție')
|
||||
->title(__('Eroare la recepție'))
|
||||
->body($e->getMessage())
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
@@ -43,16 +43,16 @@ class RoleResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Rol')
|
||||
Schemas\Components\Section::make(__('Rol'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')->label('Slug')->required()->maxLength(64)
|
||||
Forms\Components\TextInput::make('name')->label(__('Slug'))->required()->maxLength(64)
|
||||
->disabled(fn ($record) => $record && in_array($record->name, array_keys(Permissions::roleMatrix()), true))
|
||||
->helperText('Rolurile sistem (owner/admin/etc.) au numele blocat'),
|
||||
->helperText(__('Rolurile sistem (owner/admin/etc.) au numele blocat')),
|
||||
Forms\Components\TextInput::make('guard_name')->default('web')->disabled(),
|
||||
]),
|
||||
Schemas\Components\Section::make('Drepturi')
|
||||
->description('Bifează ce poate face acest rol. Modificările au efect imediat.')
|
||||
Schemas\Components\Section::make(__('Drepturi'))
|
||||
->description(__('Bifează ce poate face acest rol. Modificările au efect imediat.'))
|
||||
->schema(self::permissionFields())
|
||||
->columns(1),
|
||||
]);
|
||||
@@ -88,25 +88,25 @@ class RoleResource extends Resource
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->label('Rol')
|
||||
->label(__('Rol'))
|
||||
->formatStateUsing(fn ($state) => Permissions::roleLabels()[$state] ?? $state)
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('name')
|
||||
->label('Slug')
|
||||
->label(__('Slug'))
|
||||
->copyable()
|
||||
->color('gray'),
|
||||
Tables\Columns\TextColumn::make('permissions_count')
|
||||
->counts('permissions')
|
||||
->label('Drepturi')
|
||||
->label(__('Drepturi'))
|
||||
->badge(),
|
||||
Tables\Columns\TextColumn::make('users_count')
|
||||
->counts('users')
|
||||
->label('Utilizatori')
|
||||
->label(__('Utilizatori'))
|
||||
->badge(),
|
||||
])
|
||||
->actions([
|
||||
Actions\EditAction::make()->label('Editează drepturi'),
|
||||
Actions\EditAction::make()->label(__('Editează drepturi')),
|
||||
Actions\DeleteAction::make()
|
||||
->hidden(fn ($record) => in_array($record->name, array_keys(Permissions::roleMatrix()), true)),
|
||||
])
|
||||
|
||||
@@ -30,9 +30,19 @@ class ServiceTemplateResource extends Resource
|
||||
return __('nav.group.Service');
|
||||
}
|
||||
|
||||
protected static ?string $modelLabel = 'șablon';
|
||||
protected static ?string $modelLabel = null;
|
||||
|
||||
protected static ?string $pluralModelLabel = 'șabloane servicii';
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('șablon');
|
||||
}
|
||||
|
||||
protected static ?string $pluralModelLabel = null;
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('șabloane servicii');
|
||||
}
|
||||
|
||||
protected static ?int $navigationSort = 33;
|
||||
|
||||
@@ -42,14 +52,14 @@ class ServiceTemplateResource extends Resource
|
||||
Schemas\Components\Section::make()
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')->label('Denumire')->required()
|
||||
->placeholder('ex: Revizie completă 15.000 km')->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()
|
||||
->placeholder(__('ex: Revizie completă 15.000 km'))->columnSpanFull(),
|
||||
Forms\Components\Select::make('category')
|
||||
->label('Categorie')
|
||||
->label(__('Categorie'))
|
||||
->options(array_combine(Labor::CATEGORIES, Labor::CATEGORIES))
|
||||
->searchable(),
|
||||
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true),
|
||||
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2),
|
||||
Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -60,18 +70,18 @@ class ServiceTemplateResource extends Resource
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('category')->badge()->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('items_count')->counts('items')->label('Linii')->alignRight(),
|
||||
Tables\Columns\IconColumn::make('is_active')->label('Activ')->boolean(),
|
||||
Tables\Columns\TextColumn::make('items_count')->counts('items')->label(__('Linii'))->alignRight(),
|
||||
Tables\Columns\IconColumn::make('is_active')->label(__('Activ'))->boolean(),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\TernaryFilter::make('is_active')->label('Active'),
|
||||
Tables\Filters\TernaryFilter::make('is_active')->label(__('Active')),
|
||||
])
|
||||
->actions([
|
||||
Actions\EditAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
])
|
||||
->emptyStateHeading('Niciun șablon')
|
||||
->emptyStateDescription('Grupează manopere + piese frecvente într-un șablon (ex: „Schimb ulei complet") și aplică-l pe o fișă cu un click.')
|
||||
->emptyStateHeading(__('Niciun șablon'))
|
||||
->emptyStateDescription(__('Grupează manopere + piese frecvente într-un șablon (ex: „Schimb ulei complet") și aplică-l pe o fișă cu un click.'))
|
||||
->emptyStateIcon('heroicon-o-clipboard-document-list')
|
||||
->defaultSort('name');
|
||||
}
|
||||
|
||||
+15
-10
@@ -18,19 +18,24 @@ class ItemsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'items';
|
||||
|
||||
protected static ?string $title = 'Conținut șablon';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
|
||||
{
|
||||
return __('Conținut șablon');
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Forms\Components\Select::make('kind')
|
||||
->label('Tip')
|
||||
->label(__('Tip'))
|
||||
->options(ServiceTemplateItem::KINDS)
|
||||
->default('labor')
|
||||
->live()
|
||||
->required(),
|
||||
Forms\Components\Select::make('labor_id')
|
||||
->label('Manoperă')
|
||||
->label(__('Manoperă'))
|
||||
->options(fn () => Labor::where('is_active', true)->pluck('name_ro', 'id'))
|
||||
->searchable()
|
||||
->visible(fn (Get $get) => $get('kind') === 'labor')
|
||||
@@ -42,7 +47,7 @@ class ItemsRelationManager extends RelationManager
|
||||
}
|
||||
}),
|
||||
Forms\Components\Select::make('part_id')
|
||||
->label('Piesă')
|
||||
->label(__('Piesă'))
|
||||
->options(fn () => Part::where('is_active', true)
|
||||
->get()->mapWithKeys(fn ($p) => [$p->id => "{$p->name} " . ($p->article ? "[{$p->article}]" : '')])->toArray())
|
||||
->searchable()
|
||||
@@ -51,10 +56,10 @@ class ItemsRelationManager extends RelationManager
|
||||
->afterStateUpdated(function ($state, Set $set) {
|
||||
if ($state && $p = Part::find($state)) $set('name', $p->name);
|
||||
}),
|
||||
Forms\Components\TextInput::make('name')->label('Denumire')->required()->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('hours')->label('Ore')->numeric()
|
||||
Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('hours')->label(__('Ore'))->numeric()
|
||||
->visible(fn (Get $get) => $get('kind') === 'labor'),
|
||||
Forms\Components\TextInput::make('qty')->label('Cantitate')->numeric()->default(1)
|
||||
Forms\Components\TextInput::make('qty')->label(__('Cantitate'))->numeric()->default(1)
|
||||
->visible(fn (Get $get) => $get('kind') === 'part'),
|
||||
]);
|
||||
}
|
||||
@@ -65,13 +70,13 @@ class ItemsRelationManager extends RelationManager
|
||||
->recordTitleAttribute('name')
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('kind')
|
||||
->label('Tip')
|
||||
->label(__('Tip'))
|
||||
->formatStateUsing(fn ($s) => ServiceTemplateItem::KINDS[$s] ?? $s)
|
||||
->badge()
|
||||
->color(fn ($s) => $s === 'labor' ? 'info' : 'gray'),
|
||||
Tables\Columns\TextColumn::make('name')->wrap(),
|
||||
Tables\Columns\TextColumn::make('hours')->label('Ore')->placeholder('—')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('qty')->label('Cant.')->placeholder('—')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('hours')->label(__('Ore'))->placeholder('—')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('qty')->label(__('Cant.'))->placeholder('—')->alignRight(),
|
||||
])
|
||||
->headerActions([Actions\CreateAction::make()])
|
||||
->actions([Actions\EditAction::make(), Actions\DeleteAction::make()]);
|
||||
|
||||
@@ -32,24 +32,20 @@ class ShopCustomerResource extends Resource
|
||||
return __('nav.group.Magazin');
|
||||
}
|
||||
|
||||
protected static ?string $modelLabel = 'client magazin';
|
||||
|
||||
protected static ?string $pluralModelLabel = 'clienți magazin';
|
||||
|
||||
protected static ?int $navigationSort = 52;
|
||||
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make()->columns(2)->schema([
|
||||
Forms\Components\TextInput::make('name')->label('Nume')->required()->maxLength(160),
|
||||
Forms\Components\TextInput::make('phone')->label('Telefon')->required()->maxLength(40),
|
||||
Forms\Components\TextInput::make('email')->label('Email')->email()->maxLength(160),
|
||||
Forms\Components\TextInput::make('name')->label(__('Nume'))->required()->maxLength(160),
|
||||
Forms\Components\TextInput::make('phone')->label(__('Telefon'))->required()->maxLength(40),
|
||||
Forms\Components\TextInput::make('email')->label(__('Email'))->email()->maxLength(160),
|
||||
Forms\Components\Select::make('client_id')
|
||||
->label('Client legat (CRM)')
|
||||
->label(__('Client legat (CRM)'))
|
||||
->options(fn () => \App\Models\Tenant\Client::pluck('name', 'id'))
|
||||
->searchable()
|
||||
->helperText('Legătura cu fișa CRM (opțional). Auto-matched la înregistrare după telefon.'),
|
||||
->helperText(__('Legătura cu fișa CRM (opțional). Auto-matched la înregistrare după telefon.')),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -61,19 +57,19 @@ class ShopCustomerResource extends Resource
|
||||
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('phone')->copyable()->searchable(),
|
||||
Tables\Columns\TextColumn::make('email')->placeholder('—')->copyable()->toggleable(),
|
||||
Tables\Columns\TextColumn::make('client.name')->label('Client CRM')->placeholder('—')->toggleable(),
|
||||
Tables\Columns\TextColumn::make('orders_count')->counts('orders')->label('Comenzi')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('last_login_at')->label('Ultim login')->since()->placeholder('Niciodată'),
|
||||
Tables\Columns\TextColumn::make('created_at')->label('Înregistrat')->date('d.m.Y')->toggleable(),
|
||||
Tables\Columns\TextColumn::make('client.name')->label(__('Client CRM'))->placeholder('—')->toggleable(),
|
||||
Tables\Columns\TextColumn::make('orders_count')->counts('orders')->label(__('Comenzi'))->alignRight(),
|
||||
Tables\Columns\TextColumn::make('last_login_at')->label(__('Ultim login'))->since()->placeholder(__('Niciodată')),
|
||||
Tables\Columns\TextColumn::make('created_at')->label(__('Înregistrat'))->date('d.m.Y')->toggleable(),
|
||||
])
|
||||
->actions([
|
||||
Actions\Action::make('reset_password')
|
||||
->label('Trimite reset parolă')
|
||||
->label(__('Trimite reset parolă'))
|
||||
->icon('heroicon-m-key')
|
||||
->color('warning')
|
||||
->visible(fn (ShopCustomer $r) => ! empty($r->email))
|
||||
->requiresConfirmation()
|
||||
->modalDescription('Trimite emailul standard de resetare a parolei către clientul magazinului.')
|
||||
->modalDescription(__('Trimite emailul standard de resetare a parolei către clientul magazinului.'))
|
||||
->action(function (ShopCustomer $r) {
|
||||
$status = Password::broker('shop_customers')->sendResetLink(['email' => $r->email]);
|
||||
Notification::make()
|
||||
@@ -86,8 +82,8 @@ class ShopCustomerResource extends Resource
|
||||
Actions\EditAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
])
|
||||
->emptyStateHeading('Niciun client magazin')
|
||||
->emptyStateDescription('Aici apar clienții care și-au creat cont în magazinul online (/shop/register).')
|
||||
->emptyStateHeading(__('Niciun client magazin'))
|
||||
->emptyStateDescription(__('Aici apar clienții care și-au creat cont în magazinul online (/shop/register).'))
|
||||
->emptyStateIcon('heroicon-o-user-circle')
|
||||
->defaultSort('created_at', 'desc');
|
||||
}
|
||||
|
||||
+9
-4
@@ -11,15 +11,20 @@ class OrdersRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'orders';
|
||||
|
||||
protected static ?string $title = 'Comenzi';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
|
||||
{
|
||||
return __('Comenzi');
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->recordTitleAttribute('number')
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('number')->label('Nr.'),
|
||||
Tables\Columns\TextColumn::make('created_at')->label('Data')->dateTime('d.m.Y H:i'),
|
||||
Tables\Columns\TextColumn::make('number')->label(__('Nr.')),
|
||||
Tables\Columns\TextColumn::make('created_at')->label(__('Data'))->dateTime('d.m.Y H:i'),
|
||||
Tables\Columns\TextColumn::make('status')
|
||||
->formatStateUsing(fn ($s) => OnlineOrder::STATUSES[$s] ?? $s)
|
||||
->badge()
|
||||
@@ -33,6 +38,6 @@ class OrdersRelationManager extends RelationManager
|
||||
Tables\Columns\TextColumn::make('total')->money('MDL')->alignRight(),
|
||||
])
|
||||
->defaultSort('created_at', 'desc')
|
||||
->emptyStateHeading('Nicio comandă încă');
|
||||
->emptyStateHeading(__('Nicio comandă încă'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,6 @@ class SubcontractJobResource extends Resource
|
||||
return __('nav.group.Subcontractare');
|
||||
}
|
||||
|
||||
protected static ?string $modelLabel = 'lucrare terți';
|
||||
|
||||
protected static ?string $pluralModelLabel = 'lucrări terți';
|
||||
|
||||
protected static ?int $navigationSort = 71;
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
@@ -45,43 +41,43 @@ class SubcontractJobResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Lucrare')
|
||||
Schemas\Components\Section::make(__('Lucrare'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('number')->label('Nr.')->disabled()->dehydrated(false)->placeholder('Generat automat'),
|
||||
Forms\Components\TextInput::make('number')->label(__('Nr.'))->disabled()->dehydrated(false)->placeholder(__('Generat automat')),
|
||||
Forms\Components\Select::make('status')->options(SubcontractJob::STATUSES)->default('sent')->required(),
|
||||
Forms\Components\Select::make('subcontractor_id')
|
||||
->label('Subcontractor')
|
||||
->label(__('Subcontractor'))
|
||||
->options(fn () => Subcontractor::where('is_active', true)->pluck('name', 'id'))
|
||||
->searchable(),
|
||||
Forms\Components\Select::make('work_order_id')
|
||||
->label('Fișă asociată')
|
||||
->label(__('Fișă asociată'))
|
||||
->options(fn () => WorkOrder::whereNotIn('status', ['done', 'cancelled'])
|
||||
->get()->mapWithKeys(fn ($w) => [$w->id => "#{$w->number} · " . ($w->vehicle?->plate ?? '')])->toArray())
|
||||
->searchable(),
|
||||
Forms\Components\Select::make('category')
|
||||
->label('Categorie')
|
||||
->label(__('Categorie'))
|
||||
->options(array_combine(Subcontractor::SPECIALTIES, Subcontractor::SPECIALTIES))
|
||||
->searchable(),
|
||||
Forms\Components\Textarea::make('description')->label('Descriere')->rows(2)->columnSpanFull(),
|
||||
Forms\Components\Textarea::make('description')->label(__('Descriere'))->rows(2)->columnSpanFull(),
|
||||
]),
|
||||
Schemas\Components\Section::make('Cost & marjă')
|
||||
Schemas\Components\Section::make(__('Cost & marjă'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('cost')->label('Cost (de la terț)')->numeric()->default(0)->required(),
|
||||
Forms\Components\TextInput::make('markup_pct')->label('Markup %')->numeric()->default(0)
|
||||
->helperText('> 0 calculează automat prețul client.'),
|
||||
Forms\Components\TextInput::make('client_price')->label('Preț client')->numeric()->default(0)
|
||||
->helperText('Setat manual dacă markup = 0.'),
|
||||
Forms\Components\Toggle::make('paid_to_sub')->label('Plătit către terț'),
|
||||
Forms\Components\TextInput::make('cost')->label(__('Cost (de la terț)'))->numeric()->default(0)->required(),
|
||||
Forms\Components\TextInput::make('markup_pct')->label(__('Markup %'))->numeric()->default(0)
|
||||
->helperText(__('> 0 calculează automat prețul client.')),
|
||||
Forms\Components\TextInput::make('client_price')->label(__('Preț client'))->numeric()->default(0)
|
||||
->helperText(__('Setat manual dacă markup = 0.')),
|
||||
Forms\Components\Toggle::make('paid_to_sub')->label(__('Plătit către terț')),
|
||||
]),
|
||||
Schemas\Components\Section::make('Termene')
|
||||
Schemas\Components\Section::make(__('Termene'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
Forms\Components\DatePicker::make('sent_at')->label('Trimis')->default(today()),
|
||||
Forms\Components\DatePicker::make('eta')->label('ETA'),
|
||||
Forms\Components\DatePicker::make('returned_at')->label('Returnat'),
|
||||
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2),
|
||||
Forms\Components\DatePicker::make('sent_at')->label(__('Trimis'))->default(today()),
|
||||
Forms\Components\DatePicker::make('eta')->label(__('ETA')),
|
||||
Forms\Components\DatePicker::make('returned_at')->label(__('Returnat')),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -90,14 +86,14 @@ class SubcontractJobResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('number')->label('Nr.')->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('subcontractor.name')->label('Terț')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('number')->label(__('Nr.'))->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('subcontractor.name')->label(__('Terț'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('category')->badge()->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('workOrder.number')->label('Fișă')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('cost')->label('Cost')->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('client_price')->label('Preț client')->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('workOrder.number')->label(__('Fișă'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('cost')->label(__('Cost'))->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('client_price')->label(__('Preț client'))->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('margin')
|
||||
->label('Marjă')
|
||||
->label(__('Marjă'))
|
||||
->state(fn (SubcontractJob $r) => $r->margin())
|
||||
->money('MDL')
|
||||
->alignRight()
|
||||
@@ -110,20 +106,20 @@ class SubcontractJobResource extends Resource
|
||||
'success' => ['done', 'returned'],
|
||||
'danger' => ['cancelled'],
|
||||
]),
|
||||
Tables\Columns\IconColumn::make('paid_to_sub')->label('Plătit terț')->boolean()->toggleable(),
|
||||
Tables\Columns\IconColumn::make('paid_to_sub')->label(__('Plătit terț'))->boolean()->toggleable(),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('status')->options(SubcontractJob::STATUSES),
|
||||
Tables\Filters\SelectFilter::make('subcontractor_id')
|
||||
->label('Subcontractor')
|
||||
->label(__('Subcontractor'))
|
||||
->options(fn () => Subcontractor::pluck('name', 'id')),
|
||||
])
|
||||
->actions([
|
||||
Actions\EditAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
])
|
||||
->emptyStateHeading('Nicio lucrare la terți')
|
||||
->emptyStateDescription('Înregistrează lucrările trimise la ateliere externe (turbo, cutii, vopsitorie). Costul terțului + markup intră automat în totalul fișei asociate.')
|
||||
->emptyStateHeading(__('Nicio lucrare la terți'))
|
||||
->emptyStateDescription(__('Înregistrează lucrările trimise la ateliere externe (turbo, cutii, vopsitorie). Costul terțului + markup intră automat în totalul fișei asociate.'))
|
||||
->emptyStateIcon('heroicon-o-arrow-top-right-on-square')
|
||||
->defaultSort('created_at', 'desc');
|
||||
}
|
||||
|
||||
@@ -38,19 +38,19 @@ class SubcontractorResource extends Resource
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make()->columns(2)->schema([
|
||||
Forms\Components\TextInput::make('name')->label('Nume')->required()->maxLength(160),
|
||||
Forms\Components\TextInput::make('name')->label(__('Nume'))->required()->maxLength(160),
|
||||
Forms\Components\Select::make('specialty')
|
||||
->label('Specialitate')
|
||||
->label(__('Specialitate'))
|
||||
->options(array_combine(Subcontractor::SPECIALTIES, Subcontractor::SPECIALTIES))
|
||||
->searchable(),
|
||||
Forms\Components\TextInput::make('phone')->label('Telefon')->tel()->maxLength(40),
|
||||
Forms\Components\TextInput::make('phone')->label(__('Telefon'))->tel()->maxLength(40),
|
||||
Forms\Components\TextInput::make('email')->email()->maxLength(120),
|
||||
Forms\Components\Select::make('rating')
|
||||
->label('Rating')
|
||||
->label(__('Rating'))
|
||||
->options([1 => '★', 2 => '★★', 3 => '★★★', 4 => '★★★★', 5 => '★★★★★'])
|
||||
->default(3),
|
||||
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true),
|
||||
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2),
|
||||
Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -63,18 +63,18 @@ class SubcontractorResource extends Resource
|
||||
Tables\Columns\TextColumn::make('specialty')->badge()->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('phone')->copyable()->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('rating')->formatStateUsing(fn ($s) => str_repeat('★', (int) $s)),
|
||||
Tables\Columns\TextColumn::make('jobs_count')->counts('jobs')->label('Lucrări')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('jobs_count')->counts('jobs')->label(__('Lucrări'))->alignRight(),
|
||||
Tables\Columns\IconColumn::make('is_active')->boolean(),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\TernaryFilter::make('is_active')->label('Activi'),
|
||||
Tables\Filters\TernaryFilter::make('is_active')->label(__('Activi')),
|
||||
])
|
||||
->actions([
|
||||
Actions\EditAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
])
|
||||
->emptyStateHeading('Niciun subcontractor')
|
||||
->emptyStateDescription('Adaugă atelierele terțe la care trimiți lucrări (turbo, cutii, vopsitorie, PDR) și urmărește costul + marja.')
|
||||
->emptyStateHeading(__('Niciun subcontractor'))
|
||||
->emptyStateDescription(__('Adaugă atelierele terțe la care trimiți lucrări (turbo, cutii, vopsitorie, PDR) și urmărește costul + marja.'))
|
||||
->emptyStateIcon('heroicon-o-user-group')
|
||||
->defaultSort('name');
|
||||
}
|
||||
|
||||
@@ -37,30 +37,30 @@ class SupplierResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Identificare')
|
||||
Schemas\Components\Section::make(__('Identificare'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')->label('Nume')->required()->maxLength(160),
|
||||
Forms\Components\TextInput::make('contact_name')->label('Persoană contact')->maxLength(120),
|
||||
Forms\Components\TextInput::make('name')->label(__('Nume'))->required()->maxLength(160),
|
||||
Forms\Components\TextInput::make('contact_name')->label(__('Persoană contact'))->maxLength(120),
|
||||
Forms\Components\TextInput::make('phone')->tel()->maxLength(40),
|
||||
Forms\Components\TextInput::make('email')->email()->maxLength(120),
|
||||
Forms\Components\TextInput::make('website')->url()->maxLength(160),
|
||||
]),
|
||||
Schemas\Components\Section::make('Comercial')
|
||||
Schemas\Components\Section::make(__('Comercial'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('pay_terms')->label('Termeni plată')->placeholder('Net 30 / Avans')->maxLength(60),
|
||||
Forms\Components\TextInput::make('delivery_days')->label('Zile livrare')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('pay_terms')->label(__('Termeni plată'))->placeholder(__('Net 30 / Avans'))->maxLength(60),
|
||||
Forms\Components\TextInput::make('delivery_days')->label(__('Zile livrare'))->numeric()->default(0),
|
||||
Forms\Components\Select::make('rating')
|
||||
->label('Rating')
|
||||
->label(__('Rating'))
|
||||
->options([1 => '★', 2 => '★★', 3 => '★★★', 4 => '★★★★', 5 => '★★★★★'])
|
||||
->default(3)
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('discount_pct')->label('Discount %')->numeric()->default(0),
|
||||
Forms\Components\TagsInput::make('categories')->label('Categorii')->placeholder('Frâne, Ulei, ...')->columnSpan(2),
|
||||
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true),
|
||||
Forms\Components\TextInput::make('discount_pct')->label(__('Discount %'))->numeric()->default(0),
|
||||
Forms\Components\TagsInput::make('categories')->label(__('Categorii'))->placeholder(__('Frâne, Ulei, ...'))->columnSpan(2),
|
||||
Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
|
||||
]),
|
||||
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -69,41 +69,41 @@ class SupplierResource extends Resource
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('contact_name')->label('Contact')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('contact_name')->label(__('Contact'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('phone')->copyable()->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('rating')
|
||||
->label('Rating')
|
||||
->label(__('Rating'))
|
||||
->formatStateUsing(fn ($s) => str_repeat('★', (int) $s)),
|
||||
Tables\Columns\TextColumn::make('on_time_pct')
|
||||
->label('La timp 90d')
|
||||
->label(__('La timp 90d'))
|
||||
->state(fn (Supplier $r) => app(\App\Services\Warehouse\SupplierAnalytics::class)->onTimeRate($r))
|
||||
->formatStateUsing(fn ($s) => $s === null ? '—' : "{$s}%")
|
||||
->color(fn ($s) => $s === null ? 'gray' : ($s >= 90 ? 'success' : ($s >= 70 ? 'warning' : 'danger')))
|
||||
->alignRight()
|
||||
->toggleable(),
|
||||
Tables\Columns\TextColumn::make('avg_delivery_days')
|
||||
->label('Avg zile')
|
||||
->label(__('Avg zile'))
|
||||
->state(fn (Supplier $r) => app(\App\Services\Warehouse\SupplierAnalytics::class)->avgDeliveryDays($r))
|
||||
->formatStateUsing(fn ($s) => $s === null ? '—' : (string) $s)
|
||||
->alignRight()
|
||||
->toggleable(),
|
||||
Tables\Columns\TextColumn::make('spend_90d')
|
||||
->label('Cheltuit 90d')
|
||||
->label(__('Cheltuit 90d'))
|
||||
->state(fn (Supplier $r) => app(\App\Services\Warehouse\SupplierAnalytics::class)->spend($r))
|
||||
->money('MDL')
|
||||
->alignRight()
|
||||
->toggleable(),
|
||||
Tables\Columns\TextColumn::make('delivery_days')->label('Livrare (zile)')->alignRight()->toggleable(),
|
||||
Tables\Columns\TextColumn::make('discount_pct')->label('Discount')
|
||||
Tables\Columns\TextColumn::make('delivery_days')->label(__('Livrare (zile)'))->alignRight()->toggleable(),
|
||||
Tables\Columns\TextColumn::make('discount_pct')->label(__('Discount'))
|
||||
->formatStateUsing(fn ($s) => $s . '%')->alignRight()->toggleable(),
|
||||
Tables\Columns\IconColumn::make('is_active')->boolean(),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\TernaryFilter::make('is_active')->label('Activi'),
|
||||
Tables\Filters\TernaryFilter::make('is_active')->label(__('Activi')),
|
||||
])
|
||||
->actions([
|
||||
Actions\Action::make('rate')
|
||||
->label('Rerating')
|
||||
->label(__('Rerating'))
|
||||
->icon('heroicon-m-arrow-path')
|
||||
->color('gray')
|
||||
->action(function (Supplier $r) {
|
||||
@@ -111,8 +111,8 @@ class SupplierResource extends Resource
|
||||
->computedRating($r);
|
||||
if ($score === null) {
|
||||
\Filament\Notifications\Notification::make()
|
||||
->title('Date insuficiente')
|
||||
->body('Necesită cel puțin 2 recepții complete cu data așteptată setată.')
|
||||
->title(__('Date insuficiente'))
|
||||
->body(__('Necesită cel puțin 2 recepții complete cu data așteptată setată.'))
|
||||
->warning()
|
||||
->send();
|
||||
return;
|
||||
|
||||
@@ -32,10 +32,6 @@ class TireSetResource extends Resource
|
||||
return __('nav.group.Anvelope');
|
||||
}
|
||||
|
||||
protected static ?string $modelLabel = 'set anvelope';
|
||||
|
||||
protected static ?string $pluralModelLabel = 'seturi anvelope';
|
||||
|
||||
protected static ?int $navigationSort = 60;
|
||||
|
||||
public static function getNavigationBadge(): ?string
|
||||
@@ -52,53 +48,53 @@ class TireSetResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Proprietar')
|
||||
Schemas\Components\Section::make(__('Proprietar'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Select::make('client_id')
|
||||
->label('Client')
|
||||
->label(__('Client'))
|
||||
->options(fn () => Client::pluck('name', 'id'))
|
||||
->searchable()
|
||||
->live()
|
||||
->required(),
|
||||
Forms\Components\Select::make('vehicle_id')
|
||||
->label('Auto')
|
||||
->label(__('Auto'))
|
||||
->options(fn (Get $get) => $get('client_id')
|
||||
? Vehicle::where('client_id', $get('client_id'))->get()
|
||||
->mapWithKeys(fn ($v) => [$v->id => "{$v->make} {$v->model} {$v->plate}"])->toArray()
|
||||
: [])
|
||||
->searchable(),
|
||||
Forms\Components\TextInput::make('label')->label('Etichetă')->placeholder('ex: Iarnă Michelin'),
|
||||
Forms\Components\Select::make('season')->label('Sezon')->options(TireSet::SEASONS)->default('winter')->required(),
|
||||
Forms\Components\TextInput::make('label')->label(__('Etichetă'))->placeholder(__('ex: Iarnă Michelin')),
|
||||
Forms\Components\Select::make('season')->label(__('Sezon'))->options(TireSet::SEASONS)->default('winter')->required(),
|
||||
]),
|
||||
Schemas\Components\Section::make('Specificații')
|
||||
Schemas\Components\Section::make(__('Specificații'))
|
||||
->columns(3)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('width')->label('Lățime')->numeric()->placeholder('205'),
|
||||
Forms\Components\TextInput::make('profile')->label('Profil')->numeric()->placeholder('55'),
|
||||
Forms\Components\TextInput::make('diameter')->label('Diametru R')->numeric()->placeholder('16'),
|
||||
Forms\Components\TextInput::make('width')->label(__('Lățime'))->numeric()->placeholder('205'),
|
||||
Forms\Components\TextInput::make('profile')->label(__('Profil'))->numeric()->placeholder('55'),
|
||||
Forms\Components\TextInput::make('diameter')->label(__('Diametru R'))->numeric()->placeholder('16'),
|
||||
Forms\Components\TextInput::make('brand')->maxLength(64),
|
||||
Forms\Components\TextInput::make('model')->maxLength(64),
|
||||
Forms\Components\TextInput::make('dot_year')->label('DOT')->maxLength(8)->placeholder('3621'),
|
||||
Forms\Components\Toggle::make('has_rims')->label('Cu jante'),
|
||||
Forms\Components\Select::make('rim_type')->label('Tip jante')->options(['steel' => 'Tablă', 'alloy' => 'Aliaj']),
|
||||
Forms\Components\Select::make('condition')->label('Stare')->options(TireSet::CONDITIONS),
|
||||
Forms\Components\TextInput::make('dot_year')->label(__('DOT'))->maxLength(8)->placeholder('3621'),
|
||||
Forms\Components\Toggle::make('has_rims')->label(__('Cu jante')),
|
||||
Forms\Components\Select::make('rim_type')->label(__('Tip jante'))->options(['steel' => 'Tablă', 'alloy' => 'Aliaj']),
|
||||
Forms\Components\Select::make('condition')->label(__('Stare'))->options(TireSet::CONDITIONS),
|
||||
]),
|
||||
Schemas\Components\Section::make('Uzură (mm) per poziție')
|
||||
Schemas\Components\Section::make(__('Uzură (mm) per poziție'))
|
||||
->columns(4)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('tread.fl')->label('Față-Stânga')->numeric(),
|
||||
Forms\Components\TextInput::make('tread.fr')->label('Față-Dreapta')->numeric(),
|
||||
Forms\Components\TextInput::make('tread.rl')->label('Spate-Stânga')->numeric(),
|
||||
Forms\Components\TextInput::make('tread.rr')->label('Spate-Dreapta')->numeric(),
|
||||
Forms\Components\TextInput::make('tread.fl')->label(__('Față-Stânga'))->numeric(),
|
||||
Forms\Components\TextInput::make('tread.fr')->label(__('Față-Dreapta'))->numeric(),
|
||||
Forms\Components\TextInput::make('tread.rl')->label(__('Spate-Stânga'))->numeric(),
|
||||
Forms\Components\TextInput::make('tread.rr')->label(__('Spate-Dreapta'))->numeric(),
|
||||
]),
|
||||
Schemas\Components\Section::make('TPMS & foto')
|
||||
Schemas\Components\Section::make(__('TPMS & foto'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Toggle::make('tpms')->label('Senzori TPMS'),
|
||||
Forms\Components\TextInput::make('notes')->label('Observații'),
|
||||
Forms\Components\Toggle::make('tpms')->label(__('Senzori TPMS')),
|
||||
Forms\Components\TextInput::make('notes')->label(__('Observații')),
|
||||
\Filament\Forms\Components\SpatieMediaLibraryFileUpload::make('photos')
|
||||
->label('Fotografii')
|
||||
->label(__('Fotografii'))
|
||||
->collection('photos')
|
||||
->multiple()
|
||||
->image()
|
||||
@@ -112,23 +108,23 @@ class TireSetResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('client.name')->label('Client')->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('label')->label('Etichetă')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('client.name')->label(__('Client'))->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('label')->label(__('Etichetă'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('size')
|
||||
->label('Dimensiune')
|
||||
->label(__('Dimensiune'))
|
||||
->state(fn (TireSet $r) => $r->sizeLabel()),
|
||||
Tables\Columns\TextColumn::make('season')
|
||||
->label('Sezon')
|
||||
->label(__('Sezon'))
|
||||
->formatStateUsing(fn ($s) => TireSet::SEASONS[$s] ?? $s)
|
||||
->badge()
|
||||
->colors(['warning' => ['summer'], 'info' => ['winter'], 'gray' => ['allseason']]),
|
||||
Tables\Columns\TextColumn::make('tread_min')->label('Uzură min')
|
||||
Tables\Columns\TextColumn::make('tread_min')->label(__('Uzură min'))
|
||||
->formatStateUsing(fn ($s) => $s ? $s . ' mm' : '—')
|
||||
->color(fn ($s) => $s !== null && (float) $s < 3 ? 'danger' : null)
|
||||
->alignRight(),
|
||||
Tables\Columns\IconColumn::make('tpms')->label('TPMS')->boolean()->toggleable(),
|
||||
Tables\Columns\IconColumn::make('tpms')->label(__('TPMS'))->boolean()->toggleable(),
|
||||
Tables\Columns\TextColumn::make('storage_status')
|
||||
->label('Depozit')
|
||||
->label(__('Depozit'))
|
||||
->state(fn (TireSet $r) => $r->isStored() ? ($r->currentStorage()?->location ?? 'da') : '—')
|
||||
->badge()
|
||||
->color(fn ($state) => $state === '—' ? 'gray' : 'success'),
|
||||
@@ -136,19 +132,19 @@ class TireSetResource extends Resource
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('season')->options(TireSet::SEASONS),
|
||||
Tables\Filters\Filter::make('stored')
|
||||
->label('În depozit')
|
||||
->label(__('În depozit'))
|
||||
->query(fn ($q) => $q->whereHas('storage', fn ($s) => $s->where('status', 'stored'))),
|
||||
])
|
||||
->actions([
|
||||
Actions\Action::make('check_in')
|
||||
->label('Check-in depozit')
|
||||
->label(__('Check-in depozit'))
|
||||
->icon('heroicon-m-arrow-down-on-square')
|
||||
->color('success')
|
||||
->visible(fn (TireSet $r) => ! $r->isStored())
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('location')->label('Locație (raft)')->required()->placeholder('A1-03'),
|
||||
Forms\Components\TextInput::make('season_label')->label('Perioadă')->placeholder('Iarnă 2025-2026'),
|
||||
Forms\Components\TextInput::make('fee')->label('Taxă depozitare')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('location')->label(__('Locație (raft)'))->required()->placeholder(__('A1-03')),
|
||||
Forms\Components\TextInput::make('season_label')->label(__('Perioadă'))->placeholder(__('Iarnă 2025-2026')),
|
||||
Forms\Components\TextInput::make('fee')->label(__('Taxă depozitare'))->numeric()->default(0),
|
||||
])
|
||||
->action(function (TireSet $r, array $data) {
|
||||
\App\Models\Tenant\TireStorage::create([
|
||||
@@ -159,27 +155,27 @@ class TireSetResource extends Resource
|
||||
'status' => 'stored',
|
||||
'checked_in_at' => now(),
|
||||
]);
|
||||
\Filament\Notifications\Notification::make()->title('Set primit în depozit')->success()->send();
|
||||
\Filament\Notifications\Notification::make()->title(__('Set primit în depozit'))->success()->send();
|
||||
}),
|
||||
Actions\Action::make('check_out')
|
||||
->label('Eliberează')
|
||||
->label(__('Eliberează'))
|
||||
->icon('heroicon-m-arrow-up-on-square')
|
||||
->color('warning')
|
||||
->visible(fn (TireSet $r) => $r->isStored())
|
||||
->requiresConfirmation()
|
||||
->modalDescription('Marchează setul ca ridicat de client.')
|
||||
->modalDescription(__('Marchează setul ca ridicat de client.'))
|
||||
->action(function (TireSet $r) {
|
||||
$storage = $r->currentStorage();
|
||||
if ($storage) {
|
||||
$storage->update(['status' => 'retrieved', 'checked_out_at' => now()]);
|
||||
}
|
||||
\Filament\Notifications\Notification::make()->title('Set eliberat din depozit')->success()->send();
|
||||
\Filament\Notifications\Notification::make()->title(__('Set eliberat din depozit'))->success()->send();
|
||||
}),
|
||||
Actions\EditAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
])
|
||||
->emptyStateHeading('Niciun set de anvelope')
|
||||
->emptyStateDescription('Înregistrează seturile de anvelope ale clienților și gestionează depozitarea sezonieră (tire hotel). Urmărește uzura, TPMS și locația în depozit.')
|
||||
->emptyStateHeading(__('Niciun set de anvelope'))
|
||||
->emptyStateDescription(__('Înregistrează seturile de anvelope ale clienților și gestionează depozitarea sezonieră (tire hotel). Urmărește uzura, TPMS și locația în depozit.'))
|
||||
->emptyStateIcon('heroicon-o-lifebuoy')
|
||||
->defaultSort('created_at', 'desc');
|
||||
}
|
||||
|
||||
+13
-8
@@ -11,25 +11,30 @@ class StorageRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'storage';
|
||||
|
||||
protected static ?string $title = 'Istoric depozitare';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
|
||||
{
|
||||
return __('Istoric depozitare');
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('season_label')->label('Perioadă')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('location')->label('Locație')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('checked_in_at')->label('Primit')->dateTime('d.m.Y'),
|
||||
Tables\Columns\TextColumn::make('checked_out_at')->label('Ridicat')->dateTime('d.m.Y')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('season_label')->label(__('Perioadă'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('location')->label(__('Locație'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('checked_in_at')->label(__('Primit'))->dateTime('d.m.Y'),
|
||||
Tables\Columns\TextColumn::make('checked_out_at')->label(__('Ridicat'))->dateTime('d.m.Y')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('status')
|
||||
->formatStateUsing(fn ($s) => TireStorage::STATUSES[$s] ?? $s)
|
||||
->badge()
|
||||
->colors(['success' => ['stored'], 'gray' => ['retrieved']]),
|
||||
Tables\Columns\TextColumn::make('fee')->money('MDL')->alignRight(),
|
||||
Tables\Columns\IconColumn::make('paid')->label('Plătit')->boolean(),
|
||||
Tables\Columns\IconColumn::make('paid')->label(__('Plătit'))->boolean(),
|
||||
])
|
||||
->defaultSort('checked_in_at', 'desc')
|
||||
->emptyStateHeading('Niciun istoric')
|
||||
->emptyStateDescription('Folosește „Check-in depozit" pe set pentru a înregistra prima depozitare.');
|
||||
->emptyStateHeading(__('Niciun istoric'))
|
||||
->emptyStateDescription(__('Folosește „Check-in depozit" pe set pentru a înregistra prima depozitare.'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,39 +54,39 @@ class UserResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Identitate')
|
||||
Schemas\Components\Section::make(__('Identitate'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('name')->label('Nume')->required()->maxLength(120),
|
||||
Forms\Components\TextInput::make('name')->label(__('Nume'))->required()->maxLength(120),
|
||||
Forms\Components\TextInput::make('email')->email()->required()->maxLength(120),
|
||||
Forms\Components\TextInput::make('phone')->tel()->maxLength(40),
|
||||
Forms\Components\Select::make('locale')
|
||||
->options(['ro' => 'Română', 'ru' => 'Русский', 'en' => 'English'])
|
||||
->default('ro'),
|
||||
]),
|
||||
Schemas\Components\Section::make('Acces')
|
||||
Schemas\Components\Section::make(__('Acces'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Select::make('role')
|
||||
->label('Rol primar')
|
||||
->label(__('Rol primar'))
|
||||
->options(\App\Auth\Permissions::roleLabels())
|
||||
->required()
|
||||
->default('mechanic')
|
||||
->helperText('Rolul principal — sincronizat automat cu drepturile RBAC.'),
|
||||
->helperText(__('Rolul principal — sincronizat automat cu drepturile RBAC.')),
|
||||
Forms\Components\Select::make('status')
|
||||
->options(['active' => 'Activ', 'inactive' => 'Inactiv', 'blocked' => 'Blocat'])
|
||||
->default('active')
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('password')
|
||||
->label('Parolă')
|
||||
->label(__('Parolă'))
|
||||
->password()
|
||||
->required(fn (string $context) => $context === 'create')
|
||||
->dehydrated(fn ($state) => filled($state))
|
||||
->dehydrateStateUsing(fn ($state) => Hash::make($state))
|
||||
->minLength(6)
|
||||
->helperText('La editare lasă gol pentru a păstra parola actuală.'),
|
||||
->helperText(__('La editare lasă gol pentru a păstra parola actuală.')),
|
||||
Forms\Components\Select::make('roles_picked')
|
||||
->label('Roluri suplimentare')
|
||||
->label(__('Roluri suplimentare'))
|
||||
->multiple()
|
||||
->options(\App\Auth\Permissions::roleLabels())
|
||||
->afterStateHydrated(function ($component, $record) {
|
||||
@@ -94,35 +94,35 @@ class UserResource extends Resource
|
||||
})
|
||||
->dehydrated(false)
|
||||
->columnSpanFull()
|
||||
->helperText('Roluri suplimentare peste rolul primar — drepturile se cumulează.'),
|
||||
->helperText(__('Roluri suplimentare peste rolul primar — drepturile se cumulează.')),
|
||||
]),
|
||||
Schemas\Components\Section::make('Salariu & marjă')
|
||||
->description('Configurează procentele pentru calcul salariu. Marja internă (nu TVA) se scade din prețul de manoperă pentru a determina baza salariului.')
|
||||
Schemas\Components\Section::make(__('Salariu & marjă'))
|
||||
->description(__('Configurează procentele pentru calcul salariu. Marja internă (nu TVA) se scade din prețul de manoperă pentru a determina baza salariului.'))
|
||||
->columns(2)
|
||||
->visible(fn () => auth()->user()?->canDo(\App\Auth\Permissions::FINANCE_VIEW_INTERNAL_MARGIN) ?? false)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('hourly_rate')
|
||||
->label('Tarif orar (MDL)')
|
||||
->label(__('Tarif orar (MDL)'))
|
||||
->numeric()
|
||||
->step(0.01)
|
||||
->placeholder('Ex: 100'),
|
||||
->placeholder(__('Ex: 100')),
|
||||
Forms\Components\TextInput::make('internal_margin_pct')
|
||||
->label('Marjă internă (%)')
|
||||
->label(__('Marjă internă (%)'))
|
||||
->numeric()
|
||||
->step(0.01)
|
||||
->minValue(0)
|
||||
->maxValue(90)
|
||||
->placeholder('Ex: 20 pentru +20%')
|
||||
->helperText('Doar la manopere (proprii + subcontract). Baza salariu = preț client × (1 − marjă/100). Lasă gol pentru a folosi valoarea implicită a companiei.'),
|
||||
->placeholder(__('Ex: 20 pentru +20%'))
|
||||
->helperText(__('Doar la manopere (proprii + subcontract). Baza salariu = preț client × (1 − marjă/100). Lasă gol pentru a folosi valoarea implicită a companiei.')),
|
||||
]),
|
||||
Schemas\Components\Section::make('Securitate')
|
||||
Schemas\Components\Section::make(__('Securitate'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Placeholder::make('mfa_status')
|
||||
->label('Autentificare 2FA')
|
||||
->label(__('Autentificare 2FA'))
|
||||
->content(fn ($record) => $record && $record->hasTwoFactorEnabled() ? '✓ Activat (TOTP)' : '✗ Dezactivat'),
|
||||
Forms\Components\Placeholder::make('last_login')
|
||||
->label('Ultima autentificare')
|
||||
->label(__('Ultima autentificare'))
|
||||
->content(fn ($record) => $record?->last_login_at?->diffForHumans() ?? '—'),
|
||||
]),
|
||||
]);
|
||||
@@ -139,7 +139,7 @@ class UserResource extends Resource
|
||||
->formatStateUsing(fn ($state) => \App\Auth\Permissions::roleLabels()[$state] ?? $state)
|
||||
->badge(),
|
||||
Tables\Columns\IconColumn::make('app_authentication_secret')
|
||||
->label('2FA')
|
||||
->label(__('2FA'))
|
||||
->boolean()
|
||||
->getStateUsing(fn ($record) => $record->hasTwoFactorEnabled())
|
||||
->trueIcon('heroicon-o-shield-check')
|
||||
@@ -147,14 +147,14 @@ class UserResource extends Resource
|
||||
->falseIcon('heroicon-o-shield-exclamation')
|
||||
->falseColor('warning'),
|
||||
Tables\Columns\TextColumn::make('active_sessions')
|
||||
->label('Sesiuni')
|
||||
->label(__('Sesiuni'))
|
||||
->getStateUsing(fn ($record) => \Illuminate\Support\Facades\DB::table('sessions')->where('user_id', $record->id)->count())
|
||||
->badge()
|
||||
->color(fn ($state) => $state > 0 ? 'success' : 'gray')
|
||||
->toggleable(),
|
||||
Tables\Columns\TextColumn::make('permission_overrides_count')
|
||||
->counts('permissionOverrides')
|
||||
->label('Excepții')
|
||||
->label(__('Excepții'))
|
||||
->badge()
|
||||
->color('warning')
|
||||
->toggleable(isToggledHiddenByDefault: true),
|
||||
@@ -180,7 +180,7 @@ class UserResource extends Resource
|
||||
->actions([
|
||||
Actions\EditAction::make(),
|
||||
Actions\Action::make('force_logout')
|
||||
->label('Force logout')
|
||||
->label(__('Force logout'))
|
||||
->icon('heroicon-o-arrow-right-on-rectangle')
|
||||
->color('warning')
|
||||
->visible(fn ($record) => \Illuminate\Support\Facades\DB::table('sessions')->where('user_id', $record->id)->exists())
|
||||
@@ -191,16 +191,16 @@ class UserResource extends Resource
|
||||
\Filament\Notifications\Notification::make()->title("$n sesiuni revoke-uite")->success()->send();
|
||||
}),
|
||||
Actions\Action::make('reset_2fa')
|
||||
->label('Resetează 2FA')
|
||||
->label(__('Resetează 2FA'))
|
||||
->icon('heroicon-o-shield-exclamation')
|
||||
->color('warning')
|
||||
->visible(fn ($record) => $record && $record->hasTwoFactorEnabled())
|
||||
->requiresConfirmation()
|
||||
->modalDescription('Dezactivează 2FA pentru acest utilizator. Va trebui să re-configureze TOTP la următoarea autentificare.')
|
||||
->modalDescription(__('Dezactivează 2FA pentru acest utilizator. Va trebui să re-configureze TOTP la următoarea autentificare.'))
|
||||
->action(function ($record) {
|
||||
$record->saveAppAuthenticationSecret(null);
|
||||
$record->saveAppAuthenticationRecoveryCodes(null);
|
||||
\Filament\Notifications\Notification::make()->title('2FA resetat')->success()->send();
|
||||
\Filament\Notifications\Notification::make()->title(__('2FA resetat'))->success()->send();
|
||||
}),
|
||||
Actions\DeleteAction::make(),
|
||||
])
|
||||
|
||||
+15
-10
@@ -16,18 +16,23 @@ class PermissionOverridesRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'permissionOverrides';
|
||||
|
||||
protected static ?string $title = 'Excepții drepturi';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
|
||||
{
|
||||
return __('Excepții drepturi');
|
||||
}
|
||||
|
||||
protected static string|\BackedEnum|null $icon = 'heroicon-o-shield-exclamation';
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Excepție')
|
||||
Schemas\Components\Section::make(__('Excepție'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Select::make('permission_id')
|
||||
->label('Drept')
|
||||
->label(__('Drept'))
|
||||
->required()
|
||||
->searchable()
|
||||
->options(fn () => Permission::orderBy('name')->pluck('name', 'id'))
|
||||
@@ -37,12 +42,12 @@ class PermissionOverridesRelationManager extends RelationManager
|
||||
->options(['grant' => 'GRANT — adaugă dreptul', 'deny' => 'DENY — interzice dreptul'])
|
||||
->default('grant'),
|
||||
Forms\Components\DatePicker::make('expires_at')
|
||||
->label('Expiră la (opțional)')
|
||||
->label(__('Expiră la (opțional)'))
|
||||
->minDate(now()),
|
||||
Forms\Components\Textarea::make('reason')
|
||||
->label('Motiv')
|
||||
->label(__('Motiv'))
|
||||
->columnSpanFull()
|
||||
->placeholder('Ex: lockdown temporar; acces pentru audit; etc.')
|
||||
->placeholder(__('Ex: lockdown temporar; acces pentru audit; etc.'))
|
||||
->rows(2),
|
||||
]),
|
||||
]);
|
||||
@@ -54,7 +59,7 @@ class PermissionOverridesRelationManager extends RelationManager
|
||||
->recordTitleAttribute('mode')
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('permission.name')
|
||||
->label('Drept')
|
||||
->label(__('Drept'))
|
||||
->searchable()
|
||||
->sortable(),
|
||||
Tables\Columns\TextColumn::make('mode')
|
||||
@@ -65,12 +70,12 @@ class PermissionOverridesRelationManager extends RelationManager
|
||||
->limit(40)
|
||||
->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('expires_at')
|
||||
->label('Expiră')
|
||||
->label(__('Expiră'))
|
||||
->date()
|
||||
->placeholder('niciodată')
|
||||
->placeholder(__('niciodată'))
|
||||
->color(fn ($record) => $record?->isExpired() ? 'danger' : 'gray'),
|
||||
Tables\Columns\TextColumn::make('grantedBy.name')
|
||||
->label('Acordat de')
|
||||
->label(__('Acordat de'))
|
||||
->placeholder('—')
|
||||
->toggleable(),
|
||||
])
|
||||
|
||||
@@ -24,9 +24,19 @@ class VehicleResource extends Resource
|
||||
return __('nav.label.Automobile');
|
||||
}
|
||||
|
||||
protected static ?string $modelLabel = 'mașină';
|
||||
protected static ?string $modelLabel = null;
|
||||
|
||||
protected static ?string $pluralModelLabel = 'mașini';
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('mașină');
|
||||
}
|
||||
|
||||
protected static ?string $pluralModelLabel = null;
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('mașini');
|
||||
}
|
||||
|
||||
protected static ?int $navigationSort = 20;
|
||||
|
||||
@@ -51,21 +61,21 @@ class VehicleResource extends Resource
|
||||
public static function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make('Identificare')
|
||||
Schemas\Components\Section::make(__('Identificare'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\Select::make('client_id')
|
||||
->label('Proprietar')
|
||||
->label(__('Proprietar'))
|
||||
->options(fn () => Client::pluck('name', 'id'))
|
||||
->searchable()
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('plate')->label('Nr. înmatriculare')->maxLength(16),
|
||||
Forms\Components\TextInput::make('make')->label('Marca')->required()->maxLength(60),
|
||||
Forms\Components\TextInput::make('plate')->label(__('Nr. înmatriculare'))->maxLength(16),
|
||||
Forms\Components\TextInput::make('make')->label(__('Marca'))->required()->maxLength(60),
|
||||
Forms\Components\TextInput::make('model')->required()->maxLength(60),
|
||||
Forms\Components\TextInput::make('year')->numeric()->minValue(1950)->maxValue(2100),
|
||||
Forms\Components\TextInput::make('vin')->maxLength(32),
|
||||
]),
|
||||
Schemas\Components\Section::make('Tehnice')
|
||||
Schemas\Components\Section::make(__('Tehnice'))
|
||||
->columns(2)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('engine')->maxLength(60),
|
||||
@@ -76,13 +86,13 @@ class VehicleResource extends Resource
|
||||
'EV' => 'Electric', 'GPL' => 'GPL', 'GNC' => 'GNC',
|
||||
]),
|
||||
Forms\Components\Select::make('vehicle_class')
|
||||
->label('Clasă (pentru pricing)')
|
||||
->label(__('Clasă (pentru pricing)'))
|
||||
->options(\App\Models\Tenant\PricingCoefficient::VEHICLE_CLASSES)
|
||||
->helperText('Folosită de coeficienții de preț. Hibrid/EV se deduc și din combustibil.'),
|
||||
Forms\Components\TextInput::make('mileage')->label('Kilometraj')->numeric()->default(0),
|
||||
->helperText(__('Folosită de coeficienții de preț. Hibrid/EV se deduc și din combustibil.')),
|
||||
Forms\Components\TextInput::make('mileage')->label(__('Kilometraj'))->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('color')->maxLength(40),
|
||||
]),
|
||||
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(3),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(3),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -90,18 +100,18 @@ class VehicleResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('plate')->label('Nr.')->searchable(),
|
||||
Tables\Columns\TextColumn::make('plate')->label(__('Nr.'))->searchable(),
|
||||
Tables\Columns\TextColumn::make('make')->sortable(),
|
||||
Tables\Columns\TextColumn::make('model'),
|
||||
Tables\Columns\TextColumn::make('year'),
|
||||
Tables\Columns\TextColumn::make('client.name')->label('Proprietar')->searchable(),
|
||||
Tables\Columns\TextColumn::make('client.name')->label(__('Proprietar'))->searchable(),
|
||||
Tables\Columns\TextColumn::make('vin')->toggleable(isToggledHiddenByDefault: true),
|
||||
Tables\Columns\TextColumn::make('mileage')->label('Km')->numeric(),
|
||||
Tables\Columns\TextColumn::make('mileage')->label(__('Km'))->numeric(),
|
||||
Tables\Columns\TextColumn::make('created_at')->date()->sortable(),
|
||||
])
|
||||
->actions([
|
||||
Actions\Action::make('decode_vin')
|
||||
->label('Decode VIN')
|
||||
->label(__('Decode VIN'))
|
||||
->icon('heroicon-m-cpu-chip')
|
||||
->color('gray')
|
||||
->visible(fn (\App\Models\Tenant\Vehicle $r) => ! empty($r->vin) && strlen($r->vin) === 17)
|
||||
@@ -113,11 +123,11 @@ class VehicleResource extends Resource
|
||||
return view('filament.tenant.vin-decode', ['info' => $info, 'vehicle' => $r]);
|
||||
}),
|
||||
Actions\Action::make('ai_recommend')
|
||||
->label('AI: recomandări')
|
||||
->label(__('AI: recomandări'))
|
||||
->icon('heroicon-m-sparkles')
|
||||
->color('primary')
|
||||
->visible(fn (\App\Models\Tenant\Vehicle $r) => ! empty($r->vin))
|
||||
->modalHeading('Recomandări AI')
|
||||
->modalHeading(__('Recomandări AI'))
|
||||
->modalSubmitAction(false)
|
||||
->modalCancelActionLabel('Închide')
|
||||
->modalContent(function (\App\Models\Tenant\Vehicle $r) {
|
||||
@@ -128,8 +138,8 @@ class VehicleResource extends Resource
|
||||
Actions\EditAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
])
|
||||
->emptyStateHeading('Nicio mașină încă')
|
||||
->emptyStateDescription('Adaugă mașini manual sau importă din CSV. Folosește VIN-căutare pentru decoder rapid și completare automată brand/model/an.')
|
||||
->emptyStateHeading(__('Nicio mașină încă'))
|
||||
->emptyStateDescription(__('Adaugă mașini manual sau importă din CSV. Folosește VIN-căutare pentru decoder rapid și completare automată brand/model/an.'))
|
||||
->emptyStateIcon('heroicon-o-truck')
|
||||
->defaultSort('created_at', 'desc');
|
||||
}
|
||||
|
||||
@@ -17,16 +17,16 @@ class ListVehicles extends ListRecords
|
||||
{
|
||||
return [
|
||||
Actions\Action::make('export')
|
||||
->label('Export CSV')
|
||||
->label(__('Export CSV'))
|
||||
->icon('heroicon-m-arrow-down-tray')
|
||||
->color('gray')
|
||||
->action(fn () => app(CsvImportExport::class)->exportVehicles()),
|
||||
Actions\Action::make('import')
|
||||
->label('Import CSV')
|
||||
->label(__('Import CSV'))
|
||||
->icon('heroicon-m-arrow-up-tray')
|
||||
->color('gray')
|
||||
->modalHeading('Import mașini din CSV')
|
||||
->modalDescription('CSV cu header: ' . implode(', ', CsvImportExport::VEHICLE_COLUMNS) . '. Coloana client_phone trebuie să existe deja la clienți.')
|
||||
->modalHeading(__('Import mașini din CSV'))
|
||||
->modalDescription(__('CSV cu header: ' . implode(', ', CsvImportExport::VEHICLE_COLUMNS) . '. Coloana client_phone trebuie să existe deja la clienți.'))
|
||||
->schema([
|
||||
Forms\Components\FileUpload::make('file')
|
||||
->required()
|
||||
|
||||
@@ -38,11 +38,11 @@ class WarehouseResource extends Resource
|
||||
{
|
||||
return $schema->components([
|
||||
Schemas\Components\Section::make()->columns(2)->schema([
|
||||
Forms\Components\TextInput::make('code')->label('Cod')->required()->maxLength(32),
|
||||
Forms\Components\TextInput::make('name')->label('Denumire')->required()->maxLength(120),
|
||||
Forms\Components\TextInput::make('address')->label('Adresă')->columnSpanFull()->maxLength(200),
|
||||
Forms\Components\Toggle::make('is_default')->label('Depozit implicit'),
|
||||
Forms\Components\Toggle::make('is_active')->label('Activ')->default(true),
|
||||
Forms\Components\TextInput::make('code')->label(__('Cod'))->required()->maxLength(32),
|
||||
Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()->maxLength(120),
|
||||
Forms\Components\TextInput::make('address')->label(__('Adresă'))->columnSpanFull()->maxLength(200),
|
||||
Forms\Components\Toggle::make('is_default')->label(__('Depozit implicit')),
|
||||
Forms\Components\Toggle::make('is_active')->label(__('Activ'))->default(true),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -54,19 +54,19 @@ class WarehouseResource extends Resource
|
||||
Tables\Columns\TextColumn::make('code')->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('name')->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('address')->placeholder('—')->toggleable(),
|
||||
Tables\Columns\IconColumn::make('is_default')->label('Implicit')->boolean(),
|
||||
Tables\Columns\IconColumn::make('is_active')->label('Activ')->boolean(),
|
||||
Tables\Columns\IconColumn::make('is_default')->label(__('Implicit'))->boolean(),
|
||||
Tables\Columns\IconColumn::make('is_active')->label(__('Activ'))->boolean(),
|
||||
Tables\Columns\TextColumn::make('batches_count')
|
||||
->counts('batches')
|
||||
->label('Loturi')
|
||||
->label(__('Loturi'))
|
||||
->alignRight(),
|
||||
])
|
||||
->actions([
|
||||
Actions\EditAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
])
|
||||
->emptyStateHeading('Niciun depozit')
|
||||
->emptyStateDescription('Un depozit implicit a fost creat la migrare. Adaugă altele dacă ai locații fizice separate (sucursală, hală, mobil).')
|
||||
->emptyStateHeading(__('Niciun depozit'))
|
||||
->emptyStateDescription(__('Un depozit implicit a fost creat la migrare. Adaugă altele dacă ai locații fizice separate (sucursală, hală, mobil).'))
|
||||
->emptyStateIcon('heroicon-o-building-storefront')
|
||||
->defaultSort('code');
|
||||
}
|
||||
|
||||
@@ -34,9 +34,19 @@ class WorkOrderResource extends Resource
|
||||
return __('nav.group.Service');
|
||||
}
|
||||
|
||||
protected static ?string $modelLabel = 'fișă';
|
||||
protected static ?string $modelLabel = null;
|
||||
|
||||
protected static ?string $pluralModelLabel = 'fișe lucru';
|
||||
public static function getModelLabel(): string
|
||||
{
|
||||
return __('fișă');
|
||||
}
|
||||
|
||||
protected static ?string $pluralModelLabel = null;
|
||||
|
||||
public static function getPluralModelLabel(): string
|
||||
{
|
||||
return __('fișe lucru');
|
||||
}
|
||||
|
||||
protected static ?int $navigationSort = 30;
|
||||
|
||||
@@ -63,76 +73,76 @@ class WorkOrderResource extends Resource
|
||||
{
|
||||
return $schema->components([
|
||||
// Antet compact — o singură secțiune densă cu doar esențialul, mereu deschisă.
|
||||
Schemas\Components\Section::make('Antet fișă')
|
||||
->description('Informația de bază — click pe titlul secțiunilor de mai jos pentru detalii.')
|
||||
Schemas\Components\Section::make(__('Antet fișă'))
|
||||
->description(__('Informația de bază — click pe titlul secțiunilor de mai jos pentru detalii.'))
|
||||
->compact()
|
||||
->columns(4)
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('number')->label('Nr.')->disabled()->dehydrated(false)->placeholder('auto'),
|
||||
Forms\Components\DatePicker::make('opened_at')->label('Deschis')->default(today())->required(),
|
||||
Forms\Components\TextInput::make('number')->label(__('Nr.'))->disabled()->dehydrated(false)->placeholder('auto'),
|
||||
Forms\Components\DatePicker::make('opened_at')->label(__('Deschis'))->default(today())->required(),
|
||||
Forms\Components\Select::make('status')->options(WorkOrder::STATUSES)->default('new')->required(),
|
||||
Forms\Components\Select::make('urgency')->label('Urgență')
|
||||
Forms\Components\Select::make('urgency')->label(__('Urgență'))
|
||||
->options(\App\Models\Tenant\PricingCoefficient::URGENCY)->default('normal')->required(),
|
||||
Forms\Components\Select::make('client_id')->label('Client')
|
||||
Forms\Components\Select::make('client_id')->label(__('Client'))
|
||||
->options(fn () => Client::pluck('name', 'id'))
|
||||
->searchable()->live()->required()->columnSpan(2),
|
||||
Forms\Components\Select::make('vehicle_id')->label('Auto')
|
||||
Forms\Components\Select::make('vehicle_id')->label(__('Auto'))
|
||||
->options(fn (Get $get) => $get('client_id')
|
||||
? Vehicle::where('client_id', $get('client_id'))->get()
|
||||
->mapWithKeys(fn ($v) => [$v->id => "{$v->make} {$v->model} {$v->plate}"])->toArray()
|
||||
: [])
|
||||
->searchable()->columnSpan(2),
|
||||
Forms\Components\Select::make('master_id')->label('Maistru')
|
||||
Forms\Components\Select::make('master_id')->label(__('Maistru'))
|
||||
->options(fn () => User::where('status', 'active')->pluck('name', 'id'))
|
||||
->searchable()->columnSpan(2),
|
||||
Forms\Components\TextInput::make('mileage_in')->label('Km intrare')->numeric(),
|
||||
Forms\Components\TextInput::make('mileage_out')->label('Km ieșire')->numeric(),
|
||||
Forms\Components\TextInput::make('mileage_in')->label(__('Km intrare'))->numeric(),
|
||||
Forms\Components\TextInput::make('mileage_out')->label(__('Km ieșire'))->numeric(),
|
||||
]),
|
||||
|
||||
// Toate secțiunile de detaliu — collapsible și collapsed by default
|
||||
Schemas\Components\Section::make('Diagnostic')
|
||||
Schemas\Components\Section::make(__('Diagnostic'))
|
||||
->collapsible()->collapsed()->compact()
|
||||
->schema([
|
||||
Forms\Components\Textarea::make('complaint')->label('Plângere client')->rows(2)->columnSpanFull(),
|
||||
Forms\Components\Textarea::make('diagnosis')->label('Diagnostic')->rows(3)->columnSpanFull(),
|
||||
Forms\Components\Textarea::make('recommendations')->label('Recomandări')->rows(2)->columnSpanFull(),
|
||||
Forms\Components\Textarea::make('complaint')->label(__('Plângere client'))->rows(2)->columnSpanFull(),
|
||||
Forms\Components\Textarea::make('diagnosis')->label(__('Diagnostic'))->rows(3)->columnSpanFull(),
|
||||
Forms\Components\Textarea::make('recommendations')->label(__('Recomandări'))->rows(2)->columnSpanFull(),
|
||||
]),
|
||||
Schemas\Components\Section::make('Foto')
|
||||
Schemas\Components\Section::make(__('Foto'))
|
||||
->collapsible()->collapsed()->compact()
|
||||
->schema([
|
||||
\Filament\Forms\Components\SpatieMediaLibraryFileUpload::make('photos')
|
||||
->label('Fotografii')->collection('photos')
|
||||
->label(__('Fotografii'))->collection('photos')
|
||||
->multiple()->reorderable()->image()->imageEditor()->maxFiles(20)
|
||||
->columnSpanFull(),
|
||||
]),
|
||||
Schemas\Components\Section::make('Tracking & ETA')
|
||||
Schemas\Components\Section::make(__('Tracking & ETA'))
|
||||
->columns(3)->collapsible()->collapsed()->compact()
|
||||
->schema([
|
||||
Forms\Components\DateTimePicker::make('eta_at')->label('Gata estimat (ETA)')->seconds(false),
|
||||
Forms\Components\TextInput::make('tracking_token')->label('Token public')
|
||||
Forms\Components\DateTimePicker::make('eta_at')->label(__('Gata estimat (ETA)'))->seconds(false),
|
||||
Forms\Components\TextInput::make('tracking_token')->label(__('Token public'))
|
||||
->disabled()->dehydrated(false)->columnSpan(2)
|
||||
->helperText(fn (?WorkOrder $record) => $record?->tracking_token
|
||||
? 'Link client: ' . $record->trackingUrl()
|
||||
: 'Se generează la salvare'),
|
||||
]),
|
||||
Schemas\Components\Section::make('Plată & total')
|
||||
Schemas\Components\Section::make(__('Plată & total'))
|
||||
->columns(3)->collapsible()->collapsed()->compact()
|
||||
->schema([
|
||||
Forms\Components\Select::make('pay_status')->options(WorkOrder::PAY_STATUSES)->default('unpaid')->required(),
|
||||
Forms\Components\TextInput::make('discount_pct')->label('Discount %')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('discount_pct')->label(__('Discount %'))->numeric()->default(0),
|
||||
Forms\Components\Toggle::make('apply_margin')
|
||||
->label('Aplică marjă internă')
|
||||
->label(__('Aplică marjă internă'))
|
||||
->default(true)
|
||||
->helperText('On = din prețul manoperelor se scade marja pentru salariu. Off = manopere la cost (salariu pe Total integral).')
|
||||
->helperText(__('On = din prețul manoperelor se scade marja pentru salariu. Off = manopere la cost (salariu pe Total integral).'))
|
||||
->visible(fn () => auth()->user()?->canDo(\App\Auth\Permissions::FINANCE_VIEW_INTERNAL_MARGIN) ?? false),
|
||||
Forms\Components\TextInput::make('override_margin_pct')->label('Marjă internă (%) — override')
|
||||
Forms\Components\TextInput::make('override_margin_pct')->label(__('Marjă internă (%) — override'))
|
||||
->numeric()->step(0.01)->minValue(0)->maxValue(90)
|
||||
->placeholder('Ex: 25')
|
||||
->helperText('Doar pentru cazuri speciale. Lasă gol → folosește marja mecanicului.')
|
||||
->placeholder(__('Ex: 25'))
|
||||
->helperText(__('Doar pentru cazuri speciale. Lasă gol → folosește marja mecanicului.'))
|
||||
->visible(fn () => auth()->user()?->canDo(\App\Auth\Permissions::FINANCE_VIEW_INTERNAL_MARGIN) ?? false),
|
||||
Forms\Components\TextInput::make('total')->label('Total')->numeric()->disabled()->dehydrated(false),
|
||||
Forms\Components\Toggle::make('approved')->label('Aprobat de client'),
|
||||
Forms\Components\DatePicker::make('closed_at')->label('Închis'),
|
||||
Forms\Components\TextInput::make('total')->label(__('Total'))->numeric()->disabled()->dehydrated(false),
|
||||
Forms\Components\Toggle::make('approved')->label(__('Aprobat de client')),
|
||||
Forms\Components\DatePicker::make('closed_at')->label(__('Închis')),
|
||||
]),
|
||||
]);
|
||||
}
|
||||
@@ -141,11 +151,11 @@ class WorkOrderResource extends Resource
|
||||
{
|
||||
return $table
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('number')->label('Nr.')->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('opened_at')->label('Deschis')->date('d.m.Y')->sortable(),
|
||||
Tables\Columns\TextColumn::make('client.name')->label('Client')->searchable(),
|
||||
Tables\Columns\TextColumn::make('vehicle.plate')->label('Auto')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('master.name')->label('Maistru')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('number')->label(__('Nr.'))->searchable()->sortable(),
|
||||
Tables\Columns\TextColumn::make('opened_at')->label(__('Deschis'))->date('d.m.Y')->sortable(),
|
||||
Tables\Columns\TextColumn::make('client.name')->label(__('Client'))->searchable(),
|
||||
Tables\Columns\TextColumn::make('vehicle.plate')->label(__('Auto'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('master.name')->label(__('Maistru'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('status')
|
||||
->formatStateUsing(fn ($state) => WorkOrder::STATUSES[$state] ?? $state)
|
||||
->badge()
|
||||
@@ -170,12 +180,12 @@ class WorkOrderResource extends Resource
|
||||
Tables\Filters\SelectFilter::make('status')->options(WorkOrder::STATUSES),
|
||||
Tables\Filters\SelectFilter::make('pay_status')->options(WorkOrder::PAY_STATUSES),
|
||||
Tables\Filters\SelectFilter::make('master_id')
|
||||
->label('Maistru')
|
||||
->label(__('Maistru'))
|
||||
->options(fn () => User::pluck('name', 'id')),
|
||||
])
|
||||
->actions([
|
||||
Actions\Action::make('pdf')
|
||||
->label('PDF')
|
||||
->label(__('PDF'))
|
||||
->icon('heroicon-m-document-arrow-down')
|
||||
->color('gray')
|
||||
->action(function (WorkOrder $r) {
|
||||
@@ -190,8 +200,8 @@ class WorkOrderResource extends Resource
|
||||
Actions\EditAction::make(),
|
||||
Actions\DeleteAction::make(),
|
||||
])
|
||||
->emptyStateHeading('Nicio fișă de lucru')
|
||||
->emptyStateDescription('Crează prima fișă pentru o mașină existentă. Adaugă manopere, piese, plăți — totalul se calculează automat.')
|
||||
->emptyStateHeading(__('Nicio fișă de lucru'))
|
||||
->emptyStateDescription(__('Crează prima fișă pentru o mașină existentă. Adaugă manopere, piese, plăți — totalul se calculează automat.'))
|
||||
->emptyStateIcon('heroicon-o-wrench-screwdriver')
|
||||
->defaultSort('opened_at', 'desc');
|
||||
}
|
||||
|
||||
@@ -33,12 +33,12 @@ class EditWorkOrder extends EditRecord
|
||||
{
|
||||
return [
|
||||
Actions\Action::make('apply_template')
|
||||
->label('Aplică șablon')
|
||||
->label(__('Aplică șablon'))
|
||||
->icon('heroicon-m-clipboard-document-list')
|
||||
->color('gray')
|
||||
->schema([
|
||||
\Filament\Forms\Components\Select::make('template_id')
|
||||
->label('Șablon serviciu')
|
||||
->label(__('Șablon serviciu'))
|
||||
->options(fn () => \App\Models\Tenant\ServiceTemplate::where('is_active', true)->pluck('name', 'id'))
|
||||
->searchable()
|
||||
->required(),
|
||||
@@ -53,11 +53,11 @@ class EditWorkOrder extends EditRecord
|
||||
->success()->send();
|
||||
}),
|
||||
Actions\Action::make('ai_diagnose')
|
||||
->label('AI: sugerează diagnostic')
|
||||
->label(__('AI: sugerează diagnostic'))
|
||||
->icon('heroicon-m-sparkles')
|
||||
->color('primary')
|
||||
->visible(fn () => ! empty($this->record->complaint))
|
||||
->modalHeading('Diagnostic AI bazat pe plângerea clientului')
|
||||
->modalHeading(__('Diagnostic AI bazat pe plângerea clientului'))
|
||||
->modalSubmitAction(false)
|
||||
->modalCancelActionLabel('Închide')
|
||||
->modalContent(function () {
|
||||
@@ -66,7 +66,7 @@ class EditWorkOrder extends EditRecord
|
||||
return view('filament.tenant.ai-reply', ['reply' => $reply, 'meta' => $meta]);
|
||||
}),
|
||||
Actions\Action::make('tracking')
|
||||
->label('Link client (QR)')
|
||||
->label(__('Link client (QR)'))
|
||||
->icon('heroicon-m-qr-code')
|
||||
->color('primary')
|
||||
->modalHeading(fn () => 'Tracking client — WO #' . $this->record->number)
|
||||
@@ -76,7 +76,7 @@ class EditWorkOrder extends EditRecord
|
||||
'wo' => $this->record,
|
||||
])),
|
||||
Actions\Action::make('pdf')
|
||||
->label('Descarcă PDF')
|
||||
->label(__('Descarcă PDF'))
|
||||
->icon('heroicon-m-document-arrow-down')
|
||||
->color('gray')
|
||||
->action(function () {
|
||||
|
||||
+26
-21
@@ -19,13 +19,18 @@ class PartsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'parts';
|
||||
|
||||
protected static ?string $title = 'Piese';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
|
||||
{
|
||||
return __('Piese');
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Forms\Components\Select::make('part_id')
|
||||
->label('Din catalog (lasă gol pentru text liber)')
|
||||
->label(__('Din catalog (lasă gol pentru text liber)'))
|
||||
->options(fn () => Part::where('is_active', true)
|
||||
->get()
|
||||
->mapWithKeys(fn ($p) => [$p->id => "{$p->name} " . ($p->article ? "[{$p->article}] " : '') . "(stoc: {$p->qty})"])
|
||||
@@ -43,20 +48,20 @@ class PartsRelationManager extends RelationManager
|
||||
}
|
||||
})
|
||||
->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('name')->label('Denumire')->required()->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('article')->label('Cod articol')->maxLength(64),
|
||||
Forms\Components\TextInput::make('name')->label(__('Denumire'))->required()->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('article')->label(__('Cod articol'))->maxLength(64),
|
||||
Forms\Components\TextInput::make('brand')->maxLength(64),
|
||||
Forms\Components\TextInput::make('qty')->label('Cantitate')->numeric()->default(1)->required(),
|
||||
Forms\Components\TextInput::make('unit')->label('UM')->maxLength(16)->default('buc'),
|
||||
Forms\Components\TextInput::make('buy_price')->label('Preț achiziție')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('sell_price')->label('Preț vânzare')->numeric()->required(),
|
||||
Forms\Components\TextInput::make('discount_pct')->label('Discount %')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('qty')->label(__('Cantitate'))->numeric()->default(1)->required(),
|
||||
Forms\Components\TextInput::make('unit')->label(__('UM'))->maxLength(16)->default('buc'),
|
||||
Forms\Components\TextInput::make('buy_price')->label(__('Preț achiziție'))->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('sell_price')->label(__('Preț vânzare'))->numeric()->required(),
|
||||
Forms\Components\TextInput::make('discount_pct')->label(__('Discount %'))->numeric()->default(0),
|
||||
Forms\Components\Select::make('status')
|
||||
->options(WorkOrderPart::STATUSES)
|
||||
->default('needed')
|
||||
->required()
|
||||
->helperText('La trecere pe „Montată" se scade automat din stoc.'),
|
||||
Forms\Components\Textarea::make('notes')->label('Observații')->columnSpanFull()->rows(2),
|
||||
->helperText(__('La trecere pe „Montată" se scade automat din stoc.')),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Observații'))->columnSpanFull()->rows(2),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -65,11 +70,11 @@ class PartsRelationManager extends RelationManager
|
||||
return $table
|
||||
->recordTitleAttribute('name')
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')->label('Piesă')->wrap(),
|
||||
Tables\Columns\TextColumn::make('article')->label('Cod')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('name')->label(__('Piesă'))->wrap(),
|
||||
Tables\Columns\TextColumn::make('article')->label(__('Cod'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('brand')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('qty')->label('Cant.')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('sell_price')->label('Preț')->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('qty')->label(__('Cant.'))->alignRight(),
|
||||
Tables\Columns\TextColumn::make('sell_price')->label(__('Preț'))->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('total')->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('status')
|
||||
->formatStateUsing(fn ($s) => WorkOrderPart::STATUSES[$s] ?? $s)
|
||||
@@ -86,11 +91,11 @@ class PartsRelationManager extends RelationManager
|
||||
])
|
||||
->actions([
|
||||
Actions\Action::make('smart_price')
|
||||
->label('Preț inteligent')
|
||||
->label(__('Preț inteligent'))
|
||||
->icon('heroicon-m-sparkles')
|
||||
->color('primary')
|
||||
->visible(fn (WorkOrderPart $r) => (bool) $r->part_id)
|
||||
->modalHeading('Preț contextual')
|
||||
->modalHeading(__('Preț contextual'))
|
||||
->modalSubmitActionLabel('Aplică prețul')
|
||||
->modalContent(function (WorkOrderPart $r) {
|
||||
$wo = $r->workOrder;
|
||||
@@ -112,7 +117,7 @@ class PartsRelationManager extends RelationManager
|
||||
->success()->send();
|
||||
}),
|
||||
Actions\Action::make('issue_now')
|
||||
->label('Eliberează')
|
||||
->label(__('Eliberează'))
|
||||
->icon('heroicon-m-arrow-up-on-square')
|
||||
->color('warning')
|
||||
->visible(fn (WorkOrderPart $r) => $r->part_id
|
||||
@@ -128,7 +133,7 @@ class PartsRelationManager extends RelationManager
|
||||
->success()->send();
|
||||
}),
|
||||
Actions\Action::make('return_part')
|
||||
->label('Restituire')
|
||||
->label(__('Restituire'))
|
||||
->icon('heroicon-m-arrow-uturn-left')
|
||||
->color('gray')
|
||||
->visible(fn (WorkOrderPart $r) => $r->part_id
|
||||
@@ -137,12 +142,12 @@ class PartsRelationManager extends RelationManager
|
||||
->exists())
|
||||
->schema([
|
||||
Forms\Components\TextInput::make('qty')
|
||||
->label('Cantitate restituită')
|
||||
->label(__('Cantitate restituită'))
|
||||
->numeric()
|
||||
->required()
|
||||
->minValue(0.001)
|
||||
->default(fn (WorkOrderPart $r) => (float) $r->qty),
|
||||
Forms\Components\Textarea::make('notes')->rows(2)->label('Observații'),
|
||||
Forms\Components\Textarea::make('notes')->rows(2)->label(__('Observații')),
|
||||
])
|
||||
->action(function (WorkOrderPart $r, array $data) {
|
||||
$batch = app(WarehouseService::class)->returnPart(
|
||||
|
||||
+12
-7
@@ -14,19 +14,24 @@ class PaymentsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'payments';
|
||||
|
||||
protected static ?string $title = 'Plăți';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
|
||||
{
|
||||
return __('Plăți');
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Forms\Components\DatePicker::make('paid_at')->label('Data')->default(today())->required(),
|
||||
Forms\Components\TextInput::make('amount')->label('Sumă')->numeric()->required(),
|
||||
Forms\Components\DatePicker::make('paid_at')->label(__('Data'))->default(today())->required(),
|
||||
Forms\Components\TextInput::make('amount')->label(__('Sumă'))->numeric()->required(),
|
||||
Forms\Components\Select::make('method')
|
||||
->options(Payment::METHODS)
|
||||
->default('cash')
|
||||
->required(),
|
||||
Forms\Components\TextInput::make('reference')->label('Referință')->maxLength(64),
|
||||
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(2),
|
||||
Forms\Components\TextInput::make('reference')->label(__('Referință'))->maxLength(64),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(2),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -35,9 +40,9 @@ class PaymentsRelationManager extends RelationManager
|
||||
return $table
|
||||
->recordTitleAttribute('amount')
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('paid_at')->label('Data')->date('d.m.Y'),
|
||||
Tables\Columns\TextColumn::make('paid_at')->label(__('Data'))->date('d.m.Y'),
|
||||
Tables\Columns\TextColumn::make('amount')->money('MDL')->alignRight()
|
||||
->summarize(Tables\Columns\Summarizers\Sum::make()->money('MDL')->label('Plătit')),
|
||||
->summarize(Tables\Columns\Summarizers\Sum::make()->money('MDL')->label(__('Plătit'))),
|
||||
Tables\Columns\TextColumn::make('method')
|
||||
->formatStateUsing(fn ($s) => Payment::METHODS[$s] ?? $s)
|
||||
->badge(),
|
||||
|
||||
+18
-13
@@ -15,27 +15,32 @@ class SubcontractJobsRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'subcontractJobs';
|
||||
|
||||
protected static ?string $title = 'Lucrări la terți';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
|
||||
{
|
||||
return __('Lucrări la terți');
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Forms\Components\Select::make('subcontractor_id')
|
||||
->label('Subcontractor')
|
||||
->label(__('Subcontractor'))
|
||||
->options(fn () => Subcontractor::where('is_active', true)->pluck('name', 'id'))
|
||||
->searchable()
|
||||
->columnSpanFull(),
|
||||
Forms\Components\Select::make('category')
|
||||
->label('Categorie')
|
||||
->label(__('Categorie'))
|
||||
->options(array_combine(Subcontractor::SPECIALTIES, Subcontractor::SPECIALTIES))
|
||||
->searchable(),
|
||||
Forms\Components\Select::make('status')->options(SubcontractJob::STATUSES)->default('sent')->required(),
|
||||
Forms\Components\Textarea::make('description')->label('Descriere')->rows(2)->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('cost')->label('Cost (terț)')->numeric()->default(0)->required(),
|
||||
Forms\Components\TextInput::make('markup_pct')->label('Markup %')->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('client_price')->label('Preț client')->numeric()->default(0)
|
||||
->helperText('Folosit dacă markup = 0.'),
|
||||
Forms\Components\DatePicker::make('eta')->label('ETA'),
|
||||
Forms\Components\Textarea::make('description')->label(__('Descriere'))->rows(2)->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('cost')->label(__('Cost (terț)'))->numeric()->default(0)->required(),
|
||||
Forms\Components\TextInput::make('markup_pct')->label(__('Markup %'))->numeric()->default(0),
|
||||
Forms\Components\TextInput::make('client_price')->label(__('Preț client'))->numeric()->default(0)
|
||||
->helperText(__('Folosit dacă markup = 0.')),
|
||||
Forms\Components\DatePicker::make('eta')->label(__('ETA')),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -44,13 +49,13 @@ class SubcontractJobsRelationManager extends RelationManager
|
||||
return $table
|
||||
->recordTitleAttribute('number')
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('number')->label('Nr.'),
|
||||
Tables\Columns\TextColumn::make('subcontractor.name')->label('Terț')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('number')->label(__('Nr.')),
|
||||
Tables\Columns\TextColumn::make('subcontractor.name')->label(__('Terț'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('category')->badge()->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('cost')->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('client_price')->label('Preț client')->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('client_price')->label(__('Preț client'))->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('margin')
|
||||
->label('Marjă')
|
||||
->label(__('Marjă'))
|
||||
->state(fn (SubcontractJob $r) => $r->margin())
|
||||
->money('MDL')->alignRight()
|
||||
->color(fn ($s) => (float) $s > 0 ? 'success' : ((float) $s < 0 ? 'danger' : 'gray')),
|
||||
|
||||
+17
-12
@@ -17,13 +17,18 @@ class WorksRelationManager extends RelationManager
|
||||
{
|
||||
protected static string $relationship = 'works';
|
||||
|
||||
protected static ?string $title = 'Manopere';
|
||||
protected static ?string $title = null;
|
||||
|
||||
public static function getTitle(\Illuminate\Database\Eloquent\Model $ownerRecord, string $pageClass): string
|
||||
{
|
||||
return __('Manopere');
|
||||
}
|
||||
|
||||
public function form(Schema $schema): Schema
|
||||
{
|
||||
return $schema->components([
|
||||
Forms\Components\Select::make('labor_id')
|
||||
->label('Catalog manoperă')
|
||||
->label(__('Catalog manoperă'))
|
||||
->options(fn () => Labor::where('is_active', true)
|
||||
->get()
|
||||
->mapWithKeys(fn ($l) => [$l->id => "[{$l->category}] {$l->name_ro} ({$l->hours}h)"])
|
||||
@@ -38,18 +43,18 @@ class WorksRelationManager extends RelationManager
|
||||
}
|
||||
})
|
||||
->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('name')->label('Nume')->required()->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('hours')->label('Ore')->numeric()->default(1)->required(),
|
||||
Forms\Components\TextInput::make('price_per_hour')->label('Preț/h')->numeric()->required(),
|
||||
Forms\Components\TextInput::make('name')->label(__('Nume'))->required()->columnSpanFull(),
|
||||
Forms\Components\TextInput::make('hours')->label(__('Ore'))->numeric()->default(1)->required(),
|
||||
Forms\Components\TextInput::make('price_per_hour')->label(__('Preț/h'))->numeric()->required(),
|
||||
Forms\Components\Select::make('master_id')
|
||||
->label('Maistru')
|
||||
->label(__('Maistru'))
|
||||
->options(fn () => User::pluck('name', 'id'))
|
||||
->searchable(),
|
||||
Forms\Components\Select::make('status')
|
||||
->options(WorkOrderWork::STATUSES)
|
||||
->default('todo')
|
||||
->required(),
|
||||
Forms\Components\Textarea::make('notes')->label('Notițe')->columnSpanFull()->rows(2),
|
||||
Forms\Components\Textarea::make('notes')->label(__('Notițe'))->columnSpanFull()->rows(2),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -68,11 +73,11 @@ class WorksRelationManager extends RelationManager
|
||||
return $table
|
||||
->recordTitleAttribute('name')
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')->label('Manoperă')->wrap(),
|
||||
Tables\Columns\TextColumn::make('hours')->label('Ore')->numeric(decimalPlaces: 2)->alignRight(),
|
||||
Tables\Columns\TextColumn::make('price_per_hour')->label('Preț/h')->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('name')->label(__('Manoperă'))->wrap(),
|
||||
Tables\Columns\TextColumn::make('hours')->label(__('Ore'))->numeric(decimalPlaces: 2)->alignRight(),
|
||||
Tables\Columns\TextColumn::make('price_per_hour')->label(__('Preț/h'))->money('MDL')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('total')
|
||||
->label('Total')
|
||||
->label(__('Total'))
|
||||
->money('MDL')
|
||||
->alignRight()
|
||||
->description(function ($record) {
|
||||
@@ -82,7 +87,7 @@ class WorksRelationManager extends RelationManager
|
||||
if ((float) $record->applied_margin_pct === 0.0) return null;
|
||||
return 'Bază salariu: ' . number_format((float) $record->salary_base, 2) . ' MDL · marjă ' . rtrim(rtrim(number_format((float) $record->applied_margin_pct, 2), '0'), '.') . '%';
|
||||
}),
|
||||
Tables\Columns\TextColumn::make('master.name')->label('Maistru')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('master.name')->label(__('Maistru'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('status')
|
||||
->formatStateUsing(fn ($s) => WorkOrderWork::STATUSES[$s] ?? $s)
|
||||
->badge()
|
||||
|
||||
@@ -25,13 +25,13 @@ class LowStockTable extends BaseWidget
|
||||
->orderBy('qty')
|
||||
)
|
||||
->columns([
|
||||
Tables\Columns\TextColumn::make('name')->label('Piesă')->wrap()->searchable(),
|
||||
Tables\Columns\TextColumn::make('article')->label('Cod')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('qty')->label('Stoc')->alignRight()
|
||||
Tables\Columns\TextColumn::make('name')->label(__('Piesă'))->wrap()->searchable(),
|
||||
Tables\Columns\TextColumn::make('article')->label(__('Cod'))->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('qty')->label(__('Stoc'))->alignRight()
|
||||
->color(fn ($state) => $state <= 0 ? 'danger' : 'warning')
|
||||
->weight('bold'),
|
||||
Tables\Columns\TextColumn::make('min_qty')->label('Min.')->alignRight(),
|
||||
Tables\Columns\TextColumn::make('preferredSupplier.name')->label('Furnizor')->placeholder('—'),
|
||||
Tables\Columns\TextColumn::make('min_qty')->label(__('Min.'))->alignRight(),
|
||||
Tables\Columns\TextColumn::make('preferredSupplier.name')->label(__('Furnizor'))->placeholder('—'),
|
||||
])
|
||||
->paginated(false)
|
||||
->defaultSort('qty');
|
||||
|
||||
@@ -22,22 +22,22 @@ class StatsOverview extends BaseWidget
|
||||
|
||||
return [
|
||||
Stat::make('Clienți', Client::count())
|
||||
->description('Total în baza de date')
|
||||
->description(__('Total în baza de date'))
|
||||
->icon('heroicon-o-users')
|
||||
->color('primary'),
|
||||
|
||||
Stat::make('Mașini', Vehicle::count())
|
||||
->description('Total înregistrate')
|
||||
->description(__('Total înregistrate'))
|
||||
->icon('heroicon-o-truck')
|
||||
->color('info'),
|
||||
|
||||
Stat::make('Cereri noi', $newLeads)
|
||||
->description('De procesat')
|
||||
->description(__('De procesat'))
|
||||
->icon('heroicon-o-inbox-arrow-down')
|
||||
->color($newLeads > 0 ? 'warning' : 'success'),
|
||||
|
||||
Stat::make('Deal-uri active', $openDeals)
|
||||
->description('În pipeline')
|
||||
->description(__('În pipeline'))
|
||||
->icon('heroicon-o-funnel')
|
||||
->color('primary'),
|
||||
|
||||
|
||||
+1419
-71
File diff suppressed because it is too large
Load Diff
+1419
-71
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user