From 8b01f91bf75580e0f52f1830183c65fb835c48bd Mon Sep 17 00:00:00 2001 From: dhanabalan Date: Sun, 26 Jul 2026 10:30:50 +0530 Subject: [PATCH 1/5] Added dealer visit plan migration file --- ...095425_create_dealer_visit_plans_table.php | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 database/migrations/2026_07_20_095425_create_dealer_visit_plans_table.php diff --git a/database/migrations/2026_07_20_095425_create_dealer_visit_plans_table.php b/database/migrations/2026_07_20_095425_create_dealer_visit_plans_table.php new file mode 100644 index 0000000..b520b15 --- /dev/null +++ b/database/migrations/2026_07_20_095425_create_dealer_visit_plans_table.php @@ -0,0 +1,51 @@ + Date: Sun, 26 Jul 2026 10:31:36 +0530 Subject: [PATCH 2/5] Added dealer visit plan model file --- app/Models/DealerVisitPlan.php | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 app/Models/DealerVisitPlan.php diff --git a/app/Models/DealerVisitPlan.php b/app/Models/DealerVisitPlan.php new file mode 100644 index 0000000..8060745 --- /dev/null +++ b/app/Models/DealerVisitPlan.php @@ -0,0 +1,33 @@ +belongsTo(EmployeeMaster::class); + } + +} -- 2.49.1 From f7d37a7c3f711c366765e6fb2d26f5982491bba4 Mon Sep 17 00:00:00 2001 From: dhanabalan Date: Sun, 26 Jul 2026 10:34:07 +0530 Subject: [PATCH 3/5] Added dealer visit plan resource pages --- .../Resources/DealerVisitPlanResource.php | 209 ++++++++++++++++++ .../Pages/CreateDealerVisitPlan.php | 12 + .../Pages/EditDealerVisitPlan.php | 22 ++ .../Pages/ListDealerVisitPlans.php | 19 ++ .../Pages/ViewDealerVisitPlan.php | 19 ++ 5 files changed, 281 insertions(+) create mode 100644 app/Filament/Resources/DealerVisitPlanResource.php create mode 100644 app/Filament/Resources/DealerVisitPlanResource/Pages/CreateDealerVisitPlan.php create mode 100644 app/Filament/Resources/DealerVisitPlanResource/Pages/EditDealerVisitPlan.php create mode 100644 app/Filament/Resources/DealerVisitPlanResource/Pages/ListDealerVisitPlans.php create mode 100644 app/Filament/Resources/DealerVisitPlanResource/Pages/ViewDealerVisitPlan.php diff --git a/app/Filament/Resources/DealerVisitPlanResource.php b/app/Filament/Resources/DealerVisitPlanResource.php new file mode 100644 index 0000000..dfce986 --- /dev/null +++ b/app/Filament/Resources/DealerVisitPlanResource.php @@ -0,0 +1,209 @@ +schema([ + Forms\Components\TextInput::make('organizer') + ->label('Organizer'), + Forms\Components\TextInput::make('mobile_number') + ->label('Mobile Number'), + Forms\Components\TextInput::make('name') + ->label('Dealer Name'), + Forms\Components\TextInput::make('company') + ->label('Dealer Company'), + Forms\Components\DatePicker::make('visit_plan_date') + ->label('Visit Plan Date'), + Forms\Components\Select::make('employee_master_id') + ->label('Recipient Employee') + ->relationship('employeeMaster', 'name') + ->required(), + Forms\Components\TextInput::make('number_of_person') + ->label('Number Of Person'), + Forms\Components\TextInput::make('purpose_of_visit') + ->label('Purpose Of Visit'), + Forms\Components\Select::make('mode_of_travel') + ->label('Mode Of Travel') + ->options([ + 'Rental' => 'Rental', + 'Car' => 'Car', + 'Bike' => 'Bike', + ]) + ->reactive() + ->placeholder('Select Mode of Travel'), + Forms\Components\Select::make('status') + ->label('Status') + ->options([ + 'Planned' => 'Planned', + 'Completed' => 'Completed', + ]) + ->required(), + Forms\Components\Hidden::make('created_by') + ->label('Created By') + ->default(Filament::auth()->user()?->name), + Forms\Components\Hidden::make('updated_by') + ->label('Updated By') + ->default(Filament::auth()->user()?->name), + ]); + } + + public static function table(Table $table): Table + { + return $table + ->columns([ + Tables\Columns\TextColumn::make('No.') + ->label('No.') + ->getStateUsing(function ($record, $livewire, $column, $rowLoop) { + $paginator = $livewire->getTableRecords(); + $perPage = method_exists($paginator, 'perPage') ? $paginator->perPage() : 10; + $currentPage = method_exists($paginator, 'currentPage') ? $paginator->currentPage() : 1; + + return ($currentPage - 1) * $perPage + $rowLoop->iteration; + }), + Tables\Columns\TextColumn::make('name') + ->label('Dealer Name') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('company') + ->label('Dealer Company') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('visit_plan_date') + ->label('Visit Plan Date') + ->alignCenter() + ->searchable() + ->date() + ->sortable(), + Tables\Columns\TextColumn::make('organizer') + ->label('Organizer') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('mobile_number') + ->label('Mobile Number') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('employeeMaster.name') + ->label('Recipient Employee') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('number_of_person') + ->label('No of Person') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('purpose_of_visit') + ->label('Purpose of Visit') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('mode_of_travel') + ->label('Mode of travel') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('status') + ->label('Status') + ->alignCenter() + ->searchable() + ->sortable(), + Tables\Columns\TextColumn::make('created_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + Tables\Columns\TextColumn::make('updated_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + Tables\Columns\TextColumn::make('deleted_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->filters([ + Tables\Filters\TrashedFilter::make(), + ]) + ->actions([ + Tables\Actions\ViewAction::make(), + Tables\Actions\EditAction::make(), + ]) + ->bulkActions([ + Tables\Actions\BulkActionGroup::make([ + Tables\Actions\DeleteBulkAction::make(), + Tables\Actions\ForceDeleteBulkAction::make(), + Tables\Actions\RestoreBulkAction::make(), + ]), + ]) + ->headerActions([ + ImportAction::make() + ->label('Import Dealer Visit Plan') + ->color('warning') + ->importer(DealerVisitPlanImporter::class) + ->visible(function () { + return Filament::auth()->user()->can('view import dealer visit plan'); + }), + ExportAction::make() + ->label('Export Dealer Visit Plan') + ->color('warning') + ->exporter(DealerVisitPlanExporter::class) + ->visible(function () { + return Filament::auth()->user()->can('view export dealer visit plan'); + }), + ]); + } + + public static function getRelations(): array + { + return [ + // + ]; + } + + public static function getPages(): array + { + return [ + 'index' => Pages\ListDealerVisitPlans::route('/'), + 'create' => Pages\CreateDealerVisitPlan::route('/create'), + 'view' => Pages\ViewDealerVisitPlan::route('/{record}'), + 'edit' => Pages\EditDealerVisitPlan::route('/{record}/edit'), + ]; + } + + public static function getEloquentQuery(): Builder + { + return parent::getEloquentQuery() + ->withoutGlobalScopes([ + SoftDeletingScope::class, + ]); + } +} diff --git a/app/Filament/Resources/DealerVisitPlanResource/Pages/CreateDealerVisitPlan.php b/app/Filament/Resources/DealerVisitPlanResource/Pages/CreateDealerVisitPlan.php new file mode 100644 index 0000000..6374418 --- /dev/null +++ b/app/Filament/Resources/DealerVisitPlanResource/Pages/CreateDealerVisitPlan.php @@ -0,0 +1,12 @@ + Date: Sun, 26 Jul 2026 10:35:25 +0530 Subject: [PATCH 4/5] Added dealer vist plan importer and exporter --- .../Exports/DealerVisitPlanExporter.php | 64 +++++++++ .../Imports/DealerVisitPlanImporter.php | 127 ++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 app/Filament/Exports/DealerVisitPlanExporter.php create mode 100644 app/Filament/Imports/DealerVisitPlanImporter.php diff --git a/app/Filament/Exports/DealerVisitPlanExporter.php b/app/Filament/Exports/DealerVisitPlanExporter.php new file mode 100644 index 0000000..9bc9480 --- /dev/null +++ b/app/Filament/Exports/DealerVisitPlanExporter.php @@ -0,0 +1,64 @@ +label('NO') + ->state(function ($record) use (&$rowNumber) { + // Increment and return the row number + return ++$rowNumber; + }), + ExportColumn::make('name') + ->label('DEALER NAME'), + ExportColumn::make('company') + ->label('DEALER COMPANY'), + ExportColumn::make('visit_plan_date') + ->label('VISIT PLAN DATE'), + ExportColumn::make('organizer') + ->label('ORGANIZER'), + ExportColumn::make('employeeMaster.name') + ->label('RECIPIENT NAME'), + ExportColumn::make('number_of_person') + ->label('NUMBER OF PERSON'), + ExportColumn::make('purpose_of_visit') + ->label('PURPOSE OF VISIT'), + ExportColumn::make('status') + ->label('STATUS'), + ExportColumn::make('created_at') + ->label('CREATED AT'), + ExportColumn::make('updated_at') + ->label('UPDATED AT'), + ExportColumn::make('created_by') + ->label('CREATED BY'), + ExportColumn::make('updated_by') + ->label('UPDATED BY'), + ExportColumn::make('deleted_at') + ->label('DELETED AT') + ->enabledByDefault(false), + ]; + } + + public static function getCompletedNotificationBody(Export $export): string + { + $body = 'Your dealer visit plan export has completed and ' . number_format($export->successful_rows) . ' ' . str('row')->plural($export->successful_rows) . ' exported.'; + + if ($failedRowsCount = $export->getFailedRowsCount()) { + $body .= ' ' . number_format($failedRowsCount) . ' ' . str('row')->plural($failedRowsCount) . ' failed to export.'; + } + + return $body; + } +} diff --git a/app/Filament/Imports/DealerVisitPlanImporter.php b/app/Filament/Imports/DealerVisitPlanImporter.php new file mode 100644 index 0000000..aeaff60 --- /dev/null +++ b/app/Filament/Imports/DealerVisitPlanImporter.php @@ -0,0 +1,127 @@ +requiredMapping() + ->exampleHeader('Dealer Name') + ->example('Suresh') + ->label('Dealer Name') + ->rules(['required']), + ImportColumn::make('company') + ->requiredMapping() + ->exampleHeader('Dealer Company') + ->example('Suresh Traders') + ->label('Dealer Company') + ->rules(['required']), + ImportColumn::make('visit_plan_date') + ->requiredMapping() + ->exampleHeader('Visit Plan Date') + ->example('2026-07-20') + ->label('Visit Plan Date'), + ImportColumn::make('organizer') + ->requiredMapping() + ->exampleHeader('Organizer') + ->example('Ramesh') + ->label('Organizer'), + ImportColumn::make('employee_master_id') + ->requiredMapping() + ->exampleHeader('Receipient Employee Code') + ->example('RAS001234') + ->label('Receipient Employee Code') + ->relationship(resolveUsing: 'code') + ->rules(['required']), + ImportColumn::make('number_of_person') + ->requiredMapping() + ->exampleHeader('Number Of Person') + ->example('10') + ->label('Number Of Person'), + ImportColumn::make('purpose_of_visit') + ->requiredMapping() + ->exampleHeader('Purpose Of Visit') + ->example('Meeting') + ->label('Purpose Of Visit'), + ImportColumn::make('status') + ->requiredMapping() + ->exampleHeader('Status') + ->example('Planned/Completed') + ->label('Status'), + ]; + } + + public function resolveRecord(): ?DealerVisitPlan + { + $warnMsg = []; + + $user = null; + $dealerName = trim($this->data['name']); + $dealerCompany = trim($this->data['company']); + $visitPlanDt = trim($this->data['visit_plan_date']); + $organizer = trim($this->data['organizer']); + $employeeCode = trim($this->data['employee_master_id']); + $noOfPerson = trim($this->data['number_of_person']); + $purposeOfVisit = trim($this->data['purpose_of_visit']); + $status = trim($this->data['status']); + $user = Filament::auth()->user()->name; + + if (Str::length($employeeCode) < 5) { + $warnMsg[] = 'Invalid receipient employee number found!'; + } + if (! empty($warnMsg)) { + throw new RowImportFailedException(implode(', ', $warnMsg)); + } + + $employeeId = EmployeeMaster::where('code', $employeeCode)->first(); + + $eId = $employeeId->id; + + DealerVisitPlan::updateOrCreate( + [ + 'name' => $dealerName, + 'company' => $dealerCompany, + 'visit_plan_date' => Carbon::createFromFormat('d-m-Y', $visitPlanDt)->format('Y-m-d'), + 'organizer' => $organizer, + 'employee_master_id' => $eId, + 'number_of_person' => $noOfPerson, + 'purpose_of_visit' => $purposeOfVisit, + 'status' => $status, + 'created_by' => $user, + 'updated_by' => $user, + ] + ); + + return null; + + // return new DealerVisitPlan(); + } + + public static function getCompletedNotificationBody(Import $import): string + { + $body = 'Your dealer visit plan import has completed and ' . number_format($import->successful_rows) . ' ' . str('row')->plural($import->successful_rows) . ' imported.'; + + if ($failedRowsCount = $import->getFailedRowsCount()) { + $body .= ' ' . number_format($failedRowsCount) . ' ' . str('row')->plural($failedRowsCount) . ' failed to import.'; + } + + return $body; + } +} -- 2.49.1 From 28503c0953bfdb400733a52e60994b429b7fe350 Mon Sep 17 00:00:00 2001 From: dhanabalan Date: Sun, 26 Jul 2026 10:36:41 +0530 Subject: [PATCH 5/5] Added dealer visit plan policy file --- app/Policies/DealerVisitPlanPolicy.php | 106 +++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 app/Policies/DealerVisitPlanPolicy.php diff --git a/app/Policies/DealerVisitPlanPolicy.php b/app/Policies/DealerVisitPlanPolicy.php new file mode 100644 index 0000000..f3b8f5b --- /dev/null +++ b/app/Policies/DealerVisitPlanPolicy.php @@ -0,0 +1,106 @@ +checkPermissionTo('view-any DealerVisitPlan'); + } + + /** + * Determine whether the user can view the model. + */ + public function view(User $user, DealerVisitPlan $dealervisitplan): bool + { + return $user->checkPermissionTo('view DealerVisitPlan'); + } + + /** + * Determine whether the user can create models. + */ + public function create(User $user): bool + { + return $user->checkPermissionTo('create DealerVisitPlan'); + } + + /** + * Determine whether the user can update the model. + */ + public function update(User $user, DealerVisitPlan $dealervisitplan): bool + { + return $user->checkPermissionTo('update DealerVisitPlan'); + } + + /** + * Determine whether the user can delete the model. + */ + public function delete(User $user, DealerVisitPlan $dealervisitplan): bool + { + return $user->checkPermissionTo('delete DealerVisitPlan'); + } + + /** + * Determine whether the user can delete any models. + */ + public function deleteAny(User $user): bool + { + return $user->checkPermissionTo('delete-any DealerVisitPlan'); + } + + /** + * Determine whether the user can restore the model. + */ + public function restore(User $user, DealerVisitPlan $dealervisitplan): bool + { + return $user->checkPermissionTo('restore DealerVisitPlan'); + } + + /** + * Determine whether the user can restore any models. + */ + public function restoreAny(User $user): bool + { + return $user->checkPermissionTo('restore-any DealerVisitPlan'); + } + + /** + * Determine whether the user can replicate the model. + */ + public function replicate(User $user, DealerVisitPlan $dealervisitplan): bool + { + return $user->checkPermissionTo('replicate DealerVisitPlan'); + } + + /** + * Determine whether the user can reorder the models. + */ + public function reorder(User $user): bool + { + return $user->checkPermissionTo('reorder DealerVisitPlan'); + } + + /** + * Determine whether the user can permanently delete the model. + */ + public function forceDelete(User $user, DealerVisitPlan $dealervisitplan): bool + { + return $user->checkPermissionTo('force-delete DealerVisitPlan'); + } + + /** + * Determine whether the user can permanently delete any models. + */ + public function forceDeleteAny(User $user): bool + { + return $user->checkPermissionTo('force-delete-any DealerVisitPlan'); + } +} -- 2.49.1