Files
kreios/backoffice/app/Filament/Resources/ClienteResource.php
T
carmine.savino 8bef57b64b feat: implement discount and transport features in the quote system
- Added maximum discount settings configurable by the administrator.
- Implemented discount calculations in the quote confirmation process.
- Introduced transport options with associated costs, including handling for exceeding maximum piece limits.
- Updated frontend to display discounts and transport information in the quote summary.
- Created new database migrations for storing discount and transport settings.
- Developed a new Filament page for managing global discount settings.
- Enhanced email templates to include discount and transport details.
- Added tests to ensure proper functionality of discounts and transport calculations.
2026-10-20 17:45:50 +02:00

203 lines
11 KiB
PHP

<?php
namespace App\Filament\Resources;
use App\Filament\Resources\ClienteResource\Pages;
use App\Models\Cliente;
use App\Models\Impostazione;
use App\Support\ClienteCredenziali;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Infolists;
use Filament\Infolists\Infolist;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
class ClienteResource extends Resource
{
protected static ?string $model = Cliente::class;
protected static ?string $navigationIcon = 'heroicon-o-users';
protected static ?string $navigationLabel = 'Clienti';
protected static ?string $modelLabel = 'cliente';
protected static ?string $pluralModelLabel = 'clienti';
protected static ?int $navigationSort = 2;
public static function form(Form $form): Form
{
return $form->schema([
Forms\Components\Section::make('Accesso')
->description('Credenziali per l\'accesso del cliente. Generate automaticamente, ma modificabili.')
->columns(2)
->schema([
Forms\Components\TextInput::make('username')
->label('Nome utente')
->required()
->maxLength(50)
->unique(ignoreRecord: true)
->default(fn (string $operation) => $operation === 'create' ? ClienteCredenziali::generaUsername() : null)
->suffixAction(
Forms\Components\Actions\Action::make('rigeneraUsername')
->icon('heroicon-m-arrow-path')
->tooltip('Genera un nuovo nome utente')
->action(fn (Forms\Set $set) => $set('username', ClienteCredenziali::generaUsername()))
),
Forms\Components\TextInput::make('password')
->label('Password')
->password()
->revealable()
->required(fn (string $operation) => $operation === 'create')
->dehydrated(fn (?string $state) => filled($state))
->default(fn (string $operation) => $operation === 'create' ? ClienteCredenziali::generaPassword() : null)
->suffixAction(
Forms\Components\Actions\Action::make('rigeneraPassword')
->icon('heroicon-m-arrow-path')
->tooltip('Genera una nuova password')
->action(fn (Forms\Set $set) => $set('password', ClienteCredenziali::generaPassword()))
)
->helperText(fn (string $operation) => $operation === 'create'
? 'Generata automaticamente. Ricordati di copiarla dopo il salvataggio: non sarà più leggibile.'
: 'Lascia vuoto per mantenere la password attuale, oppure genera/inserisci una nuova password.'),
]),
Forms\Components\Section::make('Scontistica')
->description(fn () => sprintf(
'Sconti che il cliente può applicare al preventivo prima dell\'invio. Lascia vuoto per la regola base (1° sconto fino al %s%%, sconto extra fino al %s%% sul totale già scontato).',
static::percentuale(Impostazione::scontiBase()['max_1']),
static::percentuale(Impostazione::scontiBase()['max_2']),
))
->columns(2)
->schema([
Forms\Components\Toggle::make('sconti_abilitati')
->label('Può applicare sconti')
->default(true)
->live()
->columnSpanFull(),
Forms\Components\TextInput::make('sconto_max_1')
->label('1° sconto massimo')
->numeric()
->minValue(0)
->maxValue(100)
->suffix('%')
->placeholder(fn () => static::percentuale(Impostazione::scontiBase()['max_1']).' (regola base)')
->visible(fn (Forms\Get $get) => (bool) $get('sconti_abilitati')),
Forms\Components\TextInput::make('sconto_max_2')
->label('Sconto extra massimo')
->numeric()
->minValue(0)
->maxValue(100)
->suffix('%')
->placeholder(fn () => static::percentuale(Impostazione::scontiBase()['max_2']).' (regola base)')
->visible(fn (Forms\Get $get) => (bool) $get('sconti_abilitati')),
]),
Forms\Components\Section::make('Anagrafica')
->description('Campi facoltativi.')
->columns(2)
->schema([
Forms\Components\TextInput::make('nome')->label('Nome'),
Forms\Components\TextInput::make('cognome')->label('Cognome'),
Forms\Components\TextInput::make('ragione_sociale')->label('Ragione sociale'),
Forms\Components\TextInput::make('email')->label('Email')->email(),
Forms\Components\TextInput::make('telefono')->label('Telefono')->tel(),
Forms\Components\TextInput::make('indirizzo')->label('Indirizzo'),
Forms\Components\TextInput::make('cap')->label('CAP')->maxLength(10),
Forms\Components\TextInput::make('citta')->label('Città'),
Forms\Components\TextInput::make('provincia')->label('Provincia')->maxLength(5),
Forms\Components\Textarea::make('note')->label('Note')->columnSpanFull(),
]),
]);
}
public static function table(Table $table): Table
{
return $table
->defaultSort('created_at', 'desc')
->columns([
Tables\Columns\TextColumn::make('nominativo')->label('Nominativo')->searchable(['nome', 'cognome', 'ragione_sociale']),
Tables\Columns\TextColumn::make('username')->label('Nome utente')->searchable()->placeholder('—'),
Tables\Columns\TextColumn::make('email')->label('Email')->searchable()->copyable()->placeholder('—'),
Tables\Columns\TextColumn::make('telefono')->label('Telefono')->placeholder('—'),
Tables\Columns\TextColumn::make('citta')->label('Città')->placeholder('—'),
Tables\Columns\TextColumn::make('preventivi_count')->label('Preventivi')->counts('preventivi'),
Tables\Columns\TextColumn::make('created_at')->label('Registrato')->dateTime('d/m/Y')->sortable(),
])
->actions([
Tables\Actions\ViewAction::make(),
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make()
->modalDescription('Verrà eliminato definitivamente il cliente insieme a tutti i suoi preventivi e ai file associati (PDF, immagini)')
->successRedirectUrl(static::getUrl('index')),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make()
->modalDescription('I clienti selezionati verranno eliminati definitivamente insieme a tutti i loro preventivi e ai file associati (PDF, immagini)')
->successRedirectUrl(static::getUrl('index')),
]),
]);
}
public static function infolist(Infolist $infolist): Infolist
{
return $infolist->schema([
Infolists\Components\View::make('filament.infolists.cliente-password-reveal')
->visible(fn () => filled(session('cliente_password_plain')))
->viewData(fn () => [
'username' => session('cliente_username_plain'),
'password' => session('cliente_password_plain'),
]),
Infolists\Components\Section::make('Accesso')
->columns(2)
->schema([
Infolists\Components\TextEntry::make('username')->label('Nome utente')->copyable()->placeholder('—'),
Infolists\Components\TextEntry::make('created_at')->label('Cliente dal')->dateTime('d/m/Y H:i'),
Infolists\Components\TextEntry::make('scontistica')
->label('Scontistica')
->state(function (Cliente $record): string {
if (! $record->sconti_abilitati) {
return 'Disabilitata';
}
$massimi = $record->scontiMassimi();
$base = $record->sconto_max_1 === null && $record->sconto_max_2 === null ? ' (regola base)' : '';
return sprintf('Fino al %s%% + extra fino al %s%%%s',
static::percentuale($massimi['max_1']), static::percentuale($massimi['max_2']), $base);
})
->columnSpanFull(),
]),
Infolists\Components\Section::make('Anagrafica')
->columns(3)
->schema([
Infolists\Components\TextEntry::make('nominativo')->label('Nominativo')->placeholder('—'),
Infolists\Components\TextEntry::make('email')->label('Email')->copyable()->placeholder('—'),
Infolists\Components\TextEntry::make('telefono')->label('Telefono')->placeholder('—'),
Infolists\Components\TextEntry::make('indirizzo')->label('Indirizzo')->placeholder('—'),
Infolists\Components\TextEntry::make('cap')->label('CAP')->placeholder('—'),
Infolists\Components\TextEntry::make('citta')->label('Città')->placeholder('—'),
Infolists\Components\TextEntry::make('provincia')->label('Provincia')->placeholder('—'),
Infolists\Components\TextEntry::make('note')->label('Note')->columnSpanFull()->placeholder('—'),
]),
]);
}
/** 50.00 -> "50", 12.50 -> "12,5" */
private static function percentuale(mixed $valore): string
{
return rtrim(rtrim(number_format((float) $valore, 2, ',', ''), '0'), ',');
}
public static function getPages(): array
{
return [
'index' => Pages\ListClientes::route('/'),
'create' => Pages\CreateCliente::route('/create'),
'view' => Pages\ViewCliente::route('/{record}'),
'edit' => Pages\EditCliente::route('/{record}/edit'),
];
}
}