Now that we have our Quotes being created, we should have the ability to generate a PDF and send it to our customer:
In this lesson, we will do the following:
We can quickly create a new View page by running the following command:
php artisan make:filament-page ViewQuote --resource=QuoteResource --type=ViewRecord
This will generate a file, but we still need to register it in our Resource:
app/Filament/Resources/QuoteResource.php
// ... public static function table(Table $table): Table { return $table // ... ->bulkActions([ Tables\Actions\BulkActionGroup::make([ Tables\Actions\DeleteBulkAction::make(), ]), ]); ]) ->recordUrl(function ($record) { return Pages\ViewQuote::getUrl([$record]); }); } public static function getPages(): array{ return [ 'index' => Pages\ListQuotes::route('/'), 'create' => Pages\CreateQuote::route('/create'), 'view' => Pages\ViewQuote::route('/{record}'), 'edit' => Pages\EditQuote::route('/{record}/edit'), ];}
Now we can click on the row, and we should see our new page:
That's it for now. Next, we will generate the PDF and display it on the View page.
Next, we will install a package Laravel Invoices to deal with the invoice generation itself:
Note: This can be replaced with any other package or just pure DomPDF
composer require laraveldaily/laravel-invoices:^3.1
Then, we need to publish the package files:
php artisan invoices:install
This will create quite a few files, but we will only modify a few. First, let's open a config file and change our currency format:
config/invoices.php
// ... 'currency' => [ 'code' => 'eur', 'code' => 'usd', /* * Usually cents * Used when spelling out the amount and if your currency has decimals. * * Example: Amount in words: Eight hundred fifty thousand sixty-eight EUR and fifteen ct. */ 'fraction' => 'ct.', 'symbol' => '€', 'symbol' => '$', /* * Example: 19.00 */ 'decimals' => 2, /* * Example: 1.99 */ 'decimal_point' => '.', /* * By default empty. * Example: 1,999.00 */ 'thousands_separator' => '', /* * Supported tags {VALUE}, {SYMBOL}, {CODE} * Example: 1.99 € */ 'format' => '{VALUE} {SYMBOL}', 'format' => '{SYMBOL}{VALUE}',], 'seller' => [ // ... 'attributes' => [ // ... 'custom_fields' => [ /* * Custom attributes for Seller::class * * Used to display additional info on Seller section in invoice * attribute => value */ 'SWIFT' => 'BANK101', ], ],],
Then we can open the English translation file and change the Invoice
to Quote
:
resources/lang/vendor/invoices/en/invoice.php
// ... 'invoice' => 'Invoice','invoice' => 'Quote','serial' => 'Serial No.','date' => 'Invoice date','date' => 'Quote date', // ...
Now that our settings are in place and we have the package, we can work on the invoice generation. First, we need to create a new controller:
php artisan make:controller QuotePdfController
Then, we can open the Controller and add the following code:
app/Http/Controllers/QuotePdfController.php
use App\Models\Quote;use Illuminate\Http\Request;use LaravelDaily\Invoices\Classes\Buyer;use LaravelDaily\Invoices\Classes\InvoiceItem;use LaravelDaily\Invoices\Invoice; class QuotePdfController extends Controller{ public function __invoke(Request $request, Quote $quote) { $quote->load(['quoteProducts.product', 'customer']); $customer = new Buyer([ 'name' => $quote->customer->first_name . ' ' . $quote->customer->last_name, 'custom_fields' => [ 'email' => $quote->customer->email, ], ]); $items = []; foreach ($quote->quoteProducts as $product) { $items[] = (new InvoiceItem()) ->title($product->product->name) ->pricePerUnit($product->price) ->subTotalPrice($product->price * $product->quantity) ->quantity($product->quantity); } $invoice = Invoice::make() ->sequence($quote->id) ->buyer($customer) ->taxRate($quote->taxes) ->totalAmount($quote->total) ->addItems($items); if ($request->has('preview')) { return $invoice->stream(); } return $invoice->download(); }}
Here's what our code does:
InvoiceItem
classpreview
parameter is present, it streams the PDFNow we can register our new route:
routes/web.php
use App\Http\Controllers\QuotePdfController; // ... Route::middleware('signed') ->get('quotes/{quote}/pdf', QuotePdfController::class) ->name('quotes.pdf');
Last on our List is the PDF display on the view page and the ability to download it. To do that, we need to add an InfoList:
app/Filament/Resources/QuoteResource.php
use Filament\Infolists\Components\ViewEntry;use Filament\Infolists\Infolist; // ... public static function infolist(Infolist $infolist): Infolist{ return $infolist ->schema([ ViewEntry::make('invoice') ->columnSpanFull() ->viewData([ 'record' => $infolist->record ]) ->view('infolists.components.quote-invoice-view') ]);} // ...
We simply added a ViewEntry with a custom view. Now we can create the view:
resources/views/infolists/components/quote-invoice-view.blade.php
<iframe src="{{ URL::signedRoute('quotes.pdf', [$record->id, 'preview' => true]) }}" style="min-height: 100svh;" class="w-full"> </iframe>
Here, we created an iframe with a signed URL to our PDF controller. Now, visiting the View page, we should see a PDF:
There are still a couple of minor things we should do:
Please pay until:
from our Quote as it's not neededLet's start with the button. We can add it to our View page:
app/Filament/Resources/QuoteResource/Pages/ViewQuote.php
use Filament\Actions\Action;use URL; // ... protected function getHeaderActions(): array{ return [ Action::make('Edit Quote') ->icon('heroicon-m-pencil-square') ->url(EditQuote::getUrl([$this->record])), Action::make('Download Quote') ->icon('heroicon-s-document-check') ->url(URL::signedRoute('quotes.pdf', [$this->record->id]), true), ];}
Now, loading the page, we should see a new button:
Next, we can modify our Invoice template that was published to comment out the pay until
text:
resources/views/vendor/invoices/templates/default.blade.php
{{-- ... --}} <p> {{ trans('invoices::invoice.pay_until') }}: {{ $invoice->getPayUntilDate() }}</p> {{-- ... --}}
Now we can reload the page, and we should see the text removed:
That's it! Now, our users can create quotes for their Customers and send them out as PDFs.