In this tutorial we’ll build a basic Laravel demo that allows users to upload invoice files, process them asynchronously with a queue job using the iQSuite SDK, store extracted invoice details in the database, and then display a listing of created invoices.
Prerequisites
- Laravel installed and configured
- A working queue driver (for example, the database queue)
- iQSuite SDK installed and configured
Install iQ Suite SDK
composer require iqsuite/platform-sdk-laravel
- Publish the config file:
php artisan vendor:publish --tag=iqsuite-config
.
Get Your API Key
- Visit iQ Suite Platform.
- Sign up with your email or GitHub account.
- Navigate to "API Keys" in the sidebar
- Create a new API key
- Save your API key securely - it won't be shown again.
- Update the
.env
variables:IQSUITE_API_KEY=iq-XXXXXXXXXXXXXXXXXXXXXXX
.
1. Create the Invoice Model and Migration
First, create an Invoice
model along with a migration that defines the following fields: invoice_number, invoice_date, amount, type_of_invoice, and the default timestamps.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Invoice extends Model
{
protected $fillable = [
'invoice_number',
'invoice_date',
'amount',
'type_of_invoice',
];
}
Generate and update the migration:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateInvoicesTable extends Migration
{
public function up()
{
Schema::create('invoices', function (Blueprint $table) {
$table->id();
$table->string('invoice_number');
$table->string('invoice_date');
$table->decimal('amount', 10, 2);
$table->string('type_of_invoice');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('invoices');
}
}
Run the migration:
php artisan migrate
2. Create the Upload Form
Create a simple Blade view to allow users to upload invoice files.
// resources/views/invoice/upload.blade.php
<!DOCTYPE html>
<html>
<head>
<title>Upload Invoice</title>
</head>
<body>
<h1>Upload Invoice File</h1>
<form action="{{ route('invoice.upload') }}" method="POST" enctype="multipart/form-data">
@csrf
<input type="file" name="invoice_file" required>
<button type="submit">Upload</button>
</form>
</body>
</html>
3. Build the Invoice Controller
The InvoiceController
handles the file upload. It stores the file and dispatches a queue job to process the file using the iQSuite SDK.
<?php
namespace App\Http\Controllers;
use App\Jobs\ProcessInvoiceJob;
use Illuminate\Http\Request;
use App\Models\Invoice;
class InvoiceController extends Controller
{
public function showUploadForm()
{
return view('invoice.upload');
}
// Process invoice file upload
public function upload(Request $request)
{
$request->validate(['invoice_file' => 'required|file']);
// Get the file, store it, then pass file path along with original file details.
$file = $request->file('invoice_file');
$filePath = $file->store('invoices');
$originalName = $file->getClientOriginalName();
$mimeType = $file->getClientMimeType();
ProcessInvoiceJob::dispatch($filePath, $originalName, $mimeType);
return response()->json(['message' => 'Invoice uploaded and processing started.']);
}
// List all invoices in a table view.
public function index()
{
$invoices = Invoice::orderBy('created_at', 'desc')->get();
return view('invoice.index', compact('invoices'));
}
}
Add routes to web.php
:
<?php
use App\Http\Controllers\InvoiceController;
Route::get('invoice/upload', [InvoiceController::class, 'showUploadForm'])->name('invoice.form');
Route::post('invoice/upload', [InvoiceController::class, 'upload'])->name('invoice.upload');
// New route for listing invoices
Route::get('invoice/list', [InvoiceController::class, 'index'])->name('invoice.list');
4. Create the Queue Job to Process Invoices
The ProcessInvoiceJob
reads the stored file, re-instantiates an UploadedFile
instance (to support the iQSuite SDK’s requirement for getClientOriginalName()
), and then calls the SDK functions. It extracts invoice data and creates a new record in the database.
Run the following command to create a job:
php artisan make:job ProcessInvoiceJob
<?php
namespace App\Jobs;
use App\Models\Invoice;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Http\UploadedFile;
use IQSuite\Platform\Facades\IQSuite;
use Illuminate\Support\Facades\Storage;
class ProcessInvoiceJob implements ShouldQueue
{
use Queueable;
protected $filePath;
protected $originalName;
protected $mimeType;
protected $indexID;
protected $prompt;
public function __construct($filePath, $originalName, $mimeType, $indexID = null)
{
$this->filePath = $filePath;
$this->indexID = $indexID;
$this->originalName = $originalName;
$this->mimeType = $mimeType;
*$this->prompt = "
From the given document, please extract the following invoice detai*ls: \n
{'invoice_number', 'invoice_date', 'amount', 'type_of_invoice'} \n
- amount: should always be a number \n
- Type of Invoice (sale, purchase, rent, etc) \n
Please extract the details in JSON wrapped with triple backticks. Please only return the valid JSON and nothing else.
";
}
public function handle(): void
{
try {
$realPath = Storage::path($this->filePath);
// Re-create the UploadedFile instance to satisfy the SDK requirements.
$file = new UploadedFile(
$realPath,
$this->originalName,
$this->mimeType,
null,
true
);
// Create new index and upload document.
$response = IQSuite::createIndexAndPoll($file);
$index = $response->index_id ?? null;
// Extract invoice details.
$extracted = IQSuite::retrieve($index, $this->prompt);
$cleanedResponse = str_replace('```', '', $extracted->data->retrieval_response);
$cleanedResponse = json_decode($cleanedResponse, true);
Invoice::create([
'invoice_number' => $cleanedResponse['invoice_number'] ?? 'N/A',
'invoice_date' => $cleanedResponse['invoice_date'] ?? now()->toDateString(),
'amount' => $cleanedResponse['amount'] ?? 0,
'type_of_invoice'=> $cleanedResponse['type_of_invoice'] ?? 'Unknown',
]);
logger()->info('Invoice created');
} catch (\Throwable $th) {
logger()->error($th->getMessage());
}
}
}
Remember to configure your queue (for example, using the database queue) and start a worker:
php artisan queue:table
php artisan migrate
php artisan queue:work
php artisan queue:work --timeout=3600 **//use this if timeout exceeds it's limit**
5. Create a View for Listing Invoices
Finally, create a Blade view to display all created invoices in a table.
// resources/views/invoice/index.blade.php
<!DOCTYPE html>
<html>
<head>
<title>Invoice List</title>
<style>
table, th, td { border: 1px solid #000; border-collapse: collapse; padding: 8px; }
</style>
</head>
<body>
<h1>All Invoices</h1>
<table>
<thead>
<tr>
<th>ID</th>
<th>Invoice Number</th>
<th>Invoice Date</th>
<th>Amount</th>
<th>Type of Invoice</th>
<th>Created At</th>
</tr>
</thead>
<tbody>
@foreach($invoices as $invoice)
<tr>
<td>{{ $invoice->id }}</td>
<td>{{ $invoice->invoice_number }}</td>
<td>{{ $invoice->invoice_date }}</td>
<td>{{ $invoice->amount }}</td>
<td>{{ $invoice->type_of_invoice }}</td>
<td>{{ $invoice->created_at }}</td>
</tr>
@endforeach
</tbody>
</table>
</body>
</html>
Logging
We recommend using Laravel’s first party package pail
to trail logs locally.
composer require laravel/pail
and then once installed, run this in your terminal:
php artisan pail
and you’ll get to see streaming logs.
Conclusion
You now have a working demo of iQ Suite that supports:
- Uploading invoice files via a Blade form.
- Processing long-running tasks with a queue job (using the iQSuite SDK) to extract invoice details.
- Storing invoice data in the database.
- Listing all created invoices on a new page.